diff --git a/AGENTS.md b/AGENTS.md index eb9d690c..bf215f41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,7 +43,7 @@ Rules that implement a standardized interface must match that standard's semanti |---|---| | `src/rules/validation/` | Read-only rules (view functions, no state changes during transfer) | | `src/rules/operation/` | Read-write rules (modify state on transfer) | -| `src/rules/validation/abstract/core/` | `RuleTransferValidation` (ERC-1404/3643/7551 views), `RuleNFTAdapter` (ERC-7943 + `ITransferContext` overloads), `RuleWhitelistShared`, `TokenSupplyReader` (revert-free `totalSupply()` read, shared by `RuleMaxTotalSupply` and `RuleChainlinkPoR`), `ChainlinkPoRFeedManager` (PoR feed configuration + the revert-free reserve read; **no constructor, no ERC-1404**, so it is reusable and initializer-agnostic), `TotalSupplyCapManager` and `BalanceCapManager` (the same split for `RuleMaxTotalSupply` and `RuleMaxBalance`: state, setters and the revert-free read, with the constructor and the restriction-code mapping left in the rule) | +| `src/rules/validation/abstract/core/` | `RuleTransferValidation` (ERC-1404/3643/7551 views), `RuleNFTAdapter` (ERC-7943 + `ITransferContext` overloads), `RuleWhitelistShared`, `TokenSupplyReader` (revert-free `totalSupply()` read, shared by `RuleMaxTotalSupply` and `RuleChainlinkPoR`), `ChainlinkPoRFeedManager` (PoR feed configuration + the revert-free reserve read; **no constructor, no ERC-1404**, so it is reusable and initializer-agnostic), `TotalSupplyCapManager` and `BalanceCapManager` (the same split for `RuleMaxTotalSupply` and `RuleMaxBalance`: state, setters and the revert-free read, with the constructor and the restriction-code mapping left in the rule), `CapAccounting` (the cap comparison shared by all three cap rules; stateless, so it adds no slot) | | `src/rules/validation/abstract/` | Shared base contracts and invariant storage | | `src/rules/interfaces/` | Shared interfaces (`IAddressList`, `IIdentityRegistry`, `ISanctionsList`, `ITotalSupply`, `ITransferContext`, `IERC2980`, `IERC7943NonFungibleCompliance`, `AggregatorV3Interface`, `IDecimals`) | | `src/registry/` | Contracts filling a token's **identity registry** slot, not its compliance slot (`IdentityRegistryWhitelist`). Not rules: no `IRule`, never added to a RuleEngine | @@ -71,7 +71,9 @@ Rules that implement a standardized interface must match that standard's semanti | `RuleSanctionsList` | Block sanctioned addresses via Chainalysis oracle | | `RuleMaxBalance` / `RuleMaxBalanceOwnable2Step` | Cap how many tokens **one address** may hold, with an operator-managed exemption list. Screens the receiver only; burns exempt; mints capped. Codes 82, 83. **Bypassable by splitting across wallets** — pair with a rule admitting one address per investor (`RuleWhitelist` / `RuleReceiverWhitelist` / `RuleIdentityRegistry`) and an onboarding policy of one address per entity. `maxBalance = 0` forbids holding, it does NOT disable the rule | | `RuleMaxTotalSupply` | Cap minting so total supply never exceeds a maximum | +| `RuleMaxTotalSupplyERC3643` / `…Ownable2Step` | **ERC-3643-only** variant of `RuleMaxTotalSupply`, same re-phasing as `RuleChainlinkPoRERC3643` and the same silent-misconfiguration warning. Compose with `RuleChainlinkPoRERC3643` to add a static ceiling to the reserve-backed one. Codes 50, 51 | | `RuleChainlinkPoR` / `RuleChainlinkPoROwnable2Step` | Cap minting at the reserves reported by a Chainlink Proof of Reserve feed (`AggregatorV3Interface`). The limit equals the reported reserves exactly — no margin parameter (deliberately dropped from Chainlink's `SecureMintPolicy`); compose with `RuleMaxTotalSupply` for a static cap. Mints only; transfers and burns always pass, so a stale or broken feed never traps holders. The read path is guarded (`code.length` check + `try/catch` + saturating arithmetic) so the ERC-1404 views never revert | +| `RuleChainlinkPoRERC3643` / `…Ownable2Step` | **ERC-3643-only** variant of `RuleChainlinkPoR`. Identical reserve logic; re-phases the **write** path because ERC-3643 calls compliance *after* the mint (`_mint` then `created`, forwarded by `RuleEngine` as the 3-arg `transferred(0, to, value)`), so `totalSupply()` already includes it. The read views still project the pending amount — the token calls `canTransfer` *before* `_mint`. Do not use with CMTAT, and do not use the stock rule with ERC-3643: both mistakes are silent. Codes 75–79 | | `RuleIdentityRegistry` | Check ERC-3643 identity registry for participant verification | | `IdentityRegistryWhitelist` | The mirror image of `RuleIdentityRegistry`: **is** an ERC-3643 identity registry, backed by a whitelist, so no ONCHAINID is needed. Keeps **no identity state** — `_identity` and `_country` are accepted for signature compatibility then discarded, and `investorCountry` is a constant 0; do not add identity storage back. Inherits `RuleAddressSetInternal` (the same set machinery as `RuleWhitelist`) rather than deploying or re-implementing a whitelist — **only the internal layer**, because `RuleAddressSet`'s public `addAddress`/`removeAddress` are not `virtual` and so could not maintain the `keyHasPurpose` reverse index; a second write path would produce verified-but-unrecoverable wallets. Installed with `token.setIdentityRegistry()`. Implements **no** ERC-734 surface: `recoveryAddress` needs a real ONCHAINID as `_investorOnchainID`. A `keyHasPurpose` implementation was tried and removed — the agent chooses which contract that call lands on, so it added no security while forcing a hash-to-wallet reverse index and duplicate-tolerant registration. Do not re-add it. The token itself must hold `IDENTITY_REGISTRAR_ROLE`. See `doc/technical/contracts/IdentityRegistryWhitelist.md` | | `RuleSpenderWhitelist` / `RuleSpenderWhitelistOwnable2Step` | Allow `transferFrom` only when spender is whitelisted; direct transfers are always allowed | @@ -90,7 +92,7 @@ Rules that implement a standardized interface must match that standard's semanti - `openzeppelin-contracts` v5.7.0 — `AccessControl`, `Ownable2Step`, `EnumerableSet`, `ERC2771Context` - `openzeppelin-contracts-upgradeable` v5.7.0 - `CMTAT` v3.3.0-rc3 (submodule pin; library supports ≥ v3.0.0) — `IERC1404`, `IERC3643`, `IRuleEngine` interfaces -- `RuleEngine` v3.0.0-rc5 — `IRule`, `RulesManagementModule` +- `RuleEngine` v3.0.0-rc6 — `IRule`, `RulesManagementModule` - `forge-std` — Foundry test utilities Remappings are in `remappings.txt`; aliases used in source: `@openzeppelin/`, `CMTAT/`, `RuleEngine/`. @@ -147,10 +149,10 @@ sees stay context-free. Hardhat compiles only `src/` (Foundry's `src`), so it ne - Access control is implemented via an abstract `_authorize*()` method overridden by concrete subclasses (template method pattern). - AccessControl variants must use `onlyRole(ROLE)` in `_authorize*()` methods (avoid direct `_checkRole`). - **All `_authorize*()` / `_only*()` access-control hooks are `internal view virtual`** — both the abstract declaration and every override. An authorization hook checks and reverts; it must never mutate state, and `view` makes that a compiler-enforced invariant rather than a convention. It is free: these are `internal`, so `view` costs no gas and changes no runtime behaviour. Both OZ check functions (`AccessControl._checkRole`, `Ownable._checkOwner`) are `view`, so every hook can be. - - **One documented exception**: `RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange` cannot be `view`, because it delegates to `_onlyComplianceManager()`, which `lib/RuleEngine` declares non-`view` (Solidity checks mutability against a virtual's *declared* type, not the installed override). If you hit this constraint elsewhere, document why inline — do not silently drop `view` from a hook. + - **There is no longer an exception.** `RuleConditionalTransferLightMultiTokenBase` used to carry a non-`view` `_authorizeComplianceBindingChange` that merely delegated to `_onlyComplianceManager()` — non-`view` because Solidity checks mutability against a virtual's *declared* type, not the installed override. RuleEngine v3.0.0-rc6 supplies that delegation as the default body of `_authorizeTokenBindingChange`, so the override was deleted rather than renamed. If you hit the same constraint again, document why inline — do not silently drop `view` from a hook. - AccessControl variants treat the default admin as having all roles via `hasRole`, but the admin may not appear in role member enumerations unless explicitly granted. -- All rules **and `IdentityRegistryWhitelist`** implement `IERC3643Version` via `VersionModule`; the current version string is `"0.5.0"`. `test/Version.t.sol` asserts it for every deployable contract — keep it exhaustive, a half-covered version test reads as authoritative while missing the mirror it exists to catch. -- **ERC-165 interface IDs**: `type(IFoo).interfaceId` only XORs selectors defined directly on `IFoo` and does NOT include selectors from inherited interfaces. Always use the pre-computed library constants instead: `ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID` (from `CMTAT/library/`), `RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID` (from `CMTAT/library/`), and `RuleInterfaceId.IRULE_INTERFACE_ID` (from `RuleEngine/modules/library/`). If no pre-computed constant exists for an interface, define a flat mock interface that redeclares all functions from the full inheritance tree and use `type(IFooFlattened).interfaceId` to compute the correct value (see `lib/RuleEngine/src/mocks/IRuleInterfaceIdHelper.sol` for the established pattern). +- All rules **and `IdentityRegistryWhitelist`** implement `IERC3643Version` via `VersionModule`; the current version string is `"0.6.0"`. `test/Version.t.sol` asserts it for every deployable contract — keep it exhaustive, a half-covered version test reads as authoritative while missing the mirror it exists to catch. +- **ERC-165 interface IDs**: `type(IFoo).interfaceId` only XORs selectors defined directly on `IFoo` and does NOT include selectors from inherited interfaces. Always use the pre-computed library constants instead: `ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID` (from `CMTAT/library/`), `RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID` (from `CMTAT/library/`), and `RuleInterfaceId.IRULE_INTERFACE_ID` and `ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID` (both from `RuleEngine/modules/library/`). If no pre-computed constant exists for an interface, define a flat mock interface that redeclares all functions from the full inheritance tree and use `type(IFooFlattened).interfaceId` to compute the correct value (see `lib/RuleEngine/src/mocks/IRuleInterfaceIdHelper.sol` for the established pattern). - Batch add/remove operations are non-reverting **for duplicates and missing entries** (those are skipped and counted); single-item operations revert on the same input. The one exception is `address(0)`, which **reverts on every add path, batch included** — see I-12. A batch containing a zero entry therefore fails as a whole rather than being partially applied. - All `internal` functions should be marked `virtual`. - Do not create git commits; provide commit messages only when requested. @@ -166,6 +168,9 @@ sees stay context-free. Hardhat compiles only `src/` (Foundry's `src`), so it ne - `AGENTS.md` and `CLAUDE.md` are identical — always update both together. - **Two READMEs.** `README.md` at the root is a short summary (purpose, architecture, rule list, quick start) and is the GitHub front page; `doc/README.md` is the full reference. Update `doc/README.md` with the latest change, and the root `README.md` only when the summary itself becomes wrong (a new rule, a changed code range, a moved document). Links inside `doc/README.md` are relative to `doc/`. - New rule or features implemented: create/update technical documentation — a per-contract page in `doc/technical/contracts/`, or `doc/technical/guides/` when the material spans several contracts, update `doc/README.md` (and the root summary if the rule table or code ranges change), create/update test (target: 100% of code coverage), update CHANGELOG.md. Code coverage, run `forge coverage --report summary` +- **Do not hard-wrap prose in `CHANGELOG.md` — one line per bullet or paragraph.** Wrapping a bullet across several ~110-character lines renders identically (Markdown collapses a single newline into a space), so the cost is invisible in the published changelog and paid entirely in the repository. Two ways it hurts: **diffs stop being readable** — changing one word reflows every following line in the paragraph, so a one-word correction arrives as a twelve-line diff and a reviewer cannot see what actually changed; and **the file becomes inconsistent with itself**, because the wrap column depends on whoever wrote the entry. Both v0.5.0 and v0.6.0 were written hard-wrapped while every earlier release used one line per bullet, and the mixed result reads as damage. Let the editor soft-wrap. The exception is content where the line break is semantic — fenced code blocks, tables and blockquotes — which must keep their own line structure. +- **Break a long `CHANGELOG.md` entry into a lead sentence plus sub-bullets — do not ship a wall of prose.** Past roughly three sentences a bullet stops being scannable: the defect, its blast radius, the fix, the precedent and the caveat all run together, so a reader looking for any one of them must parse all five. Lead with one sentence naming *what changed*, then one sub-bullet per distinct claim — impact, fix, behaviour-change warning, cost, migration note. This is already the house style in the v0.3.0-v0.5.0 entries; the v0.6.0 entry was written flat and had to be restructured. A useful trigger is length: the median bullet in this file is ~230 characters, so anything past ~700 is almost certainly carrying several claims and should be split. Sub-bullets follow the same no-hard-wrap rule — one line each. +- **Never name an assistant tool, skill or slash command in `CHANGELOG.md`.** The changelog is a record of what changed in *this project*, read by integrators who have no idea what tooling produced it; a line ending "the `analyse-code-quality` skill gained the corresponding check" documents the author's toolbox, not the release. It also rots independently of the repository — a skill can be renamed or deleted, and the changelog entry is then a dangling reference to something the reader could never have seen. Describe the change and its effect on the code; if the tooling matters, it belongs in the audit report under `doc/security/audits/`, not here. **This is about tool identities, not the word "Claude":** `CLAUDE.md`, `AGENTS.md`, `CLAUDE_AUDIT.md` and `CLAUDE_ANALYSIS*.md` are files committed to this repository and are cited freely — a reader can open them. - After each implemented feature or fix, provide a one-line GitHub commit message for all changes since the last commit. ## Security Findings Reference @@ -177,11 +182,13 @@ sees stay context-free. Hardhat compiles only `src/` (Foundry's `src`), so it ne Gotchas worth knowing before you change anything: - **Deployment scripts must take the deployer as a parameter, never read `address(this)`.** Under `forge script` the broadcaster makes the calls, not the script contract, and Foundry rejects `address(this)` inside a broadcast outright. A script that reads it passes every unit test and reverts on the real deployment path, because the tests call `deploy()` directly and never enter a broadcast context. **No unit test can cover this**: Foundry refuses to combine a prank with a broadcast, so `run()` cannot be faithfully exercised from `forge test`. The guard is the `forge script` dry-run step in CI, which runs every `script/*.s.sol`. Shared token metadata and env configuration live in `script/base/CMTATDeploymentBase.sol`. See `CLAUDE_ANALYSIS_SCRIPT.md`. -- `HelperContract` already inherits `RuleConditionalTransferLightInvariantStorage`; inheriting the multi-token variant alongside it is a compile error (`OPERATOR_ROLE`, `CODE_TRANSFER_REQUEST_NOT_APPROVED` clash). +- `HelperContract` already inherits `RuleConditionalTransferLightInvariantStorage`; inheriting the multi-token variant alongside it is a compile error (`OPERATOR_ROLE`, `CODE_TRANSFER_REQUEST_NOT_APPROVED` clash). A multi-token test needing one of those errors must **re-declare it locally** — see `MultiTokenSurface.t.sol` and `RuleConditionalTransferLightMultiToken.t.sol`. +- `approveAndTransferIfAllowed` (both variants) inverts CEI deliberately — the approval is recorded before `safeTransferFrom` so the token's compliance callback can consume it — and therefore **ends with a post-condition** that the approval was consumed, reverting with `..._ApprovalNotConsumed` otherwise (NM-17). Without it a token that never calls back completed the transfer and left a spendable approval behind. The comparison is against the count *before* the helper ran, so an operator's own outstanding approvals survive; and it is read *after* the external call on purpose, so a hostile token can only make it fail, never pass. - `RuleWhitelistWrapperBase._detectTransferRestrictionForTargets` short-circuits once every target address is resolved, so a broken child rule may never be reached for some address pairs. -- `RuleWhitelistWrapper` does not ERC-165-check its child rules (unlike `RuleEngineBase._checkRule`); a non-`IAddressList` child bricks the scan. +- `RuleWhitelistWrapper` ERC-165-checks its child rules in a `_checkRule` override mirroring `RuleEngineBase`, and asks **two** questions (NM-18, NM-20, audit F-5). **Membership**: the child must advertise `IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID` (`0x20e8e17a`, the single `areAddressesListed(address[])` selector) — not the full `IADDRESS_LIST_INTERFACE_ID`, since that one function is all the wrapper calls, so demanding the other seven (four of them writes) would reject a valid read-only child. **Meaning**: the child must also advertise `IADDRESS_LIST_POLARITY_INTERFACE_ID` (`0xdc4efe10`, `isAllowList()`) and answer `true`; `IAddressList` describes membership only, so without this a `RuleBlacklist` passes every interface check and inverts the wrapper. **Absence of the polarity declaration is a refusal, never an assumed allow-list** — the only fail-closed reading. `RuleWhitelist` / `RuleReceiverWhitelist` declare `true`, `RuleBlacklist` declares `false`, and **`RuleSpenderWhitelist` deliberately declines and must not be changed to declare `true`**: its set is permitted *spenders*, not *holders*, so an honest polarity answer would still let the wrapper read spenders as eligible participants. `IAddressListBatchQuery` and `IAddressListPolarity` were split out of `IAddressList`; the flattened selector set is unchanged, so `0x5d10e182` keeps its value, and both sub-interface ids are safe as literals because they inherit nothing. **Wrappers deliberately cannot nest** (NM-19, declined): the wrapper is an OR, and an OR nested in an OR is algebraically flat, so nesting adds no expressive power while costing multiplicatively and opening an `A → B → A` cycle class. Use several wrappers in the `RuleEngine` for AND-of-ORs. Do not "fix" this by implementing `areAddressesListed` on the wrapper. - `RuleChainlinkPoR` reads the feed's `decimals()` **live on every check** and deliberately does NOT cache it. Caching saves ~2,900 gas per mint but lets an aggregator migration that changes decimals mis-scale the reserves by `10 ** delta` with no on-chain signal — in the overstating direction that is unlimited unbacked minting. Both feed calls share the `code.length` guard (Solidity's extcodesize revert on a `try` to a codeless address is uncatchable) and `MAX_FEED_DECIMALS` is re-checked at read time, not just at configuration. Do not "optimise" this back into a cache. - `RuleChainlinkPoR` (and `RuleMaxTotalSupply`) protect **one token per instance** with no on-chain guard: they read `totalSupply()` from the configured `tokenContract`, never from the token that triggered the check, and behind a RuleEngine they cannot learn that identity. One instance added to two RuleEngines evaluates both tokens against the first one's supply and feed — silently over-minting or freezing the second. Chainlink's `SecureMintPolicy` blocks this with `onInstall`/`PolicyAlreadyBound`; adding an equivalent here would mean making a stateless validation rule bindable, which is a library-wide decision. Documented, not fixed. - `ADDRESS_LIST_ADD_ROLE` / `ADDRESS_LIST_REMOVE_ROLE` live in `RuleAddressSetRolesStorage`, inherited by `RuleAddressSet` (the public layer that enforces them) — **not** by `RuleAddressSetInternal`. Do not move them back into `RuleAddressSetInvariantStorage`: a contract reusing only the internal layer (`IdentityRegistryWhitelist`) would then publish two roles it never checks, and an operator granting one would get no privilege and no signal. -- `RuleChainlinkPoR` and `RuleMaxTotalSupply` both guard `tokenContract.totalSupply()` with a code-length check plus `try/catch`, returning a restriction code (78 and 51 respectively) rather than reverting, and both validate the token at configuration (non-zero, has code, `totalSupply()` callable). The shared mechanics — the revert-free read and the configuration probe — live in `TokenSupplyReader`; each rule supplies its own token via the `_supplyToken()` hook (so the base holds **no storage** and cannot reorder any rule's slots) and raises its own named errors. Do not move the `require`s into the base: three distinct configuration failures deliberately keep three distinct per-rule errors. The ERC-1404 views MUST NOT revert — never call `totalSupply()` unguarded on a read path. Neither rule re-checks `code.length` at read time: `try/catch` cannot catch a call to a codeless address (the ABI decoder fails in the caller's frame, outside `catch`'s reach -- **not** `EXTCODESIZE`, which solc >= 0.8.10 skips when return data is expected), but the setters require code and EIP-6780 (Cancun) makes that permanent, so the check would be unreachable. This is a **deployment precondition** (Cancun or later, `foundry.toml` targets `prague`), documented in each rule doc rather than enforced at runtime — do not re-add the guard unless targeting a pre-Cancun chain. `decimals()` stays optional on the token; `totalSupply()` is mandatory. The two rules use *different* constant names for the same idea (`CODE_TOTAL_SUPPLY_UNAVAILABLE` vs `CODE_SUPPLY_ORACLE_UNAVAILABLE`) because `HelperContract` inherits both invariant-storage contracts and identical identifiers would clash. +- `RuleChainlinkPoR` and `RuleMaxTotalSupply` both guard `tokenContract.totalSupply()` with a code-length check plus `try/catch`, returning a restriction code (78 and 51 respectively) rather than reverting, and both validate the token at configuration (non-zero, has code, `totalSupply()` callable). The shared mechanics — the revert-free read and the configuration probe — live in `TokenSupplyReader`; each rule supplies its own token via the `_supplyToken()` hook (so the base holds **no storage** and cannot reorder any rule's slots) and raises its own named errors. Do not move the `require`s into the base: three distinct configuration failures deliberately keep three distinct per-rule errors. The ERC-1404 views MUST NOT revert — never call `totalSupply()` unguarded on a read path. Neither rule re-checks `code.length` at read time: `try/catch` cannot catch a call to a codeless address (the ABI decoder fails in the caller's frame, outside `catch`'s reach -- **not** `EXTCODESIZE`, which solc >= 0.8.10 skips when return data is expected), but the setters require code and EIP-6780 (Cancun) makes that permanent, so the check would be unreachable. This is a **deployment precondition** (Cancun or later, `foundry.toml` targets `prague`), documented in each rule doc rather than enforced at runtime — do not re-add the guard unless targeting a pre-Cancun chain. A low-level `staticcall` + `returndata.length` rewrite would also contain a callee that succeeds while returning short data (NM-23/24) — **considered and declined**: it is eight `try` blocks across three files, trades a known idiom for hand-rolled ABI plumbing where `abi.decode` is an assertion rather than a compiler check, and the only behaviour that improves is a diagnostic code instead of a revert on a token that has already stopped honouring its interface (fail-closed either way). Revisit only for a pre-Cancun chain or a concrete proxy-upgrade expectation, and then as **one shared helper**, not eight call sites. `decimals()` stays optional on the token; `totalSupply()` is mandatory. The two rules use *different* constant names for the same idea (`CODE_TOTAL_SUPPLY_UNAVAILABLE` vs `CODE_SUPPLY_ORACLE_UNAVAILABLE`) because `HelperContract` inherits both invariant-storage contracts and identical identifiers would clash. +- **The three cap rules (`RuleMaxBalance`, `RuleMaxTotalSupply`, `RuleChainlinkPoR`) assume the token notifies BEFORE it moves the value**, so the observation still excludes it. CMTAT does; ERC-3643 / T-REX does **not** (`Token.transfer` runs `_transfer` then `transferred`; `mint` runs `_mint` then `created`), and on such a token the stock rule counts the value twice and halves the effective cap (NM-11). Adapting is one override of `_detectTransferRestrictionOnNotify`, the hook the **write** path enforces through: `return _detectTransferRestriction(from, to, 0)`. **Do not route the read path through it** — a pre-flight view always runs before the movement on either kind of token, so it must always project the value; re-phasing it makes the pre-flight answer disagree with enforcement, which is the mirror image of the bug. The shared comparison lives in `CapAccounting`, which is stateless and deliberately holds no accounting-phase flag for exactly this reason. Second seam: `_currentSupply` / `_balanceOf` are `internal view virtual`, so a rule may serve the figure from its own storage — viable for *supply*, **not** for per-address balances, because `Token.recoveryAddress` notifies compliance on T-REX <= 4.1 (via the public `forcedTransfer`) but not on the vendored 4.2.0-beta1 (direct `_transfer`), so a shadow ledger's correctness would hinge on the token's minor version. See `doc/technical/guides/RULE_SEMANTICS.md` §5. **§6 of the same page is the per-rule ERC-3643 compatibility matrix** — which rules work, which have an inert leg (anything spender-based: no spender is ever forwarded on that path), which need their `…ERC3643` variant, and which are unsupported (`RuleMaxBalance`, `RuleConditionalTransferLightMultiToken`, and `RuleMintAllowance`, the last being silently permissive rather than restrictive). - `RuleChainlinkPoR` accepts `tokenDecimals == 0`. Chainlink's `SecureMintPolicy` requires 1–18, but CMTAT equity tokens report 0 decimals, so the lower bound was dropped. Do not re-add it. diff --git a/CHANGELOG.md b/CHANGELOG.md index d22c057f..4eae32f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ See [https://semver.org](https://semver.org) ## Type of changes +- `Summary`: main new features/change with a description (keep it short) (not a changelog tag) - `Added` for new features. - `Changed` for changes in existing functionality. - `Deprecated` for soon-to-be removed features. @@ -45,19 +46,161 @@ Custom changelog tag: `Dependencies`, `Documentation`, `Testing` -## Unreleased +## v0.6.0 - 2026-08-24 -_Nothing yet._ +Commit: -## v0.5.0 - +### Summary + +- Response to the first Nethermind AuditAgent scan: seven findings fixed, sixteen accepted as design, one declined. +- Adds ERC-3643 variants of the two supply-cap rules (ERC-3643 calls compliance *after* moving the value, so the stock rules counted the amount twice), two marker interfaces that let `RuleWhitelistWrapper` reject children it previously accepted, and a shared `CapAccounting` primitive. **No storage-layout or ABI change** — `v0.5.0` deployments are unaffected unless they adopt the new contracts. + +### Fixed + +- **NM-3 (Nethermind AuditAgent)** — `RuleIdentityRegistryBase._detectTransferRestrictionFrom` returned `TRANSFER_OK` outright when the identity registry was unset or the transfer was a burn, instead of delegating to `_detectTransferRestriction`. + - A subclass extending only that hook — the natural place to add a check — therefore applied to `transfer` but silently **not** to `transferFrom` or `burnFrom`. The guard now delegates, so the two entrypoints can no longer disagree. + - This is the same anti-pattern `RuleSanctionsListBase` was restructured to remove (`CLAUDE_ANALYSIS.md` F-2); the two sibling rules are now consistent. + - **Behaviour-preserving**: both early returns duplicated guards the delegate already performs, and the 21 pre-existing `RuleIdentityRegistry` tests pass unmodified. Burn remains exempt from the opt-in `checkSpender` check. +- **NM-10 (Nethermind AuditAgent)** — `ChainlinkPoRFeedManager._maxBackedSupply` flagged a Proof-of-Reserve feed as stale only when `block.timestamp > updatedAt`. + - That term was an underflow guard, and its side effect was that **any** future-dated round counted as fresh: a feed frozen on an old reserve answer but stamped ahead of the block could keep authorising mints until that timestamp elapsed. + - A future `updatedAt` is now treated as a **malformed answer** (`CODE_RESERVES_ANSWER_INVALID`, 77), alongside a negative reserve and an incomplete round. + - It is rejected **regardless of `maxStalenessSeconds`** — zero disables *freshness* checking, and an operator who opts out of that must not thereby accept a timestamp no aggregator on this chain could have written. + - The now-redundant underflow guard was dropped from the staleness comparison. + +- **NM-6 (Nethermind AuditAgent)** — `RuleNFTAdapter`'s ERC-7943 spender-aware overloads (`transferred`, `detectTransferRestrictionFrom`, `canTransferFrom`) called the delegated hook unconditionally, while the two `ITransferContext` entrypoints normalised `sender == from` to the direct hook; the two surfaces therefore gave different compliance answers for the same owner-initiated transfer. + - The three interfaces signal a direct transfer differently — ERC-7943 documents its `spender` as "the address performing the transfer (**owner**/operator)" and `ctx.sender` is the token's `msg.sender`, so on both an owner arrives as `spender == from`, whereas the CMTAT path uses the 3-arg overload or `spender == address(0)`. + - The adapter now routes every entrypoint through a shared `_isDelegated(spender, from)` predicate. + - **The 4-arg CMTAT path is deliberately left unchanged**, since its own convention already distinguishes the two cases — so the primary integration path, and every restriction code an existing integrator sees, are untouched. + - The one behavioural correction is `RuleSpenderWhitelist`: an owner-initiated ERC-721 `transferFrom` was rejected with code `66` despite the rule documenting that direct transfers are always allowed. The deny-list rules blocked such a transfer before and after; only which leg reported it changed. + +### Added + +- **`RuleMaxTotalSupplyERC3643` / `RuleMaxTotalSupplyERC3643Ownable2Step`** — static supply caps for **ERC-3643 tokens**, the same re-phasing as the Proof-of-Reserve variants below and for the same reason: the token mints first and reports through `created` afterwards, which `RuleEngine` forwards as the three-argument `transferred(address(0), to, value)`, so `totalSupply()` already includes the new tokens. + - Cap logic, restriction codes (50, 51), configuration, roles and events are inherited unchanged, and the read views are deliberately not re-phased. + - **Not interchangeable with the stock rule, and neither mistake reverts at deployment.** + - Designed to compose with `RuleChainlinkPoRERC3643`, which has no margin parameter: add both to one engine for a static ceiling alongside the reserve-backed one, remembering the engine reports the first non-zero code. + +- **`RuleChainlinkPoRERC3643` / `RuleChainlinkPoRERC3643Ownable2Step`** — Proof-of-Reserve minting caps for **ERC-3643 tokens**, the first consumers of the seam above. + - ERC-3643 / T-REX calls compliance *after* it has moved the value: `mint` runs `_mint(_to, _amount)` and only then `_tokenCompliance.created(_to, _amount)`, which `RuleEngine` forwards to each rule as the three-argument `transferred(address(0), to, value)`. `totalSupply()` therefore already includes the new tokens, so the variant overrides `_detectTransferRestrictionOnNotify` to re-ask with nothing left to add. + - Reserve logic, restriction codes (75–79), configuration, roles and events are inherited unchanged. + - **The read views are deliberately not re-phased** — ERC-3643 calls `canTransfer(address(0), to, amount)` *before* `_mint`, so a pre-flight query must still project the amount; the two consultations then reduce to the same condition. + - **The variants are not interchangeable with the stock rule and neither mistake reverts at deployment**: the stock rule on ERC-3643 counts the amount twice and reverts fully backed mints, and the variant on CMTAT weakens enforcement. + +- **`CapAccounting`** (`src/rules/validation/abstract/core/CapAccounting.sol`) — the cap comparison that `RuleMaxBalance`, `RuleMaxTotalSupply` and `RuleChainlinkPoR` each wrote out separately, now in one place. Stateless and constructor-free, so storage layouts are unchanged (verified byte-identical for all six deployables) and an upgradeable variant may adopt it. It deliberately carries no notion of pre- or post-update accounting: whether the observation already includes the moved value depends on which *path* is running, not on the rule. +- **`_detectTransferRestrictionOnNotify`** on all three cap rules — the seam an ERC-3643 variant overrides. The write hooks now route through it; it defaults to the pre-flight check, which is the CMTAT behaviour, so nothing changes by default. A token that notifies *after* moving the value (ERC-3643 / T-REX) needs one override: `return _detectTransferRestriction(from, to, 0)`. The read path is deliberately not routed through it — a pre-flight view always runs before the movement, so it must always project the value. +- Documented the second seam, **observation source** (`_currentSupply` / `_balanceOf`, both already `internal view virtual`), which lets a rule serve the figure from its own storage instead of calling the token. A rule that keeps its own running total controls when it updates it and so is immune to the accounting-phase question entirely. + +- **NM-18 (Nethermind AuditAgent) / audit F-5** — `RuleWhitelistWrapper` now ERC-165-checks its child rules. `_checkRule` is overridden exactly as `RuleEngineBase` does it, so one override covers both `addRule` and `setRules`; a candidate that does not advertise the required interface is rejected with `RuleWhitelistWrapper_ChildIsNotAnAddressList(rule)`. Previously a valid `IRule` that was not an address list was accepted and then reverted the blind `areAddressesListed` call during a transfer — and the early exit in the child scan made that *input-dependent*, so the wrapper looked healthy until a pair needed the full scan. A nested wrapper is now refused for the same reason. +- **A purpose-built sub-interface, `IAddressListBatchQuery`**, split out of `IAddressList`. The wrapper calls exactly one of `IAddressList`'s eight functions, so the guard requires `IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID` (`0x20e8e17a`, the single `areAddressesListed(address[])` selector) rather than the full `IADDRESS_LIST_INTERFACE_ID`. + - Demanding the other seven, four of them **writes**, would reject a read-only child that works perfectly. + - Factoring the selector into a parent left the flattened selector set unchanged, so `0x5d10e182` keeps its value; the four address-list rules advertise both ids. + - Unlike the full id, the sub-interface id is safe as a literal, because it inherits nothing and so has no omitted-parent trap. + - **The guard still cannot check polarity** — a `RuleBlacklist` advertises the same ids and passes it (NM-20). + +- **NM-17 (Nethermind AuditAgent)** — `approveAndTransferIfAllowed` now asserts that the approval it created was consumed. + - Both variants invert checks-effects-interactions deliberately, recording the approval *before* `safeTransferFrom` so the token's compliance callback can consume it; nothing verified the callback arrived. A plain ERC-20 bound with `bindToken`, or a RuleEngine never bound or since unbound, therefore completed the transfer and left the approval standing — indistinguishable from an operator-created one, and enough to authorise a later never-approved transfer of exactly `(from, to, value)`. + - The helper now reverts with `RuleConditionalTransferLight_ApprovalNotConsumed` / `RuleConditionalTransferLightMultiToken_ApprovalNotConsumed`. + - **Behaviour change**: a deployment running the helper against a non-callback token now reverts instead of completing — that is the fix, not a side effect. + - The comparison is against the count *before* the helper ran, so an operator's own outstanding approvals for the same tuple survive; and the count is read *after* the external call on purpose, so a hostile token can only make the check fail, never pass. + - Cost: two warm `SLOAD`s on an operator-only path. + +- **NM-20 (Nethermind AuditAgent)** — `RuleWhitelistWrapper` now **rejects** a child whose list has the wrong polarity, rather than only documenting the hazard. + - `IAddressList` describes *membership*, so a `RuleBlacklist` implements it identically to a whitelist and advertises the same ids; ERC-165 alone could not separate them, and adding one made its blacklisted addresses whitelisted with `isVerified` returning `true` for them. + - A new one-function marker interface **`IAddressListPolarity`** (`isAllowList()`, id `0xdc4efe10`) makes the distinction expressible, and `_checkRule` now requires it *and* a `true` answer on top of the `IAddressListBatchQuery` check. New errors: `RuleWhitelistWrapper_ChildDoesNotDeclarePolarity` and `RuleWhitelistWrapper_ChildIsNotAnAllowList`. + - **Absence of the declaration is a refusal, never an assumed allow-list** — the only fail-closed reading. + - `RuleWhitelist` / `RuleReceiverWhitelist` declare `true`, `RuleBlacklist` declares `false`, and **`RuleSpenderWhitelist` deliberately declines the interface** (documented in its NatSpec as a must-not-change): its set is permitted *spenders*, not *holders*, so an honest `true` would still let the wrapper read spenders as eligible participants. + - That closes a second wrong-child class with the same mechanism, one that had previously been prose only. + +- **Two `internal` functions were missing `virtual`** (`RuleAddressSetInternal._requireNotZeroAddress`, `RuleERC2980Internal._requireNotZeroAddress`), against the project's own convention. Both are the batch zero-address guard passed to `AddressSetBatchLib` as an **internal function pointer** — which is also why Slither reports them as dead code. Verified before changing: `virtual` is legal there, dispatch genuinely reaches an override *through the pointer* (not obvious, since Solidity resolves such pointers at assignment), and the gas is **identical** (`addAddress` 92 220, `addAddresses` 140 637 either way). +- **Seven NatSpec blocks exceeded the project's stated 20-line ceiling**, all added earlier in this release: the four ERC-3643 variant headers and the three notification-seam blocks, the latter byte-identical across `RuleChainlinkPoRBase`, `RuleMaxTotalSupplyBase` and `RuleMaxBalanceBase`. Each keeps its conclusion and its warning; the derivations move to the contract pages and `RULE_SEMANTICS.md`, which already carried them. Max block is now 19 against a median of 4 (824 blocks measured). + +### Dependencies + +- **RuleEngine bumped to `v3.0.0-rc6`** (from `v3.0.0-rc5`). The release extracts the token-binding registry from `ERC3643ComplianceModule` into a standard-agnostic `TokenBindingModule` / `ITokenBinding`, which renames two hooks the operation rules override. **Source-level breaking change for anyone subclassing them; no ABI, storage-layout or restriction-code change**, and the deployed binding surface (`bindToken`, `unbindToken`, `isTokenBound`, `getTokenBound`, `TokenBound` / `TokenUnbound`) is byte-identical. + - `onlyComplianceManager` is now `onlyTokenBindingManager` — applied to `RuleConditionalTransferLightBase.bindToken` / `bindRuleEngine` / `unbindRuleEngine` and `RuleMintAllowanceBase.bindToken`. The check itself is unchanged: the generic hook delegates to `_onlyComplianceManager()`, which every deployment still overrides with its own role or owner check. + - `_authorizeComplianceBindingChange` is now `_authorizeTokenBindingChange` — renamed in `RuleConditionalTransferLight`, `RuleMintAllowance` and both `Ownable2Step` twins, all still `internal view virtual`. + - `bindToken` needs an explicit `override(ITokenBinding, TokenBindingModule)` in the two bases, because the declaration and the implementation now sit in different upstream contracts. + - The binding errors moved from `ERC3643ComplianceModuleInvariantStorage` to `TokenBindingModuleInvariantStorage` and were renamed `RuleEngine_ERC3643Compliance_*` to `TokenBinding_*`. This library never referenced them — its own `RuleConditionalTransferLight_TokenAlreadyBound` / `RuleMintAllowance_TokenAlreadyBound` are separate errors and keep their selectors — but integrators matching on the upstream names must update. + +- **Removed the last `view` exception to the access-control-hook convention.** `RuleConditionalTransferLightMultiTokenBase` carried a non-`view` `_authorizeComplianceBindingChange` that only delegated to `_onlyComplianceManager()`; rc6's `TokenBindingModule` provides exactly that as the default, so the override is deleted rather than renamed. Every `_authorize*()` / `_only*()` hook in the codebase is now `internal view virtual` with no documented exception. + +- **The ERC-3643 `ICompliance` interface ID now comes from `ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID`** instead of the local flat mock. rc6 derives its interface IDs from the interfaces themselves, so a pre-computed constant finally exists for a surface that previously had none — which is what the house rule asks for whenever one is available. + - The advertised value is unchanged at `0x3144991c`, so ERC-165 discovery behaves identically for the four conditional-transfer deployables. + - `src/mocks/IERC3643ComplianceFull.sol` is kept, demoted to a cross-check: since the value is now *derived* upstream, an interface refactor there could move it silently, and the flat redeclaration is the independent pin that catches it. + +### Testing + +- Added `test/InterfaceId/ComplianceInterfaceId.t.sol` (2 tests) — pins `ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID` against both the flattened `IERC3643ComplianceFull` redeclaration and the literal `0x3144991c`. RuleEngine now derives that constant from its own interface hierarchy, so an upstream refactor such as the rc6 `ITokenBinding` split can move it without a compile error; these assertions are the guard. +- Added `IdentityRegistryExtraCheckHarness` (`src/mocks/harness/IdentityRegistryDelegationHarness.sol`) — a subclass overriding only `_detectTransferRestriction`, mirroring `SanctionsListDelegationHarness` — and `test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol` (8 tests) pinning NM-3. Reverting the source change fails 3 of the 8 with exactly the predicted symptoms. Coverage on `RuleIdentityRegistryBase`: 100% statements, 100% branches. +- Added 5 tests to `test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol` pinning NM-10: a future-dated round yields code 77 from the views and reverts the mint through the write hook; it is still rejected with `maxStalenessSeconds == 0` (the test that pins the design decision); and `updatedAt == block.timestamp` still passes, guarding against over-correcting into `>=`. Reverting the source change fails 4 of the 5. Coverage on `ChainlinkPoRFeedManager`: 100% statements, 100% branches. + +- Extended `test/TransferContext/OverloadParity.t.sol` for NM-6. The suite already existed to assert overload parity but only ever exercised two of the three input shapes (`sender == 0`, `sender != from`), which is why the gap survived. + - Added `_assertSelfSpenderIsDirect` — run for every rule on an allowed and a blocked pair — plus `test_NM6_SelfSpenderIsNotScreenedByTheSpenderWhitelist` and `test_NM6_CmtatFourArgPathKeepsScreeningASelfSpender`, the latter pinning the deliberate asymmetry so it is not "aligned" away later. + - Reverting the fix fails 6 of the suite's 10 tests across 5 rules. + - The suite's header comment, which described the parity as flat, now states the per-interface conventions. Coverage on `RuleNFTAdapter`: 100% statements, 100% branches. + +- Added `test/VirtualHooks/BatchGuardPointerVirtual.t.sol` pinning both halves of the `virtual` fix: the keyword is required (removing it fails the build with *"Trying to override non-virtual function"*, confirmed by mutation) and the override is actually reached through the function pointer — a compile-only check would pass either way while leaving the guard only *looking* extensible. + +- Added 5 tests for NM-17 across `RuleConditionalTransferLightApproveAndTransfer.t.sol` and `RuleConditionalTransferLightMultiToken.t.sol`. `MockERC20WithTransferContext` is a no-op notifier when no rule is set, so leaving `setRule` uncalled gives a token that moves value and tells nobody — the finding's exact shape, with no new mock. Removing the two post-conditions makes both silent-token tests fail and nothing else. Worth recording: the pre-existing 871 tests all passed unchanged when the post-condition landed, because every one of them uses a token that *does* call back — which is how the non-callback path came to have no coverage. -Commit: _see `doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS.md` for the per-finding commit map._ +- The WW-2 threat-model PoC did what its convention promises: named `..._CurrentBehaviour` because it asserted the broken behaviour, it **failed** when NM-18 was fixed. Renamed `test_WW2_NonAddressListChildRuleIsRejectedAtAddRule` and rewritten to assert the rejection, plus two new tests beside it — a nested wrapper is refused, and `test_WW2_GuardCannotRejectAnInvertedPolarityChild_CurrentBehaviour` pins the guard's limit against NM-20. Four assertions added to `test/InterfaceId/AddressListInterfaceId.t.sol` for the sub-interface id and its advertisement. `RuleWhitelistWrapperBase`: 100% statements, branches, functions. + +- Added `test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol` (10 tests, `FOUNDRY_PROFILE=erc3643`) and `test/RuleMaxTotalSupply/RuleMaxTotalSupplyERC3643.t.sol` (10 tests, default profile). The real-token suite covers mints to the ceiling, rejection past it, incremental issuance, burns freeing headroom, a raised cap, and **both compositions with `RuleChainlinkPoRERC3643`** — static cap binding and reserves binding — plus two tests pinning the stock rule's failure on the same token. `test/Version.t.sol` extended to both new deployables. + +- Added `test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol` (10 tests, `FOUNDRY_PROFILE=erc3643`) — drives the **genuine** vendored `lib/ERC-3643/` token (4.2.0-beta1), not a mock, through `RuleChainlinkPoRERC3643`. + - Covers mints up to the reserves, rejection past them, incremental issuance against a shared ceiling, a raised feed answer raising the ceiling, transfers and burns staying open while reserves are zero, and a stale feed halting issuance without trapping holders. + - Two tests pin the **stock** rule's failure on the same real token — a fully backed mint reverting, and the largest single mint halving to `reserves / 2` — so the reason the variant exists stays executable. + - `test/Version.t.sol` extended to both new deployables, keeping it exhaustive. + +- Added `test/CapAccounting/ERC3643CapSeams.t.sol` (7 tests) and `src/mocks/harness/ERC3643CapHarnesses.sol` — a worked ERC-3643 variant of each cap rule plus a tracked-supply rule. The tests reproduce NM-11 on the stock rules under post-update accounting, show the one-line override fixes it, assert the pre-flight view still projects the value, and confirm neither variant ever admits anything above the cap. Coverage after: **100% statements, branches and functions** on `CapAccounting`, `RuleMaxBalanceBase`, `RuleMaxTotalSupplyBase` and `RuleChainlinkPoRBase`. + +- Added `test/RuleConditionalTransferLightMultiToken/MultiTokenGuardReverts.t.sol` (4 tests) closing the last uncovered branches in `src/`: `approveAndTransferIfAllowed` against an unbound token and against a short allowance, `cancelTransferApproval` against an unbound token, and the execution hook against a caller that is not a bound token. Each guard's accept path was already exercised and its `require` never taken — a rule whose purpose is to refuse transfers needs its refusals asserted. Two assert the rejection is total (no approval recorded, no value moved; the approval survives a rejected execution). **Branch coverage across `src/` is now 100% (322/322).** +- Regenerated the coverage report in [`doc/coverage`](./doc/coverage). The committed report was **stale** — it predated the v0.6.0 contracts entirely (no `CapAccounting`, `RuleChainlinkPoRERC3643` or `RuleMaxTotalSupplyERC3643` page) and still carried pages for test files, one of which no longer exists. + - Measured on `src/` only: **98.34% lines (1421/1445), 100% statements (1396/1396), 100% branches (322/322), 95.15% functions (471/495)**. + - The 24 uncovered lines and 24 uncovered functions are the same items — bodyless `internal virtual` declarations (18 `_authorize*` hooks plus `_transferred`, `_transferredFrom`, `_detectTransferRestriction`, `_detectTransferRestrictionFrom` and `_supplyToken`). They have no body to execute, so no test can reach them; they are counted, not missing. + +### Documentation + +- **NM-23/24 declined**, with the reasoning recorded in the feedback file and the `CLAUDE.md` / `AGENTS.md` gotcha rather than left as an open TODO. + - Replacing the typed `try/catch` reads with low-level `staticcall` + `returndata.length` checks would close a real hole — a callee that succeeds while returning short data fails ABI decoding in the *caller's* frame, outside `catch`. + - But the only behaviour that changes is a diagnostic restriction code instead of a revert, on a token that has already stopped honouring its own interface, and the path is fail-closed either way. + - Against that: eight `try` blocks across three files, `abi.decode` as an assertion rather than a compiler check, and eight bespoke length constants in the code whose purpose is robustness. + - The claimed "retires the Cancun precondition" benefit was overstated — `foundry.toml` targets `prague`, so it is already satisfied. + - Revisit only for a pre-Cancun chain or a concrete proxy-upgrade expectation, and then as one shared helper rather than eight hand-rolled sites. + +- **NM-19 closed as *won't do*** — wrapper nesting is deliberately not enabled, and the reason is recorded in `RuleWhitelistWrapper.md`, `RULE_SEMANTICS.md` and the `CLAUDE.md` / `AGENTS.md` gotcha so it is not re-proposed. + - The finding's DoS half was already fixed by NM-18 (a nested wrapper is refused at `addRule` with a named error instead of bricking every transfer); what remained was a feature request that does not earn its cost. + - The wrapper is an OR, and `OR(OR(a,b),OR(c,d))` ≡ `OR(a,b,c,d)` — nesting an OR in an OR is algebraically flat, so it adds **no expressive power**; AND-of-ORs is already available by putting several wrappers in the `RuleEngine`, which returns the first non-zero code. + - It would cost multiplicatively (~8.8k gas per child, and the *rejected* path never early-exits, so a 10 × 10 nest is ~880k gas per transfer against ~90k flat) and open an `A → B → A` cycle class that recurses to out-of-gas, bricking transfers and `isVerified`, with no cheap on-chain defence. + +- **Code-quality review for `v0.6.0`** (`doc/security/audits/tools/v0.6.0/CLAUDE_ANALYSIS.md`) — 11 checks, no vulnerability. Seven checked-and-correct, three fixed, one left deliberately, one open for decision. + +- **Static-analysis reports re-run for `v0.6.0`** — Slither 0.11.5 and Aderyn 0.6.5, same versions as `v0.5.0` so the delta is comparable, with reports and per-finding triage in `doc/security/audits/tools/v0.6.0/`. **Nothing to fix.** + - Slither 44 → 46: one `calls-loop` on the NM-20 polarity guard (bounded, configuration-time only) and one `dead-code` false positive naming the notification seam that `RuleChainlinkPoRERC3643` exists to override — acting on it would break the ERC-3643 variant, so the triage records the three proofs it is live. + - Aderyn 336 → 346 on +204 nSLOC, which is exactly the five new production files appearing once each in `Unspecific Solidity Pragma` and `PUSH0 Opcode`; no new category, and neither `Centralization Risk` nor `Empty Block` moved, since the new variants add no privileged external function. + +- **Per-rule ERC-3643 compatibility matrix** (`RULE_SEMANTICS.md` §6). The scan's most useful signal was that a rule's guarantees depend on what the token tells it and when, and there was no single place saying so per rule. + - ERC-3643 / T-REX **never forwards a spender** (both `transfer` and `transferFrom` call the 3-argument `transferred`) and calls compliance **after** it moves the value. + - That produces three distinct and unequally dangerous failure modes: a rule with an inert *leg* (fail-open for that leg, main screening intact), a cap rule that needs its `…ERC3643` variant (fail-closed, rejects valid mints), and a rule that is wholly inert (`RuleMintAllowance` — silently permissive, and its pre-flight view agrees, so neither the token nor an integrator sees a problem). + - The section also records why `RuleMaxBalance` has no variant. +- **Corrected the ERC-3643 column of `doc/README.md`'s rule table**, which showed a green checkmark for **all 13 rules** — including the two that mis-enforce on that path and the three that enforce nothing. Now ✔ / ⚠ / ✘ with per-rule footnotes. This was an outstanding item recorded in the NM-11 remedy and not previously carried out. +- Pointers to the matrix from both READMEs' ERC-3643 sections and from the `CLAUDE.md` / `AGENTS.md` gotcha. +- Brought the AuditAgent feedback file back into agreement with itself: it still stated "no contract was modified by this triage", "three of the seven improvements have been implemented" and "one item is recommended for action" after seven findings had been fixed. + + +- Added the **Nethermind AuditAgent** (AI automated scan) run for `v0.5.0` — report and per-finding triage in `doc/security/audits/tools/v0.5.0/` (Scan ID `10`, commit `01632da`, 0 High / 13 Medium / 11 Low). + - No false positives, nothing exploitable, no contract change required for the CMTAT path; 17 of the 24 findings restate positions already documented in the source and in `CLAUDE_AUDIT.md`. + - One documentation item is outstanding (**NM-11**): the balance and supply cap rules assume the token notifies *before* moving the value, which a real ERC-3643 / T-REX token does not, so the cap double-counts and over-restricts on that path. + - Seven findings (NM-3, NM-5, NM-6, NM-10, NM-17, NM-18, NM-23/24) carry an `Improvement` section specifying what could be implemented, with the code, its cost and its limit — including the two cases where a complete fix is not reachable at the rule level. **NM-3, NM-6 and NM-10 are implemented in this release** (see *Fixed* above); the other four remain specified but unapplied. + - `AUDIT_OVERVIEW.md`, `README.md` and `doc/README.md` updated with the run, its counts and the AI-tool caveat. + +## v0.5.0 - 2026-08-14 + +Commit: `01632da0ae2cf701323e42644de29cbd951e204c` ### Summary -Four new contracts, one behavioural hardening with a migration note, a reviewed and repaired set of -deployment scripts, the first tests that run against a real ERC-3643 token, and updated dependencies -(CMTAT `v3.3.0-rc3`, RuleEngine `v3.0.0-rc5`, OpenZeppelin `v5.7.0`, solc `0.8.36`). +Four new contracts, one behavioural hardening with a migration note, a reviewed and repaired set of deployment scripts, the first tests that run against a real ERC-3643 token, and updated dependencies (CMTAT `v3.3.0-rc3`, RuleEngine `v3.0.0-rc5`, OpenZeppelin `v5.7.0`, solc `0.8.36`). Every deployable contract reports `version()` → `"0.5.0"`, asserted exhaustively by `test/Version.t.sol`. @@ -67,42 +210,13 @@ Every deployable contract reports `version()` → `"0.5.0"`, asserted exhaustive - **`IdentityRegistryWhitelist`** — a whitelist that fills an ERC-3643 token's *identity registry* slot, so a token can enforce investor eligibility with no ONCHAINID deployment. Not a rule: it implements no `IRule` and must never be added to a `RuleEngine`. - **`RuleMaxBalance`** — caps how many tokens a **single address** may hold, with an operator-managed exemption list. Restriction codes `82`, `83`. **Bypassable by splitting a position across wallets**, so it must be paired with a rule admitting one address per investor. -**Deployment scripts.** All four scripts in `script/` were reviewed and fixed; see [`doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS_SCRIPT.md`](doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS_SCRIPT.md) -for the twelve findings. Three of them (`DeployCMTATWithBlacklist`, `DeployCMTATWithWhitelist`, -`DeployCMTATWithBlacklistAndSanctionsList`) reverted under `forge script` because they read -`address(this)` inside a broadcast, so they could not deploy anything; they now take the deployer as an -explicit parameter. `DeployCMTATWithWhitelist` also deployed with `allowMintBurn = false`, producing a -token that could not be issued at all (mint rejected with code `24` even to a whitelisted investor); it -now allows mint and burn. Shared token metadata moved to the new -`script/base/CMTATDeploymentBase.sol`, which also adds environment-variable configuration (`CMTAT_NAME`, -`CMTAT_SYMBOL`, `SANCTIONS_ORACLE`, `CMTAT_MAX_SUPPLY` and others, all with the previous constants as -defaults) and labelled address logging. The scripts are documented in the new -`doc/technical/guides/DEPLOYMENT_SCRIPTS.md`. CI now runs every script as a local dry run, which is the only -faithful harness: Foundry refuses to combine a prank with a broadcast, so no unit test can exercise -`run()`. - -**Breaking behaviour: `RuleMaxTotalSupply`.** The constructor and `setTokenContract` now reject a -non-contract token and probe that `totalSupply()` is callable, and a token that later reverts yields -the new restriction code `51` instead of breaking the MUST-NOT-revert views. Deployments that passed -a placeholder address now fail at construction — see *Changed* for the migration note. This only -rejects configurations that could never have worked, which is why it is a MINOR rather than MAJOR -bump pre-1.0. - -**ERC-3643 interoperability.** Both integration directions are now covered end to end: a `RuleEngine` -in the token's *compliance* slot enforcing `RuleWhitelist`, and `IdentityRegistryWhitelist` in the -*identity* slot. One suite runs against the genuine vendored `Token.sol` rather than a mock, which -requires a second Foundry profile — **`forge test` alone no longer runs everything**, see *Testing*. - -**Code-quality pass.** A full review of `src/` for duplication, missing events, gas on the storage-read and -loop paths, `virtual` convention drift, and behaviour that is correct but at odds with the library's purpose. -Of twenty-eight findings, **twenty-four were implemented**, two were deliberately declined with the reasoning -recorded (`D-3`, `F-7c`), one is left open (`A-3`), and one needed no change (`A-1`). Three carry -corrections to the review itself: `B-1` and `B-4` overstated their gas saving — `B-1` was wrong for four of its -six sites — and `F-2`'s proposed remedy did not work and was replaced. Every gas figure quoted below was -**measured**, not estimated. Findings, dispositions and the commit for each are in -[`doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS.md`](./doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS.md). -No behavioural change reaches a token holder except where explicitly noted (`RuleSanctionsList` mint/burn -screening, F-1; `transferFrom` delegation, F-2). +**Deployment scripts.** All four scripts in `script/` were reviewed and fixed; see [`doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS_SCRIPT.md`](doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS_SCRIPT.md) for the twelve findings. Three of them (`DeployCMTATWithBlacklist`, `DeployCMTATWithWhitelist`, `DeployCMTATWithBlacklistAndSanctionsList`) reverted under `forge script` because they read `address(this)` inside a broadcast, so they could not deploy anything; they now take the deployer as an explicit parameter. `DeployCMTATWithWhitelist` also deployed with `allowMintBurn = false`, producing a token that could not be issued at all (mint rejected with code `24` even to a whitelisted investor); it now allows mint and burn. Shared token metadata moved to the new `script/base/CMTATDeploymentBase.sol`, which also adds environment-variable configuration (`CMTAT_NAME`, `CMTAT_SYMBOL`, `SANCTIONS_ORACLE`, `CMTAT_MAX_SUPPLY` and others, all with the previous constants as defaults) and labelled address logging. The scripts are documented in the new `doc/technical/guides/DEPLOYMENT_SCRIPTS.md`. CI now runs every script as a local dry run, which is the only faithful harness: Foundry refuses to combine a prank with a broadcast, so no unit test can exercise `run()`. + +**Breaking behaviour: `RuleMaxTotalSupply`.** The constructor and `setTokenContract` now reject a non-contract token and probe that `totalSupply()` is callable, and a token that later reverts yields the new restriction code `51` instead of breaking the MUST-NOT-revert views. Deployments that passed a placeholder address now fail at construction — see *Changed* for the migration note. This only rejects configurations that could never have worked, which is why it is a MINOR rather than MAJOR bump pre-1.0. + +**ERC-3643 interoperability.** Both integration directions are now covered end to end: a `RuleEngine` in the token's *compliance* slot enforcing `RuleWhitelist`, and `IdentityRegistryWhitelist` in the *identity* slot. One suite runs against the genuine vendored `Token.sol` rather than a mock, which requires a second Foundry profile — **`forge test` alone no longer runs everything**, see *Testing*. + +**Code-quality pass.** A full review of `src/` for duplication, missing events, gas on the storage-read and loop paths, `virtual` convention drift, and behaviour that is correct but at odds with the library's purpose. Of twenty-eight findings, **twenty-four were implemented**, two were deliberately declined with the reasoning recorded (`D-3`, `F-7c`), one is left open (`A-3`), and one needed no change (`A-1`). Three carry corrections to the review itself: `B-1` and `B-4` overstated their gas saving — `B-1` was wrong for four of its six sites — and `F-2`'s proposed remedy did not work and was replaced. Every gas figure quoted below was **measured**, not estimated. Findings, dispositions and the commit for each are in [`doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS.md`](./doc/security/audits/tools/v0.5.0/CLAUDE_ANALYSIS.md). No behavioural change reaches a token holder except where explicitly noted (`RuleSanctionsList` mint/burn screening, F-1; `transferFrom` delegation, F-2). ### Added @@ -239,7 +353,7 @@ screening, F-1; `transferFrom` delegation, F-2). ### Documentation -- **NatSpec length ceiling, and a pass to meet it.** `CLAUDE.md` / `AGENTS.md` gain a convention capping a NatSpec block at 20 lines, with the rationale that a comment past that has stopped being a comment and become a document, and that the more claims a block makes the more of them go stale unnoticed. The ceiling was set from the measured distribution over `src/` rather than picked: median 4 lines, 90th percentile 8. Fifteen blocks exceeded it and were rewritten to keep the safety preconditions, footguns and non-obvious design constraints while cutting code restatement, refactor narration and benefit lists — the distribution now runs median 4 / p90 8 / **max 19** across 1003 blocks, with none at or over the ceiling. Comment-only: filtering the whole diff for non-comment lines yields nothing, so no behaviour, ABI or storage change is possible; 820 + 31 tests pass unchanged. The `analyse-code-quality` skill gained the corresponding check. +- **NatSpec length ceiling, and a pass to meet it.** `CLAUDE.md` / `AGENTS.md` gain a convention capping a NatSpec block at 20 lines, with the rationale that a comment past that has stopped being a comment and become a document, and that the more claims a block makes the more of them go stale unnoticed. The ceiling was set from the measured distribution over `src/` rather than picked: median 4 lines, 90th percentile 8. Fifteen blocks exceeded it and were rewritten to keep the safety preconditions, footguns and non-obvious design constraints while cutting code restatement, refactor narration and benefit lists — the distribution now runs median 4 / p90 8 / **max 19** across 1003 blocks, with none at or over the ceiling. Comment-only: filtering the whole diff for non-comment lines yields nothing, so no behaviour, ABI or storage change is possible; 820 + 31 tests pass unchanged. - One block was damaged by the trimming and repaired in a follow-up: `BalanceCapManager`'s header lost the middle of its first `@dev`, leaving `Declares **no constructor**` with no verb or period glued to an unrelated sentence. Found by inspecting the compiled `devdoc` rather than the source. **The line-count check cannot catch this** — it measures length, not whether a trimmed sentence still parses as English — so the other fourteen rewrites were re-read by hand and are clean. - Worth knowing for anyone editing these blocks: **solc merges repeated `@dev` tags into one `details` string with no separator**, so `…the cap.` followed by a new `@dev` renders as `…the cap.{_balanceOf} must never revert`. Within a single tag, lines join with a space and blank lines collapse. Multiple `@dev` is valid and loses no content — 15 blocks in `src/` use it — but paragraph structure survives only in the source, not in `forge doc` or Etherscan output. - New [`doc/technical/contracts/RuleReceiverWhitelist.md`](./doc/technical/contracts/RuleReceiverWhitelist.md), including why receiver-only screening is the conformant choice and how the parity suite tests it. diff --git a/CLAUDE.md b/CLAUDE.md index eb9d690c..bf215f41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,7 @@ Rules that implement a standardized interface must match that standard's semanti |---|---| | `src/rules/validation/` | Read-only rules (view functions, no state changes during transfer) | | `src/rules/operation/` | Read-write rules (modify state on transfer) | -| `src/rules/validation/abstract/core/` | `RuleTransferValidation` (ERC-1404/3643/7551 views), `RuleNFTAdapter` (ERC-7943 + `ITransferContext` overloads), `RuleWhitelistShared`, `TokenSupplyReader` (revert-free `totalSupply()` read, shared by `RuleMaxTotalSupply` and `RuleChainlinkPoR`), `ChainlinkPoRFeedManager` (PoR feed configuration + the revert-free reserve read; **no constructor, no ERC-1404**, so it is reusable and initializer-agnostic), `TotalSupplyCapManager` and `BalanceCapManager` (the same split for `RuleMaxTotalSupply` and `RuleMaxBalance`: state, setters and the revert-free read, with the constructor and the restriction-code mapping left in the rule) | +| `src/rules/validation/abstract/core/` | `RuleTransferValidation` (ERC-1404/3643/7551 views), `RuleNFTAdapter` (ERC-7943 + `ITransferContext` overloads), `RuleWhitelistShared`, `TokenSupplyReader` (revert-free `totalSupply()` read, shared by `RuleMaxTotalSupply` and `RuleChainlinkPoR`), `ChainlinkPoRFeedManager` (PoR feed configuration + the revert-free reserve read; **no constructor, no ERC-1404**, so it is reusable and initializer-agnostic), `TotalSupplyCapManager` and `BalanceCapManager` (the same split for `RuleMaxTotalSupply` and `RuleMaxBalance`: state, setters and the revert-free read, with the constructor and the restriction-code mapping left in the rule), `CapAccounting` (the cap comparison shared by all three cap rules; stateless, so it adds no slot) | | `src/rules/validation/abstract/` | Shared base contracts and invariant storage | | `src/rules/interfaces/` | Shared interfaces (`IAddressList`, `IIdentityRegistry`, `ISanctionsList`, `ITotalSupply`, `ITransferContext`, `IERC2980`, `IERC7943NonFungibleCompliance`, `AggregatorV3Interface`, `IDecimals`) | | `src/registry/` | Contracts filling a token's **identity registry** slot, not its compliance slot (`IdentityRegistryWhitelist`). Not rules: no `IRule`, never added to a RuleEngine | @@ -71,7 +71,9 @@ Rules that implement a standardized interface must match that standard's semanti | `RuleSanctionsList` | Block sanctioned addresses via Chainalysis oracle | | `RuleMaxBalance` / `RuleMaxBalanceOwnable2Step` | Cap how many tokens **one address** may hold, with an operator-managed exemption list. Screens the receiver only; burns exempt; mints capped. Codes 82, 83. **Bypassable by splitting across wallets** — pair with a rule admitting one address per investor (`RuleWhitelist` / `RuleReceiverWhitelist` / `RuleIdentityRegistry`) and an onboarding policy of one address per entity. `maxBalance = 0` forbids holding, it does NOT disable the rule | | `RuleMaxTotalSupply` | Cap minting so total supply never exceeds a maximum | +| `RuleMaxTotalSupplyERC3643` / `…Ownable2Step` | **ERC-3643-only** variant of `RuleMaxTotalSupply`, same re-phasing as `RuleChainlinkPoRERC3643` and the same silent-misconfiguration warning. Compose with `RuleChainlinkPoRERC3643` to add a static ceiling to the reserve-backed one. Codes 50, 51 | | `RuleChainlinkPoR` / `RuleChainlinkPoROwnable2Step` | Cap minting at the reserves reported by a Chainlink Proof of Reserve feed (`AggregatorV3Interface`). The limit equals the reported reserves exactly — no margin parameter (deliberately dropped from Chainlink's `SecureMintPolicy`); compose with `RuleMaxTotalSupply` for a static cap. Mints only; transfers and burns always pass, so a stale or broken feed never traps holders. The read path is guarded (`code.length` check + `try/catch` + saturating arithmetic) so the ERC-1404 views never revert | +| `RuleChainlinkPoRERC3643` / `…Ownable2Step` | **ERC-3643-only** variant of `RuleChainlinkPoR`. Identical reserve logic; re-phases the **write** path because ERC-3643 calls compliance *after* the mint (`_mint` then `created`, forwarded by `RuleEngine` as the 3-arg `transferred(0, to, value)`), so `totalSupply()` already includes it. The read views still project the pending amount — the token calls `canTransfer` *before* `_mint`. Do not use with CMTAT, and do not use the stock rule with ERC-3643: both mistakes are silent. Codes 75–79 | | `RuleIdentityRegistry` | Check ERC-3643 identity registry for participant verification | | `IdentityRegistryWhitelist` | The mirror image of `RuleIdentityRegistry`: **is** an ERC-3643 identity registry, backed by a whitelist, so no ONCHAINID is needed. Keeps **no identity state** — `_identity` and `_country` are accepted for signature compatibility then discarded, and `investorCountry` is a constant 0; do not add identity storage back. Inherits `RuleAddressSetInternal` (the same set machinery as `RuleWhitelist`) rather than deploying or re-implementing a whitelist — **only the internal layer**, because `RuleAddressSet`'s public `addAddress`/`removeAddress` are not `virtual` and so could not maintain the `keyHasPurpose` reverse index; a second write path would produce verified-but-unrecoverable wallets. Installed with `token.setIdentityRegistry()`. Implements **no** ERC-734 surface: `recoveryAddress` needs a real ONCHAINID as `_investorOnchainID`. A `keyHasPurpose` implementation was tried and removed — the agent chooses which contract that call lands on, so it added no security while forcing a hash-to-wallet reverse index and duplicate-tolerant registration. Do not re-add it. The token itself must hold `IDENTITY_REGISTRAR_ROLE`. See `doc/technical/contracts/IdentityRegistryWhitelist.md` | | `RuleSpenderWhitelist` / `RuleSpenderWhitelistOwnable2Step` | Allow `transferFrom` only when spender is whitelisted; direct transfers are always allowed | @@ -90,7 +92,7 @@ Rules that implement a standardized interface must match that standard's semanti - `openzeppelin-contracts` v5.7.0 — `AccessControl`, `Ownable2Step`, `EnumerableSet`, `ERC2771Context` - `openzeppelin-contracts-upgradeable` v5.7.0 - `CMTAT` v3.3.0-rc3 (submodule pin; library supports ≥ v3.0.0) — `IERC1404`, `IERC3643`, `IRuleEngine` interfaces -- `RuleEngine` v3.0.0-rc5 — `IRule`, `RulesManagementModule` +- `RuleEngine` v3.0.0-rc6 — `IRule`, `RulesManagementModule` - `forge-std` — Foundry test utilities Remappings are in `remappings.txt`; aliases used in source: `@openzeppelin/`, `CMTAT/`, `RuleEngine/`. @@ -147,10 +149,10 @@ sees stay context-free. Hardhat compiles only `src/` (Foundry's `src`), so it ne - Access control is implemented via an abstract `_authorize*()` method overridden by concrete subclasses (template method pattern). - AccessControl variants must use `onlyRole(ROLE)` in `_authorize*()` methods (avoid direct `_checkRole`). - **All `_authorize*()` / `_only*()` access-control hooks are `internal view virtual`** — both the abstract declaration and every override. An authorization hook checks and reverts; it must never mutate state, and `view` makes that a compiler-enforced invariant rather than a convention. It is free: these are `internal`, so `view` costs no gas and changes no runtime behaviour. Both OZ check functions (`AccessControl._checkRole`, `Ownable._checkOwner`) are `view`, so every hook can be. - - **One documented exception**: `RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange` cannot be `view`, because it delegates to `_onlyComplianceManager()`, which `lib/RuleEngine` declares non-`view` (Solidity checks mutability against a virtual's *declared* type, not the installed override). If you hit this constraint elsewhere, document why inline — do not silently drop `view` from a hook. + - **There is no longer an exception.** `RuleConditionalTransferLightMultiTokenBase` used to carry a non-`view` `_authorizeComplianceBindingChange` that merely delegated to `_onlyComplianceManager()` — non-`view` because Solidity checks mutability against a virtual's *declared* type, not the installed override. RuleEngine v3.0.0-rc6 supplies that delegation as the default body of `_authorizeTokenBindingChange`, so the override was deleted rather than renamed. If you hit the same constraint again, document why inline — do not silently drop `view` from a hook. - AccessControl variants treat the default admin as having all roles via `hasRole`, but the admin may not appear in role member enumerations unless explicitly granted. -- All rules **and `IdentityRegistryWhitelist`** implement `IERC3643Version` via `VersionModule`; the current version string is `"0.5.0"`. `test/Version.t.sol` asserts it for every deployable contract — keep it exhaustive, a half-covered version test reads as authoritative while missing the mirror it exists to catch. -- **ERC-165 interface IDs**: `type(IFoo).interfaceId` only XORs selectors defined directly on `IFoo` and does NOT include selectors from inherited interfaces. Always use the pre-computed library constants instead: `ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID` (from `CMTAT/library/`), `RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID` (from `CMTAT/library/`), and `RuleInterfaceId.IRULE_INTERFACE_ID` (from `RuleEngine/modules/library/`). If no pre-computed constant exists for an interface, define a flat mock interface that redeclares all functions from the full inheritance tree and use `type(IFooFlattened).interfaceId` to compute the correct value (see `lib/RuleEngine/src/mocks/IRuleInterfaceIdHelper.sol` for the established pattern). +- All rules **and `IdentityRegistryWhitelist`** implement `IERC3643Version` via `VersionModule`; the current version string is `"0.6.0"`. `test/Version.t.sol` asserts it for every deployable contract — keep it exhaustive, a half-covered version test reads as authoritative while missing the mirror it exists to catch. +- **ERC-165 interface IDs**: `type(IFoo).interfaceId` only XORs selectors defined directly on `IFoo` and does NOT include selectors from inherited interfaces. Always use the pre-computed library constants instead: `ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID` (from `CMTAT/library/`), `RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID` (from `CMTAT/library/`), and `RuleInterfaceId.IRULE_INTERFACE_ID` and `ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID` (both from `RuleEngine/modules/library/`). If no pre-computed constant exists for an interface, define a flat mock interface that redeclares all functions from the full inheritance tree and use `type(IFooFlattened).interfaceId` to compute the correct value (see `lib/RuleEngine/src/mocks/IRuleInterfaceIdHelper.sol` for the established pattern). - Batch add/remove operations are non-reverting **for duplicates and missing entries** (those are skipped and counted); single-item operations revert on the same input. The one exception is `address(0)`, which **reverts on every add path, batch included** — see I-12. A batch containing a zero entry therefore fails as a whole rather than being partially applied. - All `internal` functions should be marked `virtual`. - Do not create git commits; provide commit messages only when requested. @@ -166,6 +168,9 @@ sees stay context-free. Hardhat compiles only `src/` (Foundry's `src`), so it ne - `AGENTS.md` and `CLAUDE.md` are identical — always update both together. - **Two READMEs.** `README.md` at the root is a short summary (purpose, architecture, rule list, quick start) and is the GitHub front page; `doc/README.md` is the full reference. Update `doc/README.md` with the latest change, and the root `README.md` only when the summary itself becomes wrong (a new rule, a changed code range, a moved document). Links inside `doc/README.md` are relative to `doc/`. - New rule or features implemented: create/update technical documentation — a per-contract page in `doc/technical/contracts/`, or `doc/technical/guides/` when the material spans several contracts, update `doc/README.md` (and the root summary if the rule table or code ranges change), create/update test (target: 100% of code coverage), update CHANGELOG.md. Code coverage, run `forge coverage --report summary` +- **Do not hard-wrap prose in `CHANGELOG.md` — one line per bullet or paragraph.** Wrapping a bullet across several ~110-character lines renders identically (Markdown collapses a single newline into a space), so the cost is invisible in the published changelog and paid entirely in the repository. Two ways it hurts: **diffs stop being readable** — changing one word reflows every following line in the paragraph, so a one-word correction arrives as a twelve-line diff and a reviewer cannot see what actually changed; and **the file becomes inconsistent with itself**, because the wrap column depends on whoever wrote the entry. Both v0.5.0 and v0.6.0 were written hard-wrapped while every earlier release used one line per bullet, and the mixed result reads as damage. Let the editor soft-wrap. The exception is content where the line break is semantic — fenced code blocks, tables and blockquotes — which must keep their own line structure. +- **Break a long `CHANGELOG.md` entry into a lead sentence plus sub-bullets — do not ship a wall of prose.** Past roughly three sentences a bullet stops being scannable: the defect, its blast radius, the fix, the precedent and the caveat all run together, so a reader looking for any one of them must parse all five. Lead with one sentence naming *what changed*, then one sub-bullet per distinct claim — impact, fix, behaviour-change warning, cost, migration note. This is already the house style in the v0.3.0-v0.5.0 entries; the v0.6.0 entry was written flat and had to be restructured. A useful trigger is length: the median bullet in this file is ~230 characters, so anything past ~700 is almost certainly carrying several claims and should be split. Sub-bullets follow the same no-hard-wrap rule — one line each. +- **Never name an assistant tool, skill or slash command in `CHANGELOG.md`.** The changelog is a record of what changed in *this project*, read by integrators who have no idea what tooling produced it; a line ending "the `analyse-code-quality` skill gained the corresponding check" documents the author's toolbox, not the release. It also rots independently of the repository — a skill can be renamed or deleted, and the changelog entry is then a dangling reference to something the reader could never have seen. Describe the change and its effect on the code; if the tooling matters, it belongs in the audit report under `doc/security/audits/`, not here. **This is about tool identities, not the word "Claude":** `CLAUDE.md`, `AGENTS.md`, `CLAUDE_AUDIT.md` and `CLAUDE_ANALYSIS*.md` are files committed to this repository and are cited freely — a reader can open them. - After each implemented feature or fix, provide a one-line GitHub commit message for all changes since the last commit. ## Security Findings Reference @@ -177,11 +182,13 @@ sees stay context-free. Hardhat compiles only `src/` (Foundry's `src`), so it ne Gotchas worth knowing before you change anything: - **Deployment scripts must take the deployer as a parameter, never read `address(this)`.** Under `forge script` the broadcaster makes the calls, not the script contract, and Foundry rejects `address(this)` inside a broadcast outright. A script that reads it passes every unit test and reverts on the real deployment path, because the tests call `deploy()` directly and never enter a broadcast context. **No unit test can cover this**: Foundry refuses to combine a prank with a broadcast, so `run()` cannot be faithfully exercised from `forge test`. The guard is the `forge script` dry-run step in CI, which runs every `script/*.s.sol`. Shared token metadata and env configuration live in `script/base/CMTATDeploymentBase.sol`. See `CLAUDE_ANALYSIS_SCRIPT.md`. -- `HelperContract` already inherits `RuleConditionalTransferLightInvariantStorage`; inheriting the multi-token variant alongside it is a compile error (`OPERATOR_ROLE`, `CODE_TRANSFER_REQUEST_NOT_APPROVED` clash). +- `HelperContract` already inherits `RuleConditionalTransferLightInvariantStorage`; inheriting the multi-token variant alongside it is a compile error (`OPERATOR_ROLE`, `CODE_TRANSFER_REQUEST_NOT_APPROVED` clash). A multi-token test needing one of those errors must **re-declare it locally** — see `MultiTokenSurface.t.sol` and `RuleConditionalTransferLightMultiToken.t.sol`. +- `approveAndTransferIfAllowed` (both variants) inverts CEI deliberately — the approval is recorded before `safeTransferFrom` so the token's compliance callback can consume it — and therefore **ends with a post-condition** that the approval was consumed, reverting with `..._ApprovalNotConsumed` otherwise (NM-17). Without it a token that never calls back completed the transfer and left a spendable approval behind. The comparison is against the count *before* the helper ran, so an operator's own outstanding approvals survive; and it is read *after* the external call on purpose, so a hostile token can only make it fail, never pass. - `RuleWhitelistWrapperBase._detectTransferRestrictionForTargets` short-circuits once every target address is resolved, so a broken child rule may never be reached for some address pairs. -- `RuleWhitelistWrapper` does not ERC-165-check its child rules (unlike `RuleEngineBase._checkRule`); a non-`IAddressList` child bricks the scan. +- `RuleWhitelistWrapper` ERC-165-checks its child rules in a `_checkRule` override mirroring `RuleEngineBase`, and asks **two** questions (NM-18, NM-20, audit F-5). **Membership**: the child must advertise `IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID` (`0x20e8e17a`, the single `areAddressesListed(address[])` selector) — not the full `IADDRESS_LIST_INTERFACE_ID`, since that one function is all the wrapper calls, so demanding the other seven (four of them writes) would reject a valid read-only child. **Meaning**: the child must also advertise `IADDRESS_LIST_POLARITY_INTERFACE_ID` (`0xdc4efe10`, `isAllowList()`) and answer `true`; `IAddressList` describes membership only, so without this a `RuleBlacklist` passes every interface check and inverts the wrapper. **Absence of the polarity declaration is a refusal, never an assumed allow-list** — the only fail-closed reading. `RuleWhitelist` / `RuleReceiverWhitelist` declare `true`, `RuleBlacklist` declares `false`, and **`RuleSpenderWhitelist` deliberately declines and must not be changed to declare `true`**: its set is permitted *spenders*, not *holders*, so an honest polarity answer would still let the wrapper read spenders as eligible participants. `IAddressListBatchQuery` and `IAddressListPolarity` were split out of `IAddressList`; the flattened selector set is unchanged, so `0x5d10e182` keeps its value, and both sub-interface ids are safe as literals because they inherit nothing. **Wrappers deliberately cannot nest** (NM-19, declined): the wrapper is an OR, and an OR nested in an OR is algebraically flat, so nesting adds no expressive power while costing multiplicatively and opening an `A → B → A` cycle class. Use several wrappers in the `RuleEngine` for AND-of-ORs. Do not "fix" this by implementing `areAddressesListed` on the wrapper. - `RuleChainlinkPoR` reads the feed's `decimals()` **live on every check** and deliberately does NOT cache it. Caching saves ~2,900 gas per mint but lets an aggregator migration that changes decimals mis-scale the reserves by `10 ** delta` with no on-chain signal — in the overstating direction that is unlimited unbacked minting. Both feed calls share the `code.length` guard (Solidity's extcodesize revert on a `try` to a codeless address is uncatchable) and `MAX_FEED_DECIMALS` is re-checked at read time, not just at configuration. Do not "optimise" this back into a cache. - `RuleChainlinkPoR` (and `RuleMaxTotalSupply`) protect **one token per instance** with no on-chain guard: they read `totalSupply()` from the configured `tokenContract`, never from the token that triggered the check, and behind a RuleEngine they cannot learn that identity. One instance added to two RuleEngines evaluates both tokens against the first one's supply and feed — silently over-minting or freezing the second. Chainlink's `SecureMintPolicy` blocks this with `onInstall`/`PolicyAlreadyBound`; adding an equivalent here would mean making a stateless validation rule bindable, which is a library-wide decision. Documented, not fixed. - `ADDRESS_LIST_ADD_ROLE` / `ADDRESS_LIST_REMOVE_ROLE` live in `RuleAddressSetRolesStorage`, inherited by `RuleAddressSet` (the public layer that enforces them) — **not** by `RuleAddressSetInternal`. Do not move them back into `RuleAddressSetInvariantStorage`: a contract reusing only the internal layer (`IdentityRegistryWhitelist`) would then publish two roles it never checks, and an operator granting one would get no privilege and no signal. -- `RuleChainlinkPoR` and `RuleMaxTotalSupply` both guard `tokenContract.totalSupply()` with a code-length check plus `try/catch`, returning a restriction code (78 and 51 respectively) rather than reverting, and both validate the token at configuration (non-zero, has code, `totalSupply()` callable). The shared mechanics — the revert-free read and the configuration probe — live in `TokenSupplyReader`; each rule supplies its own token via the `_supplyToken()` hook (so the base holds **no storage** and cannot reorder any rule's slots) and raises its own named errors. Do not move the `require`s into the base: three distinct configuration failures deliberately keep three distinct per-rule errors. The ERC-1404 views MUST NOT revert — never call `totalSupply()` unguarded on a read path. Neither rule re-checks `code.length` at read time: `try/catch` cannot catch a call to a codeless address (the ABI decoder fails in the caller's frame, outside `catch`'s reach -- **not** `EXTCODESIZE`, which solc >= 0.8.10 skips when return data is expected), but the setters require code and EIP-6780 (Cancun) makes that permanent, so the check would be unreachable. This is a **deployment precondition** (Cancun or later, `foundry.toml` targets `prague`), documented in each rule doc rather than enforced at runtime — do not re-add the guard unless targeting a pre-Cancun chain. `decimals()` stays optional on the token; `totalSupply()` is mandatory. The two rules use *different* constant names for the same idea (`CODE_TOTAL_SUPPLY_UNAVAILABLE` vs `CODE_SUPPLY_ORACLE_UNAVAILABLE`) because `HelperContract` inherits both invariant-storage contracts and identical identifiers would clash. +- `RuleChainlinkPoR` and `RuleMaxTotalSupply` both guard `tokenContract.totalSupply()` with a code-length check plus `try/catch`, returning a restriction code (78 and 51 respectively) rather than reverting, and both validate the token at configuration (non-zero, has code, `totalSupply()` callable). The shared mechanics — the revert-free read and the configuration probe — live in `TokenSupplyReader`; each rule supplies its own token via the `_supplyToken()` hook (so the base holds **no storage** and cannot reorder any rule's slots) and raises its own named errors. Do not move the `require`s into the base: three distinct configuration failures deliberately keep three distinct per-rule errors. The ERC-1404 views MUST NOT revert — never call `totalSupply()` unguarded on a read path. Neither rule re-checks `code.length` at read time: `try/catch` cannot catch a call to a codeless address (the ABI decoder fails in the caller's frame, outside `catch`'s reach -- **not** `EXTCODESIZE`, which solc >= 0.8.10 skips when return data is expected), but the setters require code and EIP-6780 (Cancun) makes that permanent, so the check would be unreachable. This is a **deployment precondition** (Cancun or later, `foundry.toml` targets `prague`), documented in each rule doc rather than enforced at runtime — do not re-add the guard unless targeting a pre-Cancun chain. A low-level `staticcall` + `returndata.length` rewrite would also contain a callee that succeeds while returning short data (NM-23/24) — **considered and declined**: it is eight `try` blocks across three files, trades a known idiom for hand-rolled ABI plumbing where `abi.decode` is an assertion rather than a compiler check, and the only behaviour that improves is a diagnostic code instead of a revert on a token that has already stopped honouring its interface (fail-closed either way). Revisit only for a pre-Cancun chain or a concrete proxy-upgrade expectation, and then as **one shared helper**, not eight call sites. `decimals()` stays optional on the token; `totalSupply()` is mandatory. The two rules use *different* constant names for the same idea (`CODE_TOTAL_SUPPLY_UNAVAILABLE` vs `CODE_SUPPLY_ORACLE_UNAVAILABLE`) because `HelperContract` inherits both invariant-storage contracts and identical identifiers would clash. +- **The three cap rules (`RuleMaxBalance`, `RuleMaxTotalSupply`, `RuleChainlinkPoR`) assume the token notifies BEFORE it moves the value**, so the observation still excludes it. CMTAT does; ERC-3643 / T-REX does **not** (`Token.transfer` runs `_transfer` then `transferred`; `mint` runs `_mint` then `created`), and on such a token the stock rule counts the value twice and halves the effective cap (NM-11). Adapting is one override of `_detectTransferRestrictionOnNotify`, the hook the **write** path enforces through: `return _detectTransferRestriction(from, to, 0)`. **Do not route the read path through it** — a pre-flight view always runs before the movement on either kind of token, so it must always project the value; re-phasing it makes the pre-flight answer disagree with enforcement, which is the mirror image of the bug. The shared comparison lives in `CapAccounting`, which is stateless and deliberately holds no accounting-phase flag for exactly this reason. Second seam: `_currentSupply` / `_balanceOf` are `internal view virtual`, so a rule may serve the figure from its own storage — viable for *supply*, **not** for per-address balances, because `Token.recoveryAddress` notifies compliance on T-REX <= 4.1 (via the public `forcedTransfer`) but not on the vendored 4.2.0-beta1 (direct `_transfer`), so a shadow ledger's correctness would hinge on the token's minor version. See `doc/technical/guides/RULE_SEMANTICS.md` §5. **§6 of the same page is the per-rule ERC-3643 compatibility matrix** — which rules work, which have an inert leg (anything spender-based: no spender is ever forwarded on that path), which need their `…ERC3643` variant, and which are unsupported (`RuleMaxBalance`, `RuleConditionalTransferLightMultiToken`, and `RuleMintAllowance`, the last being silently permissive rather than restrictive). - `RuleChainlinkPoR` accepts `tokenDecimals == 0`. Chainlink's `SecureMintPolicy` requires 1–18, but CMTAT equity tokens report 0 decimals, so the lower bound was dropped. Do not re-add it. diff --git a/README.md b/README.md index f07ee3dd..374342ae 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Each rule enforces one transfer restriction. A rule can be plugged **directly** | Rules | Contracts report `version()` | CMTAT | RuleEngine | OpenZeppelin | | --- | --- | --- | --- | --- | -| **v0.5.0** (current) | `"0.5.0"` | **≥ v3.0.0**, validated against `v3.3.0-rc3` | `v3.0.0-rc5` | `v5.7.0` | +| **v0.6.0** (current) | `"0.6.0"` | **≥ v3.0.0**, validated against `v3.3.0-rc3` | `v3.0.0-rc6` | `v5.7.0` | One rule needs more than the baseline, because it reads the **spender** the token forwards on mint: @@ -20,7 +20,7 @@ One rule needs more than the baseline, because it reads the **spender** the toke | `RuleMintAllowance` | **v3.3** | Debits the minter's quota from the 4-argument `transferred(spender, from, to, value)` / `canTransferFrom`. A token that does not forward the spender cannot drive it. | | Every other rule | v3.0.0 | Uses the 3-argument path only. | -The submodules in `lib/` are pinned to the validated versions (CMTAT `v3.3.0-rc3`, RuleEngine `v3.0.0-rc5`), so +The submodules in `lib/` are pinned to the validated versions (CMTAT `v3.3.0-rc3`, RuleEngine `v3.0.0-rc6`), so a `git submodule update --init --recursive` checkout builds and tests against exactly what this release was verified with. 📖 **[Full documentation →](./doc/README.md)** — the complete reference: every rule in detail, the API, access-control model, restriction codes, deployment guide, and security findings. This page is a summary. @@ -74,8 +74,10 @@ access-control policy, in either an `AccessControl` or an `Ownable2Step` flavour | `RuleERC2980` | ERC-2980 whitelist plus frozenlist | 60–65 | | `RuleIdentityRegistry` | Consults an ERC-3643 identity registry | 55–57 | | `RuleMaxTotalSupply` | Caps total supply on mint | 50, 51 | +| `RuleMaxTotalSupplyERC3643` | Same, for **ERC-3643 tokens** — compliance called *after* the mint | 50, 51 | | `RuleMaxBalance` | Caps how many tokens one address may hold | 82, 83 | | `RuleChainlinkPoR` | Caps minting at Chainlink Proof of Reserve reserves | 75–79 | +| `RuleChainlinkPoRERC3643` | Same, for **ERC-3643 tokens** — compliance called *after* the mint | 75–79 | | `RuleConditionalTransferLight` | Requires operator approval per transfer | 46 | | `RuleMintAllowance` | Per-minter mint quota | 70 | @@ -104,6 +106,11 @@ Use `RuleEngine`, not a bare rule. ERC-3643 drives mint and burn through `create The operation rules do implement `created` / `destroyed`, but they are bound to a single token and are not a compliance contract on their own. +**Not every rule behaves the same on this path.** ERC-3643 never forwards a spender (both `transfer` and `transferFrom` call the +3-argument `transferred`) and calls compliance *after* it moves the value. Some rules are therefore inert, and the two supply-cap +rules need their `…ERC3643` variant. The per-rule matrix is +[`RULE_SEMANTICS.md` §6](./doc/technical/guides/RULE_SEMANTICS.md). + ### Identity verification ERC-3643 decides who may hold a token by asking an **identity registry** one question: @@ -190,15 +197,33 @@ AI-assisted review, each triaged by the project team: | Type | Tool | Latest run | | --- | --- | --- | -| Static analysis | [Slither](https://github.com/crytic/slither) 0.11.5 | v0.5.0 | -| Static analysis | [Aderyn](https://github.com/Cyfrin/aderyn) 0.6.5 | v0.5.0 | +| Static analysis | [Slither](https://github.com/crytic/slither) 0.11.5 | v0.6.0 | +| Static analysis | [Aderyn](https://github.com/Cyfrin/aderyn) 0.6.5 | v0.6.0 | +| AI automated scan | [Nethermind AuditAgent](https://auditagent.nethermind.io/) | v0.5.0 | | AI-assisted review | Claude Code (Anthropic) | v0.5.0 | | AI-assisted review | Claude + custom security-audit skills | v0.4.0 | | AI-assisted review | [Wake Arena](https://getwake.io) (Ackee Blockchain Security) | v0.2.0 | Scope is the production contracts under `src/`; mocks, tests and vendored dependencies are excluded. -Every finding carries a written triage, including the ones dismissed as false positives or by-design. Nothing was outstanding as of `v0.5.0`. +Every finding carries a written triage, including the ones dismissed as false positives or by-design. Nothing is outstanding as of `v0.6.0`: the static analysers report nothing to fix (Slither 46 results, Aderyn 346 Low instances — all false-positive, by-design, environmental or cosmetic), and the AuditAgent scan is fully dispositioned. + +### Nethermind AuditAgent (v0.5.0) + +| Scan | High | Medium | Low | Info | Anything to fix? | +| --- | --- | --- | --- | --- | --- | +| 2026-08-17, commit `01632da` | 0 | 13 | 11 | 0 | **Nothing exploitable** — 7 fixed in `v0.6.0`, 16 accepted as design, 1 declined; nothing left open | + +> Note: This scan was performed by an AI-powered automated tool, not a formal human-led audit. + +No false positives, but 17 of the 24 findings restate design positions already documented in the source and in the previous audit, so the set collapses to about 11 distinct claims. Seven were fixed in `v0.6.0`; the substantive ones: + +- **NM-3** — the identity-registry rule's `transferFrom` path now always delegates to the direct restriction check. A subclass extending only that hook could previously have its check applied to `transfer` but silently skipped on `transferFrom` and `burnFrom`. +- **NM-6** — the ERC-7943 overloads now read an owner-initiated transfer (`spender == from`) as direct, matching the `ITransferContext` entrypoints, so `RuleSpenderWhitelist` no longer blocks an owner moving their own tokens. +- **NM-10** — a Proof-of-Reserve round stamped in the future is rejected as a malformed answer instead of being accepted as fresh. +- **NM-11** — the cap rules assume the token calls the compliance hook *before* moving value. CMTAT does; ERC-3643 / T-REX does not, so the stock rule counted the amount twice and reverted mints that were within the cap. `RuleChainlinkPoRERC3643` and `RuleMaxTotalSupplyERC3643` now ship for that path, verified against the vendored T-REX token; `RuleMaxBalance` stays CMTAT-only by design. + +[Report (PDF)](./doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0.pdf) · [feedback](./doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md). Reports, triage and the threat model live in [`doc/security/audits/`](./doc/security/audits/), indexed by [`AUDIT_OVERVIEW.md`](./doc/security/audits/AUDIT_OVERVIEW.md). diff --git a/doc/FOUNDRY.md b/doc/FOUNDRY.md index 42d28d7b..400dabd3 100644 --- a/doc/FOUNDRY.md +++ b/doc/FOUNDRY.md @@ -30,7 +30,7 @@ Here are the settings for [Hardhat](https://hardhat.org) and [Foundry](https://g - CMTAT [v3.3.0-rc3](https://github.com/CMTA/CMTAT/releases/tag/v3.3.0-rc3) - - RuleEngine [v3.0.0-rc5](https://github.com/CMTA/RuleEngine/releases/tag/v3.0.0-rc5) + - RuleEngine [v3.0.0-rc6](https://github.com/CMTA/RuleEngine/releases/tag/v3.0.0-rc6) ## Toolchain installation diff --git a/doc/README.md b/doc/README.md index 7109a8a5..fe2e4581 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ Each rule can be used **standalone**, directly plugged into a CMTAT token, **or* The **RuleEngine** is an external smart contract that applies transfer restrictions to security tokens such as **CMTAT** or [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643)-compatible tokens through a RuleEngine. Rules are modular validator contracts that the `RuleEngine` or `CMTAT` compatible token can call on every transfer to ensure regulatory and business-logic compliance. -**Current package version:** `v0.5.0` (contracts report `version()` → `"0.5.0"`). Built against CMTAT `v3.3.0-rc3` and RuleEngine `v3.0.0-rc5`; see [Compatibility](#compatibility) for the supported range. +**Current package version:** `v0.6.0` (contracts report `version()` → `"0.6.0"`). Built against CMTAT `v3.3.0-rc3` and RuleEngine `v3.0.0-rc6`; see [Compatibility](#compatibility) for the supported range. > This project has not undergone an audit and is provided as-is without any warranties. @@ -96,7 +96,7 @@ Interface details for each mode are documented under [Architecture](#architectur | Component | Compatible Versions | | ---------------- | ---------------------------------------------------------- | -| **Rules v0.5.0** | CMTAT ≥ v3.0.0 (tested against v3.3.0-rc3)
RuleEngine v3.0.0-rc5 | +| **Rules v0.6.0** | CMTAT ≥ v3.0.0 (tested against v3.3.0-rc3)
RuleEngine v3.0.0-rc6 | Spender-aware paths (e.g. `RuleMintAllowance`) rely on the 4-argument `canTransferFrom` / `transferred(spender, from, to, value)` callbacks, which require a CMTAT / RuleEngine that forwards the spender to the rule; this repository is validated against CMTAT `v3.3.0-rc3`. The other rules only use the 3-argument path and work across the full CMTAT ≥ v3.0.0 range. @@ -119,6 +119,8 @@ However, contrary to the RuleEngine, the whole interface is not implemented: the The alternative to use a Rule with an ERC-3643 token is through the RuleEngine, which implements the whole `ICompliance` interface. +**Not every rule behaves the same on that path.** ERC-3643 / T-REX never forwards a spender — both `transfer` and `transferFrom` call the 3-argument `transferred` — and it calls compliance **after** it has moved the value. Some rules are therefore inert, some lose one screening leg, and the two supply-cap rules need their `…ERC3643` variant. The per-rule matrix is [`RULE_SEMANTICS.md` §6](./technical/guides/RULE_SEMANTICS.md); read it before choosing rules for an ERC-3643 deployment. + The diagram below shows the recommended integration: the ERC-3643 token drives transfer, mint (`created`) and burn (`destroyed`) compliance hooks on the RuleEngine, which forwards them to the rules. A rule used on its own only implements `canTransfer` + `transferred`, so it cannot back an ERC-3643 token directly. ![Using a rule with an ERC-3643 token through a RuleEngine](./img/readme-erc3643-integration.png) @@ -566,19 +568,19 @@ Several rules are available in multiple access-control variants. Use the simples | Rule | Type
[read-only / read-write] | ERC-721 / ERC-1155 | ERC-3643 via RuleEngine / CMTAT path * | Security Audit planned in the roadmap | Description | | ------------------------------------------------------------ | ------------------------------------ | ------------------ | -------- | ------------------------------------- | ------------------------------------------------------------ | -| RuleWhitelist | Read-only | | | | This rule can be used to restrict transfers from/to only addresses inside a whitelist. | -| RuleWhitelistWrapper | Read-Only | | | | This rule can be used to restrict transfers from/to only addresses inside a group of whitelist rules managed by different operators. | -| RuleBlacklist | Read-Only | | | | This rule can be used to forbid transfer from/to addresses in the blacklist | -| RuleSanctionsList | Read-Only | | | | The purpose of this contract is to use the oracle contract from [Chainalysis](https://go.chainalysis.com/chainalysis-oracle-docs.html) to forbid transfer from/to an address included in a sanctions designation (US, EU, or UN). | -| RuleMaxTotalSupply | Read-Only | | | | This rule limits minting so that the total supply never exceeds a configured maximum. | -| RuleChainlinkPoR | Read-Only | | | | This rule limits minting so that the total supply never exceeds the reserves reported by a [Chainlink Proof of Reserve](https://docs.chain.link/data-feeds/proof-of-reserve) data feed. | -| RuleIdentityRegistry | Read-Only | | | | This rule checks the ERC-3643 Identity Registry for transfer participants when configured. | -| RuleSpenderWhitelist | Read-Only | | | | This rule blocks `transferFrom` when the spender is not in the whitelist. Direct transfers are always allowed. | +| RuleWhitelist | Read-only | | a | | This rule can be used to restrict transfers from/to only addresses inside a whitelist. | +| RuleWhitelistWrapper | Read-Only | | a | | This rule can be used to restrict transfers from/to only addresses inside a group of whitelist rules managed by different operators. | +| RuleBlacklist | Read-Only | | b | | This rule can be used to forbid transfer from/to addresses in the blacklist | +| RuleSanctionsList | Read-Only | | b | | The purpose of this contract is to use the oracle contract from [Chainalysis](https://go.chainalysis.com/chainalysis-oracle-docs.html) to forbid transfer from/to an address included in a sanctions designation (US, EU, or UN). | +| RuleMaxTotalSupply | Read-Only | | c | | This rule limits minting so that the total supply never exceeds a configured maximum. | +| RuleChainlinkPoR | Read-Only | | c | | This rule limits minting so that the total supply never exceeds the reserves reported by a [Chainlink Proof of Reserve](https://docs.chain.link/data-feeds/proof-of-reserve) data feed. | +| RuleIdentityRegistry | Read-Only | | a | | This rule checks the ERC-3643 Identity Registry for transfer participants when configured. | +| RuleSpenderWhitelist | Read-Only | | d | | This rule blocks `transferFrom` when the spender is not in the whitelist. Direct transfers are always allowed. | | RuleReceiverWhitelist | Read-Only | | | | This rule screens **only the receiver**, reproducing ERC-3643's eligibility rule (`transferFrom` works the same way; `mint` checks the receiver; `burn` is exempt). The sender and spender are never checked, so a de-listed holder can still exit. | -| RuleERC2980 | Read-Only | | | | ERC-2980 Swiss Compliant rule combining a whitelist (recipient-only) and a frozenlist (blocks sender, recipient, and spender for `transferFrom`). Frozenlist takes priority over whitelist. | +| RuleERC2980 | Read-Only | | b | | ERC-2980 Swiss Compliant rule combining a whitelist (recipient-only) and a frozenlist (blocks sender, recipient, and spender for `transferFrom`). Frozenlist takes priority over whitelist. | | RuleConditionalTransferLight | Read-Write | | | | This rule requires that transfers have to be approved by an operator before being executed. Each approval is consumed once and the same transfer can be approved multiple times. | -| RuleConditionalTransferLightMultiToken | Read-Write | | | | Multi-token variant of ConditionalTransferLight. Approvals are token-scoped with key `(token, from, to, value)` so one token cannot consume another token's approvals. | -| RuleMintAllowance | Read-Write | | Partial | | Enforces a per-minter mint quota managed by an operator; each mint reduces the minter's allowance. Regular transfers and burns are not restricted. | +| RuleConditionalTransferLightMultiToken | Read-Write | | e | | Multi-token variant of ConditionalTransferLight. Approvals are token-scoped with key `(token, from, to, value)` so one token cannot consume another token's approvals. | +| RuleMintAllowance | Read-Write | | f | | Enforces a per-minter mint quota managed by an operator; each mint reduces the minter's allowance. Regular transfers and burns are not restricted. | | [RuleConditionalTransfer](https://github.com/CMTA/RuleConditionalTransfer) (external) | Read-Write | | |
(experimental rule) | Full-featured approval-based transfer rule implementing Swiss law *Vinkulierung*. Supports automatic approval after three months, automatic transfer execution, and a conditional whitelist for address pairs that bypass approval. Maintained in a separate repository. | | [RuleSelf](https://github.com/rya-sge/ruleself) (community) | — | | — |
(community project) | Use [Self](https://self.xyz), a zero-knowledge identity solution to determine which is allowed to interact with the token.
Community-maintained rule project. Not developed or maintained by CMTA. | @@ -586,6 +588,23 @@ All rules implement the CMTAT rule interfaces needed by their supported transfer * A checkmark in this column means the rule enforces compliance for ERC-3643 tokens **through a RuleEngine or the CMTAT transfer path** — it does **not** mean the rule is itself a full ERC-3643 `ICompliance` contract. A standalone rule implements only `canTransfer` + `transferred`, so it cannot back an ERC-3643 token directly; use it through a RuleEngine, which implements the full `ICompliance` interface (see [Integration modes](#integration-modes)). +**A checkmark is not unconditional.** ERC-3643 / T-REX never forwards a spender (both `transfer` and +`transferFrom` call the 3-argument `transferred`) and calls compliance **after** it moves the value. Per-rule +detail, including the three distinct failure modes, is in +[`RULE_SEMANTICS.md` §6](./technical/guides/RULE_SEMANTICS.md). + +a Works, but **`checkSpender` never fires** — no spender reaches the rule on this path. +b Works on `from` / `to`; the **spender leg is inert**, so a listed/sanctioned/frozen *spender* moving +someone else's tokens is not caught. +c ⚠️ **Use the ERC-3643 variant** — `RuleMaxTotalSupplyERC3643` / `RuleChainlinkPoRERC3643`. The stock +rule counts the amount twice and reverts mints that are within the cap. +d ❌ **Inert** — spender screening exists only on the 4-argument path, which this token never uses. +e ❌ **Not supported** — direct-binding only, and ERC-3643 needs a RuleEngine for `created` / +`destroyed`. +f ❌ **Inert, and silently permissive** — `created` carries no minter identity, so no quota is +debited and every mint passes. `RuleMaxBalance` is likewise **not supported**: same double-count as c, +with no variant available. + `RuleMintAllowance` is **Partial**: it does not advertise the full ERC-3643 `ICompliance` interface via ERC-165 because its per-minter mint quota requires the spender-aware mint callback to identify the minter, which the 3-argument ERC-3643 mint callback cannot provide. ### Technical documentation @@ -648,6 +667,7 @@ Validation (read-only) rules have no binding requirement: they hold no per-trans - `RuleMaxTotalSupply`: trusts the configured `tokenContract` to report an **accurate** `totalSupply()`, but not to stay callable — a reverting or codeless token yields code 51 instead of breaking the MUST-NOT-revert views. Configuration rejects a non-contract token and probes that `totalSupply()` is callable. - `RuleMaxTotalSupply`: does not allow clearing the token contract; disable the rule by removing it from the RuleEngine or token. +- **`RuleMaxTotalSupplyERC3643` is the variant for ERC-3643 tokens, and the two are not interchangeable.** ERC-3643 / T-REX calls compliance **after** it has moved the value — `mint` runs `_mint(_to, _amount)` and only then `_tokenCompliance.created(_to, _amount)` — so `totalSupply()` already includes the new tokens when the rule is notified, whereas CMTAT calls the rule first. `RuleEngine` forwards `created` to each rule as the three-argument `transferred(address(0), to, value)`. The variant re-phases the **write** path only; the read views still project the pending amount, because the ERC-3643 token itself calls `canTransfer(address(0), to, amount)` *before* `_mint`. Using the stock rule on an ERC-3643 token counts the amount twice and reverts mints that are within the ceiling (the largest single mint from an empty supply halves to `cap / 2`); using the variant on CMTAT weakens enforcement. Neither mistake reverts at deployment. Compose it with [`RuleChainlinkPoRERC3643`](./technical/contracts/RuleChainlinkPoRERC3643.md) to add a static ceiling to the reserve-backed one. See [`RuleMaxTotalSupplyERC3643`](./technical/contracts/RuleMaxTotalSupplyERC3643.md). #### RuleChainlinkPoR @@ -659,6 +679,7 @@ Validation (read-only) rules have no binding requirement: they hold no per-trans - `RuleChainlinkPoR`: set `maxStalenessSeconds` from the feed's **heartbeat**; `0` disables the staleness check entirely. - `RuleChainlinkPoR`: the mint ceiling equals the reported reserves exactly — there is no margin parameter. Compose with `RuleMaxTotalSupply` if you also want a static cap, or report conservative reserves upstream for a cushion. - `RuleChainlinkPoR`: for a token that does not expose `decimals()`, the configured value is trusted as-is — a wrong value allows over-minting or blocks valid mints. +- **`RuleChainlinkPoRERC3643` is the variant for ERC-3643 tokens, and the two are not interchangeable.** ERC-3643 / T-REX calls compliance **after** it has moved the value — `mint` runs `_mint(_to, _amount)` and only then `_tokenCompliance.created(_to, _amount)` — so `totalSupply()` already includes the new tokens when the rule is notified, whereas CMTAT calls the rule first. `RuleEngine` forwards `created` to each rule as the three-argument `transferred(address(0), to, value)`. The variant re-phases the **write** path only; the read views still project the pending amount, because the ERC-3643 token itself calls `canTransfer(address(0), to, amount)` *before* `_mint`. Using the stock rule on an ERC-3643 token counts the amount twice and reverts fully backed mints (the largest single mint from an empty supply halves to `reserves / 2`); using the variant on CMTAT weakens enforcement. Neither mistake reverts at deployment. See [`RuleChainlinkPoRERC3643`](./technical/contracts/RuleChainlinkPoRERC3643.md). #### RuleWhitelistWrapper @@ -704,7 +725,7 @@ Validation (read-only) rules have no binding requirement: they hold no per-trans - All AccessControl variants: use `onlyRole(ROLE)` in `_authorize*()` and mark internal helpers `virtual`. - All AccessControl variants: use `AccessControlEnumerable`, so role members can be enumerated with `getRoleMember` / `getRoleMemberCount`; default admin is treated as having all roles via `hasRole`, but may not appear in role member lists unless explicitly granted. - All meta-tx-enabled rules: `forwarderIrrevocable` is accepted as-is (including `address(0)`) and is not validated against ERC-165 because some forwarders do not implement it. -- All rules: implement `IERC3643Version` via `VersionModule` and expose `version()` returning `"0.5.0"`. +- All rules: implement `IERC3643Version` via `VersionModule` and expose `version()` returning `"0.6.0"`. ### Read-only (validation) rule @@ -1021,7 +1042,7 @@ This repository is developed and tested with [Foundry](https://book.getfoundry.s | Gas report | `forge test --gas-report` | | Gas snapshot | `forge snapshot` (check only: `forge snapshot --check`) | | Coverage | `forge coverage` | -| Coverage report ([`doc/coverage`](./coverage/)) | `forge coverage --no-match-coverage "(script\|mocks\|test)" --report lcov && genhtml lcov.info --branch-coverage --prefix "$PWD/" --output-dir coverage` | +| Coverage report ([`doc/coverage`](./coverage/)) | `forge coverage --exclude-tests --no-match-coverage '(test\|mocks?\|script)/' --report lcov --report-file doc/coverage/lcov.info && genhtml doc/coverage/lcov.info --branch-coverage --prefix "$PWD/" --output-dir doc/coverage/coverage` | | Invariant suite only | `forge test --match-path "test/invariant/*"` | | Format | `forge fmt` | | Deploy a script | `forge script script/.s.sol --rpc-url --account ` | @@ -1950,6 +1971,37 @@ Proofs live in [`test/ThreatModel/ThreatModelTests.t.sol`](../test/ThreatModel/T See the consolidated [Audit & Security-Analysis Overview](./security/audits/AUDIT_OVERVIEW.md) for the full index and triage. Latest tool outputs (including feedback documents) are in [`doc/security/audits/tools/v0.4.0/`](./security/audits/tools/v0.4.0/). +#### Static analysis (v0.6.0) + +Re-run **2026-08-21** for the v0.6.0 release, at solc `0.8.36`, with the same tool versions as v0.5.0 so the +delta is directly comparable. Full reports and per-finding triage in +[`doc/security/audits/tools/v0.6.0/`](./security/audits/tools/v0.6.0/). + +| Tool | High | Medium | Low | Info | Anything to fix? | +|---|---|---|---|---|---| +| [Slither](https://github.com/crytic/slither) 0.11.5 | 2 | 11 | 18 | 15 | **No** — [feedback](./security/audits/tools/v0.6.0/slither-report-feedback.md) | +| [Aderyn](https://github.com/Cyfrin/aderyn) 0.6.5 | 0 | 0 | 9 categories (346 instances) | 0 | **No** — [feedback](./security/audits/tools/v0.6.0/aderyn-report-feedback.md) | + +**Nothing to fix.** Slither moved 44 → 46 and Aderyn 336 → 346, both fully attributed to code added this +release. Aderyn's +10 is *exactly* the five new production files appearing once each in the two per-file +categories (`Unspecific Solidity Pragma`, `PUSH0 Opcode`); no new category appeared, and neither +`Centralization Risk` nor `Empty Block` moved, because the new ERC-3643 variants add no privileged external +function. Slither's one new `dead-code` hit is a false positive that would be damaging to act on — it names the +notification seam `RuleChainlinkPoRERC3643` exists to override. + +The 2026-08-21 re-run, after the RuleEngine `v3.0.0-rc6` bump, **moved no detector in either tool** — same 46 +Slither results, same 346 Aderyn instances, same categories. Slither's contract count rose 221 → 225 purely +because rc6 added five upstream contracts to the inheritance graph, all under `lib/` and all filtered out of the +results. + +Commands used for `v0.6.0` (mocks excluded): + +```bash +slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks" \ + > doc/security/audits/tools/v0.6.0/slither-report.md +aderyn -x mocks --output doc/security/audits/tools/v0.6.0/aderyn-report.md +``` + #### Static analysis (v0.5.0) Re-run **2026-08-13** for the v0.5.0 release, at solc `0.8.36`. Full reports and per-finding triage in @@ -1978,6 +2030,60 @@ aderyn -x mocks --output doc/security/audits/tools/v0.5.0/aderyn-report.md > The Slither filter must list **`lib`**: this is a Foundry project, so omitting it pulls the whole vendored > dependency tree into scope and inflates the result count roughly four-fold with OpenZeppelin-internal findings. +#### Nethermind AuditAgent (v0.5.0) + +AI automated scan with [**Nethermind AuditAgent**](https://auditagent.nethermind.io/), run **2026-08-17** +(Scan ID `10`, commit `01632da0…951e204c`, 89 contracts / 9 764 LoC). +[Report (PDF)](./security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0.pdf) · +[feedback](./security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md). + +> ⚠️ **Note: this scan was performed by an AI-powered automated tool, not a formal human-led audit.** Nethermind's +> own notice states the report "has been generated entirely by AI… does not constitute a full security audit… must +> be independently verified", and that it does not authorise describing the project as "audited by Nethermind". +> The feedback file is that independent verification — every finding was opened against the cited `file:line`. + +| Tool | High | Medium | Low | Info | Anything to fix? | +|---|---|---|---|---|---| +| [Nethermind AuditAgent (AI)](https://auditagent.nethermind.io/) | 0 | 13 | 11 | 0 | **7 fixed** (NM-3, 6, 10, 11, 17, 18, 20 — `v0.6.0`), 16 accepted as design, 1 declined; nothing left open | + +**Nothing exploitable.** There are no false positives — all 24 findings describe real code — but 17 restate +positions already documented in the source and in [`CLAUDE_AUDIT.md`](./security/audits/tools/v0.4.0/claude-audit/CLAUDE_AUDIT.md) +(F-4, F-5, F-7, and the accepted-risk rows for a reverting oracle or identity registry), and the 24 items collapse +to roughly **11 distinct claims**. Every failure described is fail-closed (over-restriction, a blocked transfer) or +inert (a rule that cannot screen an identity it is never given); none of the 13 Medium ratings survives +verification at Medium. + +**Fixed in `v0.6.0` — NM-3.** `RuleIdentityRegistryBase._detectTransferRestrictionFrom` returned `TRANSFER_OK` +outright when the identity registry was unset or the transfer was a burn, instead of delegating to +`_detectTransferRestriction`. A subclass extending only that hook applied to `transfer` but silently not to +`transferFrom` or `burnFrom` — the same anti-pattern `RuleSanctionsListBase` was restructured to remove. The fix +is behaviour-preserving (both early returns duplicated guards the delegate already performs) and is pinned by +`test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol`. + +**Fixed in `v0.6.0` — NM-10.** `ChainlinkPoRFeedManager` flagged a feed as stale only when +`block.timestamp > updatedAt` — a guard against underflow whose side effect was that any future-dated round +counted as fresh, so a feed frozen on an old reserve answer could keep authorising mints. A future `updatedAt` is +now a malformed answer (code `77`), rejected regardless of `maxStalenessSeconds`. + +**Fixed in `v0.6.0` — NM-6.** `RuleNFTAdapter`'s ERC-7943 spender-aware overloads screened an owner-initiated +transfer as delegated, while the `ITransferContext` entrypoints did not. The interfaces signal a direct transfer +differently — `spender == from` on ERC-7943 and `ctx`, `spender == address(0)` on the CMTAT path — so the adapter +now normalises on a shared `_isDelegated` predicate and the CMTAT path is deliberately untouched. See +[`RULE_SEMANTICS.md` §3](./technical/guides/RULE_SEMANTICS.md) for the convention table. + +**Fixed in `v0.6.0` — NM-11.** `RuleMaxBalance`, `RuleMaxTotalSupply` and `RuleChainlinkPoR` assume the token +calls the compliance hook **before** moving the value — CMTAT does, a real ERC-3643 / T-REX token calls it +**after** — so on that path the stock rule counts the amount twice and reverts mints that are within the cap. +The direction is over-restriction, never over-issuance. `v0.6.0` adds a stateless `CapAccounting` primitive and a +`_detectTransferRestrictionOnNotify` hook on each cap rule (defaulting to today's CMTAT behaviour), then ships +[`RuleChainlinkPoRERC3643`](./technical/contracts/RuleChainlinkPoRERC3643.md) and +[`RuleMaxTotalSupplyERC3643`](./technical/contracts/RuleMaxTotalSupplyERC3643.md) as one-line overrides of it. +Only the write path is re-phased: ERC-3643 calls `canTransfer` *before* `_mint` and `created` *after*, in the same +transaction, so the read views must keep projecting the pending amount. Verified by suites driving the genuine +vendored T-REX token, including four tests pinning the stock rules failing on it. **`RuleMaxBalance` is +deliberately excluded** — a post-update variant would revert an agent's `forcedTransfer` and, on T-REX ≤ 4.1 +where `recoveryAddress` routes through it, brick wallet recovery. + Commands used for `v0.4.0` (mocks excluded): ```bash diff --git a/doc/coverage/coverage/index-sort-b.html b/doc/coverage/coverage/index-sort-b.html index a435ddbb..cb33dda6 100644 --- a/doc/coverage/coverage/index-sort-b.html +++ b/doc/coverage/coverage/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info + LCOV - lcov2.info @@ -28,30 +28,30 @@ Test: - lcov.info + lcov2.info Lines: - 1082 - 1106 - 97.8 % + 1421 + 1445 + 98.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 363 - 387 - 93.8 % + 471 + 495 + 95.2 % Branches: - 220 - 226 - 97.3 % + 322 + 322 + 100.0 % @@ -82,52 +82,52 @@ Branches Sort by branch coverage - src/rules/validation/abstract/RuleERC2980 + src/rules/operation
100.0%
100.0 % - 38 / 38 + 50 / 50 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 22 / 22 + - + 0 / 0 - src/rules/operation/abstract + src/registry -
98.4%98.4%
+
100.0%
- 98.4 % - 240 / 244 - 94.8 % - 73 / 77 - 92.6 % - 50 / 54 + 100.0 % + 1 / 1 + 100.0 % + 1 / 1 + - + 0 / 0 - src/rules/operation + src/rules/validation/deployment -
94.0%94.0%
+
100.0%
- 94.0 % - 47 / 50 - 86.4 % - 19 / 22 + 100.0 % + 210 / 210 + 100.0 % + 119 / 119 - 0 / 0 - src/rules/validation/deployment + src/rules/validation/abstract/RuleERC2980
100.0%
100.0 % - 164 / 164 + 26 / 26 100.0 % - 95 / 95 - - - 0 / 0 + 13 / 13 + 100.0 % + 2 / 2 src/modules @@ -141,41 +141,65 @@ 100.0 % 4 / 4 + + src/registry/abstract + +
93.8%93.8%
+ + 93.8 % + 15 / 16 + 85.7 % + 6 / 7 + 100.0 % + 6 / 6 + src/rules/validation/abstract/RuleAddressSet -
96.4%96.4%
+
96.7%96.7%
- 96.4 % - 54 / 56 - 90.5 % - 19 / 21 + 96.7 % + 58 / 60 + 91.7 % + 22 / 24 100.0 % 12 / 12 + + src/rules/operation/abstract + +
98.4%98.4%
+ + 98.4 % + 246 / 250 + 94.8 % + 73 / 77 + 100.0 % + 58 / 58 + src/rules/validation/abstract/core -
94.0%94.0%
+
96.3%96.3%
- 94.0 % - 79 / 84 - 82.8 % - 24 / 29 + 96.3 % + 257 / 267 + 87.2 % + 68 / 78 100.0 % - 20 / 20 + 81 / 81 src/rules/validation/abstract/base -
97.8%97.8%
+
98.7%98.7%
- 97.8 % - 447 / 457 - 92.1 % - 117 / 127 + 98.7 % + 545 / 552 + 95.3 % + 143 / 150 100.0 % - 124 / 124 + 159 / 159 diff --git a/doc/coverage/coverage/index-sort-f.html b/doc/coverage/coverage/index-sort-f.html index 320d953b..1bd67544 100644 --- a/doc/coverage/coverage/index-sort-f.html +++ b/doc/coverage/coverage/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info + LCOV - lcov2.info @@ -28,30 +28,30 @@ Test: - lcov.info + lcov2.info Lines: - 1082 - 1106 - 97.8 % + 1421 + 1445 + 98.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 363 - 387 - 93.8 % + 471 + 495 + 95.2 % Branches: - 220 - 226 - 97.3 % + 322 + 322 + 100.0 % @@ -82,64 +82,76 @@ Branches Sort by branch coverage - src/rules/validation/abstract/core + src/registry/abstract -
94.0%94.0%
+
93.8%93.8%
- 94.0 % - 79 / 84 - 82.8 % - 24 / 29 + 93.8 % + 15 / 16 + 85.7 % + 6 / 7 100.0 % - 20 / 20 + 6 / 6 - src/rules/operation + src/rules/validation/abstract/core -
94.0%94.0%
+
96.3%96.3%
- 94.0 % - 47 / 50 - 86.4 % - 19 / 22 - - - 0 / 0 + 96.3 % + 257 / 267 + 87.2 % + 68 / 78 + 100.0 % + 81 / 81 src/rules/validation/abstract/RuleAddressSet -
96.4%96.4%
+
96.7%96.7%
- 96.4 % - 54 / 56 - 90.5 % - 19 / 21 + 96.7 % + 58 / 60 + 91.7 % + 22 / 24 100.0 % 12 / 12 + + src/rules/operation/abstract + +
98.4%98.4%
+ + 98.4 % + 246 / 250 + 94.8 % + 73 / 77 + 100.0 % + 58 / 58 + src/rules/validation/abstract/base -
97.8%97.8%
+
98.7%98.7%
- 97.8 % - 447 / 457 - 92.1 % - 117 / 127 + 98.7 % + 545 / 552 + 95.3 % + 143 / 150 100.0 % - 124 / 124 + 159 / 159 - src/rules/operation/abstract + src/registry -
98.4%98.4%
+
100.0%
- 98.4 % - 240 / 244 - 94.8 % - 73 / 77 - 92.6 % - 50 / 54 + 100.0 % + 1 / 1 + 100.0 % + 1 / 1 + - + 0 / 0 src/modules @@ -159,11 +171,23 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 13 / 13 + 100.0 % + 2 / 2 + + + src/rules/operation + +
100.0%
+ + 100.0 % + 50 / 50 + 100.0 % + 22 / 22 + - + 0 / 0 src/rules/validation/deployment @@ -171,9 +195,9 @@
100.0%
100.0 % - 164 / 164 + 210 / 210 100.0 % - 95 / 95 + 119 / 119 - 0 / 0 diff --git a/doc/coverage/coverage/index-sort-l.html b/doc/coverage/coverage/index-sort-l.html index 198f184e..43945082 100644 --- a/doc/coverage/coverage/index-sort-l.html +++ b/doc/coverage/coverage/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info + LCOV - lcov2.info @@ -28,30 +28,30 @@ Test: - lcov.info + lcov2.info Lines: - 1082 - 1106 - 97.8 % + 1421 + 1445 + 98.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 363 - 387 - 93.8 % + 471 + 495 + 95.2 % Branches: - 220 - 226 - 97.3 % + 322 + 322 + 100.0 % @@ -82,64 +82,76 @@ Branches Sort by branch coverage - src/rules/operation + src/registry/abstract -
94.0%94.0%
+
93.8%93.8%
- 94.0 % - 47 / 50 - 86.4 % - 19 / 22 - - - 0 / 0 + 93.8 % + 15 / 16 + 85.7 % + 6 / 7 + 100.0 % + 6 / 6 src/rules/validation/abstract/core -
94.0%94.0%
+
96.3%96.3%
- 94.0 % - 79 / 84 - 82.8 % - 24 / 29 + 96.3 % + 257 / 267 + 87.2 % + 68 / 78 100.0 % - 20 / 20 + 81 / 81 src/rules/validation/abstract/RuleAddressSet -
96.4%96.4%
+
96.7%96.7%
- 96.4 % - 54 / 56 - 90.5 % - 19 / 21 + 96.7 % + 58 / 60 + 91.7 % + 22 / 24 100.0 % 12 / 12 + + src/rules/operation/abstract + +
98.4%98.4%
+ + 98.4 % + 246 / 250 + 94.8 % + 73 / 77 + 100.0 % + 58 / 58 + src/rules/validation/abstract/base -
97.8%97.8%
+
98.7%98.7%
- 97.8 % - 447 / 457 - 92.1 % - 117 / 127 + 98.7 % + 545 / 552 + 95.3 % + 143 / 150 100.0 % - 124 / 124 + 159 / 159 - src/rules/operation/abstract + src/registry -
98.4%98.4%
+
100.0%
- 98.4 % - 240 / 244 - 94.8 % - 73 / 77 - 92.6 % - 50 / 54 + 100.0 % + 1 / 1 + 100.0 % + 1 / 1 + - + 0 / 0 src/modules @@ -159,11 +171,23 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 13 / 13 + 100.0 % + 2 / 2 + + + src/rules/operation + +
100.0%
+ + 100.0 % + 50 / 50 + 100.0 % + 22 / 22 + - + 0 / 0 src/rules/validation/deployment @@ -171,9 +195,9 @@
100.0%
100.0 % - 164 / 164 + 210 / 210 100.0 % - 95 / 95 + 119 / 119 - 0 / 0 diff --git a/doc/coverage/coverage/index.html b/doc/coverage/coverage/index.html index 0065a385..bad1b668 100644 --- a/doc/coverage/coverage/index.html +++ b/doc/coverage/coverage/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info + LCOV - lcov2.info @@ -28,30 +28,30 @@ Test: - lcov.info + lcov2.info Lines: - 1082 - 1106 - 97.8 % + 1421 + 1445 + 98.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 363 - 387 - 93.8 % + 471 + 495 + 95.2 % Branches: - 220 - 226 - 97.3 % + 322 + 322 + 100.0 % @@ -93,15 +93,39 @@ 100.0 % 4 / 4 + + src/registry + +
100.0%
+ + 100.0 % + 1 / 1 + 100.0 % + 1 / 1 + - + 0 / 0 + + + src/registry/abstract + +
93.8%93.8%
+ + 93.8 % + 15 / 16 + 85.7 % + 6 / 7 + 100.0 % + 6 / 6 + src/rules/operation -
94.0%94.0%
+
100.0%
- 94.0 % - 47 / 50 - 86.4 % - 19 / 22 + 100.0 % + 50 / 50 + 100.0 % + 22 / 22 - 0 / 0 @@ -111,21 +135,21 @@
98.4%98.4%
98.4 % - 240 / 244 + 246 / 250 94.8 % 73 / 77 - 92.6 % - 50 / 54 + 100.0 % + 58 / 58 src/rules/validation/abstract/RuleAddressSet -
96.4%96.4%
+
96.7%96.7%
- 96.4 % - 54 / 56 - 90.5 % - 19 / 21 + 96.7 % + 58 / 60 + 91.7 % + 22 / 24 100.0 % 12 / 12 @@ -135,35 +159,35 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 13 / 13 + 100.0 % + 2 / 2 src/rules/validation/abstract/base -
97.8%97.8%
+
98.7%98.7%
- 97.8 % - 447 / 457 - 92.1 % - 117 / 127 + 98.7 % + 545 / 552 + 95.3 % + 143 / 150 100.0 % - 124 / 124 + 159 / 159 src/rules/validation/abstract/core -
94.0%94.0%
+
96.3%96.3%
- 94.0 % - 79 / 84 - 82.8 % - 24 / 29 + 96.3 % + 257 / 267 + 87.2 % + 68 / 78 100.0 % - 20 / 20 + 81 / 81 src/rules/validation/deployment @@ -171,9 +195,9 @@
100.0%
100.0 % - 164 / 164 + 210 / 210 100.0 % - 95 / 95 + 119 / 119 - 0 / 0 diff --git a/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func-sort-c.html index cdddaa4b..f6890388 100644 --- a/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/AccessControlModuleStandalone.sol - functions + LCOV - lcov2.info - src/modules/AccessControlModuleStandalone.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 7 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -70,11 +70,11 @@ AccessControlModuleStandalone.constructor - 1622 + 2595 AccessControlModuleStandalone.hasRole - 1746 + 2731
diff --git a/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func.html b/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func.html index 33940064..6ecbc749 100644 --- a/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func.html +++ b/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/AccessControlModuleStandalone.sol - functions + LCOV - lcov2.info - src/modules/AccessControlModuleStandalone.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 7 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -70,11 +70,11 @@ AccessControlModuleStandalone.constructor - 1622 + 2595 AccessControlModuleStandalone.hasRole - 1746 + 2731
diff --git a/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.gcov.html b/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.gcov.html index fda071a9..f564c285 100644 --- a/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.gcov.html +++ b/doc/coverage/coverage/src/modules/AccessControlModuleStandalone.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/AccessControlModuleStandalone.sol + LCOV - lcov2.info - src/modules/AccessControlModuleStandalone.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 7 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -98,12 +98,12 @@ 27 : : * 28 : : * @param admin The address that will receive the `DEFAULT_ADMIN_ROLE`. 29 : : */ - 30 : 1622 : constructor(address admin) { - 31 [ + + ]: 1622 : require(admin != address(0), AccessControlModuleStandalone_AddressZeroNotAllowed()); + 30 : 2595 : constructor(address admin) { + 31 [ + + ]: 2595 : require(admin != address(0), AccessControlModuleStandalone_AddressZeroNotAllowed()); 32 : : // we don't check the return value 33 : : // _grantRole attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. 34 : : // return false only if the admin has already the role - 35 : 1615 : _grantRole(DEFAULT_ADMIN_ROLE, admin); + 35 : 2588 : _grantRole(DEFAULT_ADMIN_ROLE, admin); 36 : : } 37 : : 38 : : /*////////////////////////////////////////////////////////////// @@ -114,7 +114,7 @@ 43 : : * @inheritdoc IAccessControl 44 : : * @dev The default admin is treated as holding every role. 45 : : */ - 46 : 1746 : function hasRole(bytes32 role, address account) + 46 : 2731 : function hasRole(bytes32 role, address account) 47 : : public 48 : : view 49 : : virtual @@ -123,10 +123,10 @@ 52 : : { 53 : : // Dev note: default admin is treated as having all roles but may not appear in enumerable role members. 54 : : // The Default Admin has all roles - 55 [ + + ]: 21064 : if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) { - 56 : 4032 : return true; + 55 [ + + ]: 23013 : if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) { + 56 : 4821 : return true; 57 : : } else { - 58 : 17032 : return AccessControl.hasRole(role, account); + 58 : 18192 : return AccessControl.hasRole(role, account); 59 : : } 60 : : } 61 : : } diff --git a/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func-sort-c.html index 8bcb20a0..c09fe9fb 100644 --- a/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/Ownable2StepERC165Module.sol - functions + LCOV - lcov2.info - src/modules/Ownable2StepERC165Module.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 1 @@ -70,7 +70,7 @@ Ownable2StepERC165Module.supportsInterface - 68 + 81
diff --git a/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func.html b/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func.html index c697bd8f..7b845c63 100644 --- a/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func.html +++ b/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/Ownable2StepERC165Module.sol - functions + LCOV - lcov2.info - src/modules/Ownable2StepERC165Module.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 1 @@ -70,7 +70,7 @@ Ownable2StepERC165Module.supportsInterface - 68 + 81
diff --git a/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.gcov.html b/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.gcov.html index a3d9ebbd..bc4ac090 100644 --- a/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.gcov.html +++ b/doc/coverage/coverage/src/modules/Ownable2StepERC165Module.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/Ownable2StepERC165Module.sol + LCOV - lcov2.info - src/modules/Ownable2StepERC165Module.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 1 @@ -85,10 +85,10 @@ 14 : : * @inheritdoc ERC165 15 : : * @dev Also advertises support for the IERC173 and IOwnable2Step interfaces. 16 : : */ - 17 : 68 : function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - 18 : 68 : return interfaceId == OwnableInterfaceId.IERC173_INTERFACE_ID - 19 : 57 : || interfaceId == Ownable2StepInterfaceId.IOWNABLE2STEP_INTERFACE_ID - 20 : 46 : || ERC165.supportsInterface(interfaceId); + 17 : 81 : function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + 18 : 81 : return interfaceId == OwnableInterfaceId.IERC173_INTERFACE_ID + 19 : 69 : || interfaceId == Ownable2StepInterfaceId.IOWNABLE2STEP_INTERFACE_ID + 20 : 57 : || ERC165.supportsInterface(interfaceId); 21 : : } 22 : : } 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 2f6463dd..8e2d4639 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 @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/VersionModule.sol - functions + LCOV - lcov2.info - src/modules/VersionModule.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 2 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 1 @@ -70,7 +70,7 @@ VersionModule.version - 7 + 20
diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.func.html b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html index f24e5ae7..c4f00b89 100644 --- a/doc/coverage/coverage/src/modules/VersionModule.sol.func.html +++ b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/VersionModule.sol - functions + LCOV - lcov2.info - src/modules/VersionModule.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 2 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 1 @@ -70,7 +70,7 @@ VersionModule.version - 7 + 20
diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html index c65187e0..0e4fc46e 100644 --- a/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html +++ b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules/VersionModule.sol + LCOV - lcov2.info - src/modules/VersionModule.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 2 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 1 @@ -82,7 +82,7 @@ 11 : : /** 12 : : * @notice The contract version string returned by {version}. 13 : : */ - 14 : : string private constant VERSION = "0.4.0"; + 14 : : string private constant VERSION = "0.6.0"; 15 : : 16 : : /*////////////////////////////////////////////////////////////// 17 : : PUBLIC FUNCTIONS @@ -91,8 +91,8 @@ 20 : : /** 21 : : * @inheritdoc IERC3643Version 22 : : */ - 23 : 7 : function version() public view virtual override returns (string memory version_) { - 24 : 7 : return VERSION; + 23 : 20 : function version() public pure virtual override returns (string memory version_) { + 24 : 20 : return VERSION; 25 : : } 26 : : } diff --git a/doc/coverage/coverage/src/modules/index-sort-b.html b/doc/coverage/coverage/src/modules/index-sort-b.html index d13f2740..ca1e6e31 100644 --- a/doc/coverage/coverage/src/modules/index-sort-b.html +++ b/doc/coverage/coverage/src/modules/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules + LCOV - lcov2.info - src/modules @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/modules/index-sort-f.html b/doc/coverage/coverage/src/modules/index-sort-f.html index 820d302c..393506d3 100644 --- a/doc/coverage/coverage/src/modules/index-sort-f.html +++ b/doc/coverage/coverage/src/modules/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules + LCOV - lcov2.info - src/modules @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/modules/index-sort-l.html b/doc/coverage/coverage/src/modules/index-sort-l.html index 4f4dca07..512c57de 100644 --- a/doc/coverage/coverage/src/modules/index-sort-l.html +++ b/doc/coverage/coverage/src/modules/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules + LCOV - lcov2.info - src/modules @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/modules/index.html b/doc/coverage/coverage/src/modules/index.html index 17071405..0281c799 100644 --- a/doc/coverage/coverage/src/modules/index.html +++ b/doc/coverage/coverage/src/modules/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/modules + LCOV - lcov2.info - src/modules @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.func-sort-c.html b/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.func-sort-c.html new file mode 100644 index 00000000..d904e8c3 --- /dev/null +++ b/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.func-sort-c.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/registry/IdentityRegistryWhitelist.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry - IdentityRegistryWhitelist.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
IdentityRegistryWhitelist._authorizeIdentityRegistrar107
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.func.html b/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.func.html new file mode 100644 index 00000000..97c46d36 --- /dev/null +++ b/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.func.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/registry/IdentityRegistryWhitelist.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry - IdentityRegistryWhitelist.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
IdentityRegistryWhitelist._authorizeIdentityRegistrar107
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.gcov.html b/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.gcov.html new file mode 100644 index 00000000..d4c1dfe3 --- /dev/null +++ b/doc/coverage/coverage/src/registry/IdentityRegistryWhitelist.sol.gcov.html @@ -0,0 +1,120 @@ + + + + + + + LCOV - lcov2.info - src/registry/IdentityRegistryWhitelist.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry - IdentityRegistryWhitelist.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {AccessControlModuleStandalone} from "../modules/AccessControlModuleStandalone.sol";
+       5                 :            : import {IdentityRegistryWhitelistBase} from "./abstract/IdentityRegistryWhitelistBase.sol";
+       6                 :            : 
+       7                 :            : /**
+       8                 :            :  * @title IdentityRegistryWhitelist
+       9                 :            :  * @notice A whitelist that plugs directly into an ERC-3643 token as its identity registry.
+      10                 :            :  * @dev Install with `token.setIdentityRegistry(address(this))`. Grant {IDENTITY_REGISTRAR_ROLE} to
+      11                 :            :  * the operator that maintains the whitelist **and to the token itself**, otherwise
+      12                 :            :  * `recoveryAddress` reverts -- see the technical doc.
+      13                 :            :  *
+      14                 :            :  * This is not a compliance rule: it implements no `IRule` surface and must not be added to a
+      15                 :            :  * `RuleEngine`.
+      16                 :            :  */
+      17                 :            : contract IdentityRegistryWhitelist is AccessControlModuleStandalone, IdentityRegistryWhitelistBase {
+      18                 :            :     /*//////////////////////////////////////////////////////////////
+      19                 :            :                              CONSTRUCTOR
+      20                 :            :     //////////////////////////////////////////////////////////////*/
+      21                 :            : 
+      22                 :            :     /**
+      23                 :            :      * @param admin Address that receives the default admin role.
+      24                 :            :      */
+      25                 :            :     constructor(address admin) AccessControlModuleStandalone(admin) {}
+      26                 :            : 
+      27                 :            :     /*//////////////////////////////////////////////////////////////
+      28                 :            :                             ACCESS CONTROL
+      29                 :            :     //////////////////////////////////////////////////////////////*/
+      30                 :            : 
+      31                 :            :     /**
+      32                 :            :      * @notice Restricts identity registration and deletion to IDENTITY_REGISTRAR_ROLE.
+      33                 :            :      */
+      34                 :        107 :     function _authorizeIdentityRegistrar() internal view virtual override onlyRole(IDENTITY_REGISTRAR_ROLE) {}
+      35                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.func-sort-c.html b/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.func-sort-c.html new file mode 100644 index 00000000..bcdd05ef --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.func-sort-c.html @@ -0,0 +1,109 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract/IdentityRegistryWhitelistBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstract - IdentityRegistryWhitelistBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
IdentityRegistryWhitelistBase._authorizeIdentityRegistrar0
IdentityRegistryWhitelistBase.registeredIdentityCount3
IdentityRegistryWhitelistBase.investorCountry7
IdentityRegistryWhitelistBase.deleteIdentity11
IdentityRegistryWhitelistBase.isVerified84
IdentityRegistryWhitelistBase.onlyIdentityRegistrar96
IdentityRegistryWhitelistBase.registerIdentity96
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.func.html b/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.func.html new file mode 100644 index 00000000..e6a6bb12 --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.func.html @@ -0,0 +1,109 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract/IdentityRegistryWhitelistBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstract - IdentityRegistryWhitelistBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
IdentityRegistryWhitelistBase._authorizeIdentityRegistrar0
IdentityRegistryWhitelistBase.deleteIdentity11
IdentityRegistryWhitelistBase.investorCountry7
IdentityRegistryWhitelistBase.isVerified84
IdentityRegistryWhitelistBase.onlyIdentityRegistrar96
IdentityRegistryWhitelistBase.registerIdentity96
IdentityRegistryWhitelistBase.registeredIdentityCount3
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.gcov.html b/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.gcov.html new file mode 100644 index 00000000..8b214b05 --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/IdentityRegistryWhitelistBase.sol.gcov.html @@ -0,0 +1,213 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract/IdentityRegistryWhitelistBase.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstract - IdentityRegistryWhitelistBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleAddressSetInternal} from "../../rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol";
+       5                 :            : import {IdentityRegistryWhitelistInvariantStorage} from "./IdentityRegistryWhitelistInvariantStorage.sol";
+       6                 :            : import {VersionModule} from "../../modules/VersionModule.sol";
+       7                 :            : import {IIdentityRegistryERC3643} from "../interfaces/IIdentityRegistryERC3643.sol";
+       8                 :            : 
+       9                 :            : /**
+      10                 :            :  * @title IdentityRegistryWhitelistBase
+      11                 :            :  * @notice A whitelist that presents itself to an ERC-3643 token as an identity registry.
+      12                 :            :  * @dev Installed with `token.setIdentityRegistry(address(this))`. **Not** a compliance rule: no
+      13                 :            :  * `IRule` surface, and it must never be added to a `RuleEngine`.
+      14                 :            :  *
+      15                 :            :  * @dev **No identity data is stored** -- no ONCHAINID, no country, no claims. `registerIdentity`'s
+      16                 :            :  * `_identity` and `_country` are accepted so the ERC-3643 signature matches, then discarded, and
+      17                 :            :  * {investorCountry} always returns 0. Verification means one thing here: is this wallet listed.
+      18                 :            :  * `Token.sol` reads `investorCountry` only in `recoveryAddress`, to pass it straight back, so the
+      19                 :            :  * token is unaffected; a *custom* compliance module reading it would see every investor as country 0.
+      20                 :            :  *
+      21                 :            :  * @dev **No ERC-734 surface.** `keyHasPurpose` was implemented once and removed: `recoveryAddress`
+      22                 :            :  * calls it on the address the agent supplies, never cross-checking it against the registry, so it
+      23                 :            :  * gated nothing while costing a reverse index. Supply a real ONCHAINID as `_investorOnchainID`.
+      24                 :            :  *
+      25                 :            :  * @dev The address set is inherited from {RuleAddressSetInternal}, so the registry *is* the list.
+      26                 :            :  * Only the internal layer, so there is one write API (the ERC-3643 one), not two overlapping ones.
+      27                 :            :  */
+      28                 :            : abstract contract IdentityRegistryWhitelistBase is
+      29                 :            :     RuleAddressSetInternal,
+      30                 :            :     VersionModule,
+      31                 :            :     IIdentityRegistryERC3643,
+      32                 :            :     IdentityRegistryWhitelistInvariantStorage
+      33                 :            : {
+      34                 :            :     /*//////////////////////////////////////////////////////////////
+      35                 :            :                         EXTERNAL FUNCTIONS
+      36                 :            :     //////////////////////////////////////////////////////////////*/
+      37                 :            : 
+      38                 :            :     /**
+      39                 :            :      * @inheritdoc IIdentityRegistryERC3643
+      40                 :            :      * @dev Adds the wallet to the whitelist. `_identity` is echoed in {IdentityRegistered} for
+      41                 :            :      * off-chain traceability and `_country` is ignored entirely -- neither is stored.
+      42                 :            :      *
+      43                 :            :      * Reverts on the zero address and on an already-registered wallet, matching ERC-3643's
+      44                 :            :      * reference registry (which reverts with "address stored already").
+      45                 :            :      */
+      46                 :         96 :     function registerIdentity(
+      47                 :            :         address _userAddress,
+      48                 :            :         address _identity,
+      49                 :            :         uint16 /* _country */
+      50                 :            :     )
+      51                 :            :         external
+      52                 :            :         virtual
+      53                 :            :         override
+      54                 :            :         onlyIdentityRegistrar
+      55                 :            :     {
+      56                 :            :         // Same guards, same errors as the whitelist rules: the zero address is the mint/burn
+      57                 :            :         // sentinel and must never be listed, or `isVerified(address(0))` would return true.
+      58         [ +  + ]:         93 :         require(_userAddress != address(0), RuleAddressSet_ZeroAddressNotAllowed());
+      59         [ +  + ]:         92 :         require(_addAddress(_userAddress), RuleAddressSet_AddressAlreadyListed());
+      60                 :         91 :         emit IdentityRegistered(_userAddress, _identity);
+      61                 :            :     }
+      62                 :            : 
+      63                 :            :     /**
+      64                 :            :      * @inheritdoc IIdentityRegistryERC3643
+      65                 :            :      * @dev Reverts if the wallet is not registered.
+      66                 :            :      */
+      67                 :         11 :     function deleteIdentity(address _userAddress) external virtual override onlyIdentityRegistrar {
+      68         [ +  + ]:         10 :         require(_removeAddress(_userAddress), RuleAddressSet_AddressNotFound());
+      69                 :          9 :         emit IdentityRemoved(_userAddress);
+      70                 :            :     }
+      71                 :            : 
+      72                 :            :     /**
+      73                 :            :      * @notice Returns how many wallets are registered.
+      74                 :            :      * @dev There is deliberately no full enumeration getter, matching `RuleWhitelist` and
+      75                 :            :      * `RuleBlacklist`, which expose a count but not the member list.
+      76                 :            :      * @return The number of registered wallets.
+      77                 :            :      */
+      78                 :          3 :     function registeredIdentityCount() external view virtual returns (uint256) {
+      79                 :          3 :         return _listedAddressCount();
+      80                 :            :     }
+      81                 :            : 
+      82                 :            :     /*//////////////////////////////////////////////////////////////
+      83                 :            :                         PUBLIC FUNCTIONS
+      84                 :            :     //////////////////////////////////////////////////////////////*/
+      85                 :            : 
+      86                 :            :     /**
+      87                 :            :      * @inheritdoc IIdentityRegistryERC3643
+      88                 :            :      * @dev `address(0)` is never registered, so it is never verified -- ERC-3643 defines
+      89                 :            :      * `isVerified` as "is this wallet a valid investor", and the zero address is not a wallet.
+      90                 :            :      * Mint and burn permission is the token's business, not the registry's.
+      91                 :            :      */
+      92                 :         84 :     function isVerified(address _userAddress) public view virtual override returns (bool) {
+      93                 :         84 :         return _isAddressListed(_userAddress);
+      94                 :            :     }
+      95                 :            : 
+      96                 :            :     /**
+      97                 :            :      * @inheritdoc IIdentityRegistryERC3643
+      98                 :            :      * @dev Always returns 0: this registry keeps no identity data, only a whitelist. The function
+      99                 :            :      * exists because `recoveryAddress` calls it -- omitting it would make every recovery revert --
+     100                 :            :      * and the 0 it returns is handed straight back to {registerIdentity}, which ignores it.
+     101                 :            :      */
+     102                 :          7 :     function investorCountry(
+     103                 :            :         address /* _userAddress */
+     104                 :            :     )
+     105                 :            :         public
+     106                 :            :         view
+     107                 :            :         virtual
+     108                 :            :         override
+     109                 :            :         returns (uint16)
+     110                 :            :     {
+     111                 :          7 :         return 0;
+     112                 :            :     }
+     113                 :            : 
+     114                 :            :     /*//////////////////////////////////////////////////////////////
+     115                 :            :                             ACCESS CONTROL
+     116                 :            :     //////////////////////////////////////////////////////////////*/
+     117                 :            : 
+     118                 :         96 :     modifier onlyIdentityRegistrar() {
+     119                 :         96 :         _authorizeIdentityRegistrar();
+     120                 :            :         _;
+     121                 :            :     }
+     122                 :            : 
+     123                 :            :     /**
+     124                 :            :      * @notice Authorizes the caller to register and delete identities; reverts otherwise.
+     125                 :            :      * @dev Implemented by concrete subclasses with the desired access-control policy.
+     126                 :            :      */
+     127                 :          0 :     function _authorizeIdentityRegistrar() internal view virtual;
+     128                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/index-sort-b.html b/doc/coverage/coverage/src/registry/abstract/index-sort-b.html new file mode 100644 index 00000000..5ee97771 --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/index-sort-b.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstractHitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelistBase.sol +
93.8%93.8%
+
93.8 %15 / 1685.7 %6 / 7100.0 %6 / 6
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/index-sort-f.html b/doc/coverage/coverage/src/registry/abstract/index-sort-f.html new file mode 100644 index 00000000..8c1966ae --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/index-sort-f.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstractHitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelistBase.sol +
93.8%93.8%
+
93.8 %15 / 1685.7 %6 / 7100.0 %6 / 6
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/index-sort-l.html b/doc/coverage/coverage/src/registry/abstract/index-sort-l.html new file mode 100644 index 00000000..8209c5a0 --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/index-sort-l.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstractHitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelistBase.sol +
93.8%93.8%
+
93.8 %15 / 1685.7 %6 / 7100.0 %6 / 6
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/abstract/index.html b/doc/coverage/coverage/src/registry/abstract/index.html new file mode 100644 index 00000000..5e4a528f --- /dev/null +++ b/doc/coverage/coverage/src/registry/abstract/index.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry/abstract + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registry/abstractHitTotalCoverage
Test:lcov2.infoLines:151693.8 %
Date:2026-08-19 15:38:25Functions:6785.7 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelistBase.sol +
93.8%93.8%
+
93.8 %15 / 1685.7 %6 / 7100.0 %6 / 6
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/index-sort-b.html b/doc/coverage/coverage/src/registry/index-sort-b.html new file mode 100644 index 00000000..48caa2be --- /dev/null +++ b/doc/coverage/coverage/src/registry/index-sort-b.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registryHitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelist.sol +
100.0%
+
100.0 %1 / 1100.0 %1 / 1-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/index-sort-f.html b/doc/coverage/coverage/src/registry/index-sort-f.html new file mode 100644 index 00000000..edf10b8f --- /dev/null +++ b/doc/coverage/coverage/src/registry/index-sort-f.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registryHitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelist.sol +
100.0%
+
100.0 %1 / 1100.0 %1 / 1-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/index-sort-l.html b/doc/coverage/coverage/src/registry/index-sort-l.html new file mode 100644 index 00000000..9d62d8e8 --- /dev/null +++ b/doc/coverage/coverage/src/registry/index-sort-l.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registryHitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelist.sol +
100.0%
+
100.0 %1 / 1100.0 %1 / 1-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/registry/index.html b/doc/coverage/coverage/src/registry/index.html new file mode 100644 index 00000000..b05af388 --- /dev/null +++ b/doc/coverage/coverage/src/registry/index.html @@ -0,0 +1,107 @@ + + + + + + + LCOV - lcov2.info - src/registry + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/registryHitTotalCoverage
Test:lcov2.infoLines:11100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverageBranches Sort by branch coverage
IdentityRegistryWhitelist.sol +
100.0%
+
100.0 %1 / 1100.0 %1 / 1-0 / 0
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func-sort-c.html index 73565570..27e69cd7 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLight.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLight.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 9 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 @@ -78,11 +78,11 @@ RuleConditionalTransferLight._onlyComplianceManager - 58 + 69 RuleConditionalTransferLight._authorizeTransferApproval - 7607 + 7655
diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func.html index 0677d526..d2686143 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLight.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLight.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 9 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 @@ -74,11 +74,11 @@ RuleConditionalTransferLight._authorizeTransferApproval - 7607 + 7655 RuleConditionalTransferLight._onlyComplianceManager - 58 + 69 RuleConditionalTransferLight.supportsInterface diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.gcov.html index 56926ffc..f75c9fa3 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLight.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLight.sol + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLight.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 9 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 @@ -130,12 +130,12 @@ 59 : : /** 60 : : * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. 61 : : */ - 62 : 58 : function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + 62 : 69 : function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} 63 : : 64 : : /** 65 : : * @notice Reverts unless the caller holds `OPERATOR_ROLE`. 66 : : */ - 67 : 7607 : function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} + 67 : 7655 : function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} 68 : : 69 : : /** 70 : : * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func-sort-c.html index ba65d0e5..c18c21df 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightMultiToken.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightMultiToken.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 3 @@ -74,11 +74,11 @@ RuleConditionalTransferLightMultiToken._authorizeTransferApproval - 31 + 43 RuleConditionalTransferLightMultiToken._onlyComplianceManager - 38 + 52
diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func.html index b92fbd57..94b98aed 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightMultiToken.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightMultiToken.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 3 @@ -70,11 +70,11 @@ RuleConditionalTransferLightMultiToken._authorizeTransferApproval - 31 + 43 RuleConditionalTransferLightMultiToken._onlyComplianceManager - 38 + 52 RuleConditionalTransferLightMultiToken.supportsInterface diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.gcov.html index 4475a275..8f50ad30 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiToken.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightMultiToken.sol + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightMultiToken.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 3 @@ -118,12 +118,12 @@ 47 : : /** 48 : : * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. 49 : : */ - 50 : 38 : function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + 50 : 52 : function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} 51 : : 52 : : /** 53 : : * @notice Reverts unless the caller holds `OPERATOR_ROLE`. 54 : : */ - 55 : 31 : function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} + 55 : 43 : function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} 56 : : } diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func-sort-c.html index 0dba0d34..ed0eeae4 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 6 8 - 75.0 % + 8 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 1 3 - 33.3 % + 3 + 100.0 % @@ -70,16 +70,16 @@ RuleConditionalTransferLightMultiTokenOwnable2Step._authorizeTransferApproval - 0 - - - RuleConditionalTransferLightMultiTokenOwnable2Step._onlyComplianceManager - 0 + 2 RuleConditionalTransferLightMultiTokenOwnable2Step.supportsInterface 5 + + RuleConditionalTransferLightMultiTokenOwnable2Step._onlyComplianceManager + 6 +
diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func.html index 7bddc554..a3c40945 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 6 8 - 75.0 % + 8 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 1 3 - 33.3 % + 3 + 100.0 % @@ -70,11 +70,11 @@ RuleConditionalTransferLightMultiTokenOwnable2Step._authorizeTransferApproval - 0 + 2 RuleConditionalTransferLightMultiTokenOwnable2Step._onlyComplianceManager - 0 + 6 RuleConditionalTransferLightMultiTokenOwnable2Step.supportsInterface diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.gcov.html index 48119635..5c9f585f 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 6 8 - 75.0 % + 8 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 1 3 - 33.3 % + 3 + 100.0 % @@ -117,12 +117,12 @@ 46 : : /** 47 : : * @notice Reverts unless the caller is the owner. 48 : : */ - 49 : 0 : function _onlyComplianceManager() internal view virtual override onlyOwner {} + 49 : 6 : function _onlyComplianceManager() internal view virtual override onlyOwner {} 50 : : 51 : : /** 52 : : * @notice Reverts unless the caller is the owner. 53 : : */ - 54 : 0 : function _authorizeTransferApproval() internal view virtual override onlyOwner {} + 54 : 2 : function _authorizeTransferApproval() internal view virtual override onlyOwner {} 55 : : } diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func-sort-c.html index 95384306..40b1993f 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 8 9 - 88.9 % + 9 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 3 4 - 75.0 % + 4 + 100.0 % @@ -70,16 +70,16 @@ RuleConditionalTransferLightOwnable2Step._authorizeComplianceBindingChange - 0 - - - RuleConditionalTransferLightOwnable2Step._onlyComplianceManager - 3 + 2 RuleConditionalTransferLightOwnable2Step._authorizeTransferApproval 4 + + RuleConditionalTransferLightOwnable2Step._onlyComplianceManager + 5 + RuleConditionalTransferLightOwnable2Step.supportsInterface 12 diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func.html index bc819ef1..d4ddccfe 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 8 9 - 88.9 % + 9 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 3 4 - 75.0 % + 4 + 100.0 % @@ -70,7 +70,7 @@ RuleConditionalTransferLightOwnable2Step._authorizeComplianceBindingChange - 0 + 2 RuleConditionalTransferLightOwnable2Step._authorizeTransferApproval @@ -78,7 +78,7 @@ RuleConditionalTransferLightOwnable2Step._onlyComplianceManager - 3 + 5 RuleConditionalTransferLightOwnable2Step.supportsInterface diff --git a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.gcov.html index e9a34296..4a2f3cd6 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol + LCOV - lcov2.info - src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 8 9 - 88.9 % + 9 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 3 4 - 75.0 % + 4 + 100.0 % @@ -128,7 +128,7 @@ 57 : : /** 58 : : * @notice Reverts unless the caller is the owner. 59 : : */ - 60 : 3 : function _onlyComplianceManager() internal view virtual override onlyOwner {} + 60 : 5 : function _onlyComplianceManager() internal view virtual override onlyOwner {} 61 : : 62 : : /** 63 : : * @notice Reverts unless the caller is the owner. @@ -138,7 +138,7 @@ 67 : : /** 68 : : * @notice Reverts unless the caller is the owner. 69 : : */ - 70 : 0 : function _authorizeComplianceBindingChange(address) internal view virtual override onlyOwner {} + 70 : 2 : function _authorizeComplianceBindingChange(address) internal view virtual override onlyOwner {} 71 : : } diff --git a/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func-sort-c.html index 86e07539..7141306a 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleMintAllowance.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleMintAllowance.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 @@ -74,15 +74,15 @@ RuleMintAllowance.supportsInterface - 34 + 37 RuleMintAllowance._onlyComplianceManager - 312 + 313 RuleMintAllowance._authorizeSetMintAllowance - 10070 + 10047
diff --git a/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func.html b/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func.html index 1551c0a3..57b0609f 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleMintAllowance.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleMintAllowance.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 @@ -74,15 +74,15 @@ RuleMintAllowance._authorizeSetMintAllowance - 10070 + 10047 RuleMintAllowance._onlyComplianceManager - 312 + 313 RuleMintAllowance.supportsInterface - 34 + 37
diff --git a/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.gcov.html index 53e1d295..4ff8f20e 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/RuleMintAllowance.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleMintAllowance.sol + LCOV - lcov2.info - src/rules/operation/RuleMintAllowance.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 @@ -106,7 +106,7 @@ 35 : : /** 36 : : * @inheritdoc IERC165 37 : : */ - 38 : 34 : function supportsInterface(bytes4 interfaceId) + 38 : 37 : function supportsInterface(bytes4 interfaceId) 39 : : public 40 : : view 41 : : virtual @@ -115,10 +115,10 @@ 44 : : { 45 : : // Do not advertise full ERC-3643 ICompliance: its 3-arg mint callback 46 : : // cannot identify the minter, so quota enforcement needs the spender-aware path. - 47 : 34 : return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID - 48 : 33 : || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID - 49 : 32 : || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId - 50 : 21 : || AccessControlEnumerable.supportsInterface(interfaceId); + 47 : 37 : return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID + 48 : 36 : || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID + 49 : 35 : || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId + 50 : 23 : || AccessControlEnumerable.supportsInterface(interfaceId); 51 : : } 52 : : 53 : : /*////////////////////////////////////////////////////////////// @@ -128,12 +128,12 @@ 57 : : /** 58 : : * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. 59 : : */ - 60 : 312 : function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + 60 : 313 : function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} 61 : : 62 : : /** 63 : : * @notice Reverts unless the caller holds `ALLOWANCE_OPERATOR_ROLE`. 64 : : */ - 65 : 10070 : function _authorizeSetMintAllowance() internal view virtual override onlyRole(ALLOWANCE_OPERATOR_ROLE) {} + 65 : 10047 : function _authorizeSetMintAllowance() internal view virtual override onlyRole(ALLOWANCE_OPERATOR_ROLE) {} 66 : : 67 : : /** 68 : : * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. diff --git a/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func-sort-c.html index f2353afa..c79e7bcc 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleMintAllowanceOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleMintAllowanceOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func.html index a7f43465..45102143 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleMintAllowanceOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/operation/RuleMintAllowanceOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.gcov.html index cc4d4b8a..a3238cc9 100644 --- a/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/RuleMintAllowanceOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/RuleMintAllowanceOwnable2Step.sol + LCOV - lcov2.info - src/rules/operation/RuleMintAllowanceOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 8 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 4 diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func-sort-c.html index 7820ea3b..9598268a 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 41 - 43 - 95.3 % + 42 + 44 + 95.5 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 10 @@ -69,52 +69,52 @@ Hit count Sort by hit count - RuleConditionalTransferLightApprovalBase._authorizeTransferApproval + RuleConditionalTransferLightApprovalBase._authorizeTransferApproval 0 - RuleConditionalTransferLightApprovalBase._authorizeTransferExecution + RuleConditionalTransferLightApprovalBase._authorizeTransferExecution 0 - RuleConditionalTransferLightApprovalBase._transferredFromContext - 3 + RuleConditionalTransferLightApprovalBase.onlyTransferApprover + 4 - RuleConditionalTransferLightApprovalBase.onlyTransferExecutor - 3 + RuleConditionalTransferLightApprovalBase.resetApproval + 4 - RuleConditionalTransferLightApprovalBase.transferred - 3 + RuleConditionalTransferLightApprovalBase._transferredFromContext + 6 - RuleConditionalTransferLightApprovalBase.onlyTransferApprover - 4 + RuleConditionalTransferLightApprovalBase.onlyTransferExecutor + 6 - RuleConditionalTransferLightApprovalBase.resetApproval - 4 + RuleConditionalTransferLightApprovalBase.transferred + 6 - RuleConditionalTransferLightApprovalBase.cancelTransferApproval - 1386 + RuleConditionalTransferLightApprovalBase.cancelTransferApproval + 1405 RuleConditionalTransferLightApprovalBase.approveTransfer - 6210 + 6231 - RuleConditionalTransferLightApprovalBase._transferred - 6353 + RuleConditionalTransferLightApprovalBase._transferred + 6244 - RuleConditionalTransferLightApprovalBase.approvedCount - 9207 + RuleConditionalTransferLightApprovalBase.approvedCount + 9216 - RuleConditionalTransferLightApprovalBase._transferHash - 19124 + RuleConditionalTransferLightApprovalBase._transferHash + 19083
diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func.html index 25cd8a9e..342c8bf6 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 41 - 43 - 95.3 % + 42 + 44 + 95.5 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 10 @@ -69,36 +69,36 @@ Hit count Sort by hit count - RuleConditionalTransferLightApprovalBase._authorizeTransferApproval + RuleConditionalTransferLightApprovalBase._authorizeTransferApproval 0 - RuleConditionalTransferLightApprovalBase._authorizeTransferExecution + RuleConditionalTransferLightApprovalBase._authorizeTransferExecution 0 - RuleConditionalTransferLightApprovalBase._transferHash - 19124 + RuleConditionalTransferLightApprovalBase._transferHash + 19083 - RuleConditionalTransferLightApprovalBase._transferred - 6353 + RuleConditionalTransferLightApprovalBase._transferred + 6244 - RuleConditionalTransferLightApprovalBase._transferredFromContext - 3 + RuleConditionalTransferLightApprovalBase._transferredFromContext + 6 RuleConditionalTransferLightApprovalBase.approveTransfer - 6210 + 6231 - RuleConditionalTransferLightApprovalBase.approvedCount - 9207 + RuleConditionalTransferLightApprovalBase.approvedCount + 9216 - RuleConditionalTransferLightApprovalBase.cancelTransferApproval - 1386 + RuleConditionalTransferLightApprovalBase.cancelTransferApproval + 1405 RuleConditionalTransferLightApprovalBase.onlyTransferApprover @@ -106,15 +106,15 @@ RuleConditionalTransferLightApprovalBase.onlyTransferExecutor - 3 + 6 - RuleConditionalTransferLightApprovalBase.resetApproval + RuleConditionalTransferLightApprovalBase.resetApproval 4 RuleConditionalTransferLightApprovalBase.transferred - 3 + 6
diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.gcov.html index 31677e1d..5d7d307c 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 41 - 43 - 95.3 % + 42 + 44 + 95.5 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 10 @@ -95,8 +95,8 @@ 24 : : _; 25 : : } 26 : : - 27 : 3 : modifier onlyTransferExecutor() { - 28 : 3 : _authorizeTransferExecution(); + 27 : 6 : modifier onlyTransferExecutor() { + 28 : 6 : _authorizeTransferExecution(); 29 : : _; 30 : : } 31 : : @@ -108,8 +108,8 @@ 37 : : * @notice Consumes one approval for the transfer described by `ctx`. 38 : : * @param ctx The fungible transfer context (from, to, value). 39 : : */ - 40 : 3 : function transferred(ITransferContext.FungibleTransferContext calldata ctx) external onlyTransferExecutor { - 41 : 3 : _transferredFromContext(ctx); + 40 : 6 : function transferred(ITransferContext.FungibleTransferContext calldata ctx) external onlyTransferExecutor { + 41 : 6 : _transferredFromContext(ctx); 42 : : } 43 : : 44 : : /*////////////////////////////////////////////////////////////// @@ -122,123 +122,138 @@ 51 : : * @param to The recipient of the transfer to approve. 52 : : * @param value The amount of the transfer to approve. 53 : : */ - 54 : 6210 : function approveTransfer(address from, address to, uint256 value) public onlyTransferApprover { - 55 : 6213 : bytes32 transferHash = _transferHash(from, to, value); - 56 : 6213 : approvalCounts[transferHash] += 1; - 57 : 6213 : emit TransferApproved(from, to, value, approvalCounts[transferHash]); - 58 : : } - 59 : : - 60 : : /** - 61 : : * @notice Cancels one outstanding approval for the given transfer; reverts if none exists. - 62 : : * @param from The sender of the transfer whose approval is cancelled. - 63 : : * @param to The recipient of the transfer whose approval is cancelled. - 64 : : * @param value The amount of the transfer whose approval is cancelled. - 65 : : */ - 66 : 1386 : function cancelTransferApproval(address from, address to, uint256 value) public onlyTransferApprover { - 67 : 1385 : bytes32 transferHash = _transferHash(from, to, value); - 68 : 1385 : uint256 count = approvalCounts[transferHash]; - 69 [ + + ]: 1385 : require(count != 0, TransferApprovalNotFound()); - 70 : 1384 : approvalCounts[transferHash] = count - 1; - 71 : 1384 : emit TransferApprovalCancelled(from, to, value, approvalCounts[transferHash]); - 72 : : } - 73 : : - 74 : : /** - 75 : : * @notice Discards every outstanding approval for the given transfer in one call. - 76 : : * @dev - 77 : : * - Reverts if no approval exists, per the single-item convention (use {cancelTransferApproval} - 78 : : * to remove exactly one). - 79 : : * - Deliberately does NOT require a bound token: the primary use is cleaning up approvals that - 80 : : * survived an {unbindToken}, at which point no token is bound. See the {bindToken} warning. - 81 : : * @param from The sender of the transfer whose approvals are cleared. - 82 : : * @param to The recipient of the transfer whose approvals are cleared. - 83 : : * @param value The amount of the transfer whose approvals are cleared. - 84 : : * @return cleared The approval count that was discarded. - 85 : : */ - 86 : 4 : function resetApproval(address from, address to, uint256 value) - 87 : : public - 88 : : virtual - 89 : : onlyTransferApprover - 90 : : returns (uint256 cleared) - 91 : : { - 92 : 3 : bytes32 transferHash = _transferHash(from, to, value); - 93 : 3 : cleared = approvalCounts[transferHash]; - 94 [ + + ]: 3 : require(cleared != 0, TransferApprovalNotFound()); - 95 : 2 : approvalCounts[transferHash] = 0; - 96 : 2 : emit TransferApprovalReset(from, to, value, cleared); - 97 : : } - 98 : : - 99 : : /** - 100 : : * @notice Returns the number of outstanding approvals for the given transfer. - 101 : : * @param from The sender of the transfer. - 102 : : * @param to The recipient of the transfer. - 103 : : * @param value The amount of the transfer. - 104 : : * @return The current approval count for the transfer. - 105 : : */ - 106 : 9207 : function approvedCount(address from, address to, uint256 value) public view returns (uint256) { - 107 : 9207 : bytes32 transferHash = _transferHash(from, to, value); - 108 : 9207 : return approvalCounts[transferHash]; - 109 : : } - 110 : : - 111 : : /*////////////////////////////////////////////////////////////// - 112 : : INTERNAL FUNCTIONS - 113 : : //////////////////////////////////////////////////////////////*/ - 114 : : - 115 : : /** - 116 : : * @notice Consumes one approval for the transfer described by `ctx`. - 117 : : * @param ctx The fungible transfer context (from, to, value). - 118 : : */ - 119 : 3 : function _transferredFromContext(ITransferContext.FungibleTransferContext calldata ctx) internal virtual { - 120 : 3 : _transferred(ctx.from, ctx.to, ctx.value); - 121 : : } - 122 : : - 123 : : /** - 124 : : * @notice Consumes one approval for the given transfer; reverts if none exists. - 125 : : * @dev No-op when either endpoint is the zero address (mint/burn). - 126 : : * @param from The sender of the transfer. - 127 : : * @param to The recipient of the transfer. - 128 : : * @param value The amount of the transfer. - 129 : : */ - 130 : 6353 : function _transferred(address from, address to, uint256 value) internal virtual { - 131 [ + ]: 6353 : if (from == address(0) || to == address(0)) { - 132 : 6353 : return; - 133 : : } - 134 : 2307 : bytes32 transferHash = _transferHash(from, to, value); - 135 : 2307 : uint256 count = approvalCounts[transferHash]; - 136 : : - 137 [ + + ]: 2307 : require(count != 0, TransferNotApproved()); - 138 : : - 139 : 2302 : approvalCounts[transferHash] = count - 1; - 140 : 2302 : emit TransferExecuted(from, to, value, approvalCounts[transferHash]); - 141 : : } - 142 : : - 143 : : /** - 144 : : * @notice Computes the storage key identifying a (from, to, value) transfer. - 145 : : * @param from The sender of the transfer. - 146 : : * @param to The recipient of the transfer. - 147 : : * @param value The amount of the transfer. - 148 : : * @return hash The keccak256 hash uniquely identifying the transfer. - 149 : : */ - 150 : 19124 : function _transferHash(address from, address to, uint256 value) internal pure virtual returns (bytes32 hash) { - 151 : : // Linter suggestion (`asm-keccak256`): hash packed values in assembly to avoid abi.encodePacked overhead. - 152 : : assembly ("memory-safe") { - 153 : 19124 : let ptr := mload(0x40) - 154 : 19124 : mstore(ptr, shl(96, from)) - 155 : 19124 : mstore(add(ptr, 0x20), shl(96, to)) - 156 : 19124 : mstore(add(ptr, 0x40), value) - 157 : 19124 : hash := keccak256(ptr, 0x60) - 158 : : } - 159 : : } - 160 : : - 161 : : /** - 162 : : * @notice Authorizes the caller to approve or cancel transfers; reverts if unauthorized. - 163 : : */ - 164 : 0 : function _authorizeTransferApproval() internal view virtual; - 165 : : - 166 : : /** - 167 : : * @notice Authorizes the caller to execute (consume) approved transfers; reverts if unauthorized. - 168 : : */ - 169 : 0 : function _authorizeTransferExecution() internal view virtual; - 170 : : } + 54 : 6231 : function approveTransfer(address from, address to, uint256 value) public virtual onlyTransferApprover { + 55 : 6238 : bytes32 transferHash = _transferHash(from, to, value); + 56 : 6238 : uint256 newCount = approvalCounts[transferHash] + 1; + 57 : 6238 : approvalCounts[transferHash] = newCount; + 58 : 6238 : emit TransferApproved(from, to, value, newCount); + 59 : : } + 60 : : + 61 : : /** + 62 : : * @notice Cancels one outstanding approval for the given transfer; reverts if none exists. + 63 : : * @param from The sender of the transfer whose approval is cancelled. + 64 : : * @param to The recipient of the transfer whose approval is cancelled. + 65 : : * @param value The amount of the transfer whose approval is cancelled. + 66 : : */ + 67 : 1405 : function cancelTransferApproval(address from, address to, uint256 value) public virtual onlyTransferApprover { + 68 : 1404 : bytes32 transferHash = _transferHash(from, to, value); + 69 : 1404 : uint256 count = approvalCounts[transferHash]; + 70 [ + + ]: 1404 : require(count != 0, TransferApprovalNotFound()); + 71 : 1403 : approvalCounts[transferHash] = count - 1; + 72 : 1403 : emit TransferApprovalCancelled(from, to, value, approvalCounts[transferHash]); + 73 : : } + 74 : : + 75 : : /** + 76 : : * @notice Discards every outstanding approval for the given transfer in one call. + 77 : : * @dev + 78 : : * - Reverts if no approval exists, per the single-item convention (use {cancelTransferApproval} + 79 : : * to remove exactly one). + 80 : : * - Deliberately does NOT require a bound token: the primary use is cleaning up approvals that + 81 : : * survived an {unbindToken}, at which point no token is bound. See the {bindToken} warning. + 82 : : * @param from The sender of the transfer whose approvals are cleared. + 83 : : * @param to The recipient of the transfer whose approvals are cleared. + 84 : : * @param value The amount of the transfer whose approvals are cleared. + 85 : : * @return cleared The approval count that was discarded. + 86 : : */ + 87 : 4 : function resetApproval(address from, address to, uint256 value) + 88 : : public + 89 : : virtual + 90 : : onlyTransferApprover + 91 : : returns (uint256 cleared) + 92 : : { + 93 : 3 : bytes32 transferHash = _transferHash(from, to, value); + 94 : 3 : cleared = approvalCounts[transferHash]; + 95 [ + + ]: 3 : require(cleared != 0, TransferApprovalNotFound()); + 96 : 2 : approvalCounts[transferHash] = 0; + 97 : 2 : emit TransferApprovalReset(from, to, value, cleared); + 98 : : } + 99 : : + 100 : : /** + 101 : : * @notice Returns the number of outstanding approvals for the given transfer. + 102 : : * @param from The sender of the transfer. + 103 : : * @param to The recipient of the transfer. + 104 : : * @param value The amount of the transfer. + 105 : : * @return The current approval count for the transfer. + 106 : : */ + 107 : 9216 : function approvedCount(address from, address to, uint256 value) public view returns (uint256) { + 108 : 9231 : bytes32 transferHash = _transferHash(from, to, value); + 109 : 9231 : return approvalCounts[transferHash]; + 110 : : } + 111 : : + 112 : : /*////////////////////////////////////////////////////////////// + 113 : : INTERNAL FUNCTIONS + 114 : : //////////////////////////////////////////////////////////////*/ + 115 : : + 116 : : /** + 117 : : * @notice Consumes one approval for the transfer described by `ctx`. + 118 : : * @param ctx The fungible transfer context (from, to, value). + 119 : : */ + 120 : 6 : function _transferredFromContext(ITransferContext.FungibleTransferContext calldata ctx) internal virtual { + 121 : 6 : _transferred(ctx.from, ctx.to, ctx.value); + 122 : : } + 123 : : + 124 : : /** + 125 : : * @notice Consumes one approval for the given transfer; reverts if none exists. + 126 : : * @dev No-op when either endpoint is the zero address (mint/burn). + 127 : : * @param from The sender of the transfer. + 128 : : * @param to The recipient of the transfer. + 129 : : * @param value The amount of the transfer. + 130 : : */ + 131 : 6244 : function _transferred(address from, address to, uint256 value) internal virtual { + 132 [ + ]: 6244 : if (from == address(0) || to == address(0)) { + 133 : 6244 : return; + 134 : : } + 135 : 2198 : bytes32 transferHash = _transferHash(from, to, value); + 136 : 2198 : uint256 count = approvalCounts[transferHash]; + 137 : : + 138 [ + + ]: 2198 : require(count != 0, TransferNotApproved()); + 139 : : + 140 : 2193 : approvalCounts[transferHash] = count - 1; + 141 : 2193 : emit TransferExecuted(from, to, value, approvalCounts[transferHash]); + 142 : : } + 143 : : + 144 : : /** + 145 : : * @notice Computes the storage key identifying a (from, to, value) transfer. + 146 : : * @dev The preimage is project-specific: **96 bytes, three words, each address LEFT-aligned and + 147 : : * right-padded with 12 zero bytes** (`from || to || value`). + 148 : : * WARNING: this is NEITHER `abi.encodePacked` (72 bytes, no padding) NOR `abi.encode` (96 bytes, + 149 : : * addresses RIGHT-aligned). Reimplementing it off-chain as either yields a different hash, and + 150 : : * because the result is a mapping key the mistake is **silent** -- the lookup returns 0, which is + 151 : : * indistinguishable from "no approval exists". Either of these reproduces it: + 152 : : * ```solidity + 153 : : * keccak256(abi.encodePacked(from, bytes12(0), to, bytes12(0), value)) + 154 : : * keccak256(abi.encode(bytes32(bytes20(from)), bytes32(bytes20(to)), value)) + 155 : : * ``` + 156 : : * Pinned by `testDocumentedPreimageMatchesTheStorageKey`. Use {approvedCount} unless you need + 157 : : * the storage slot directly. + 158 : : * @param from The sender of the transfer. + 159 : : * @param to The recipient of the transfer. + 160 : : * @param value The amount of the transfer. + 161 : : * @return hash The keccak256 hash uniquely identifying the transfer. + 162 : : */ + 163 : 19083 : function _transferHash(address from, address to, uint256 value) internal pure virtual returns (bytes32 hash) { + 164 : : // Hand-rolled rather than `abi.encodePacked` on the linter's `asm-keccak256` advice: this is + 165 : : // on the transfer write path, and the assembly is ~109 gas cheaper per call. Injectivity is + 166 : : // verified in `CLAUDE_AUDIT.md` F-12; the exact layout is documented above. + 167 : : assembly ("memory-safe") { + 168 : 19083 : let ptr := mload(0x40) + 169 : 19083 : mstore(ptr, shl(96, from)) + 170 : 19083 : mstore(add(ptr, 0x20), shl(96, to)) + 171 : 19083 : mstore(add(ptr, 0x40), value) + 172 : 19083 : hash := keccak256(ptr, 0x60) + 173 : : } + 174 : : } + 175 : : + 176 : : /** + 177 : : * @notice Authorizes the caller to approve or cancel transfers; reverts if unauthorized. + 178 : : */ + 179 : 0 : function _authorizeTransferApproval() internal view virtual; + 180 : : + 181 : : /** + 182 : : * @notice Authorizes the caller to execute (consume) approved transfers; reverts if unauthorized. + 183 : : */ + 184 : 0 : function _authorizeTransferExecution() internal view virtual; + 185 : : } diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func-sort-c.html index fe089185..41d4067d 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 52 - 52 + 54 + 54 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 16 @@ -49,8 +49,8 @@ Branches: - 17 - 17 + 19 + 19 100.0 % @@ -73,7 +73,7 @@ 1 - RuleConditionalTransferLightBase.canTransferFrom + RuleConditionalTransferLightBase.canTransferFrom 1 @@ -85,7 +85,7 @@ 1 - RuleConditionalTransferLightBase.detectTransferRestrictionFrom + RuleConditionalTransferLightBase.detectTransferRestrictionFrom 1 @@ -93,44 +93,44 @@ 2 - RuleConditionalTransferLightBase.unbindRuleEngine + RuleConditionalTransferLightBase.unbindRuleEngine 3 - RuleConditionalTransferLightBase.canTransfer + RuleConditionalTransferLightBase.canTransfer 4 - RuleConditionalTransferLightBase.approveAndTransferIfAllowed - 6 - - - RuleConditionalTransferLightBase.detectTransferRestriction + RuleConditionalTransferLightBase.detectTransferRestriction 7 - RuleConditionalTransferLightBase.transferred.1 + RuleConditionalTransferLightBase.transferred.1 7 - RuleConditionalTransferLightBase.isTransferExecutor + RuleConditionalTransferLightBase.isTransferExecutor 8 - RuleConditionalTransferLightBase.bindRuleEngine + RuleConditionalTransferLightBase.approveAndTransferIfAllowed + 10 + + + RuleConditionalTransferLightBase.bindRuleEngine 13 - RuleConditionalTransferLightBase.bindToken - 45 + RuleConditionalTransferLightBase.bindToken + 58 - RuleConditionalTransferLightBase.transferred.0 - 6347 + RuleConditionalTransferLightBase.transferred.0 + 6236 - RuleConditionalTransferLightBase._authorizeTransferExecution - 6357 + RuleConditionalTransferLightBase._authorizeTransferExecution + 6246
diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func.html index f808c1e5..bf7166bc 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 52 - 52 + 54 + 54 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 16 @@ -49,8 +49,8 @@ Branches: - 17 - 17 + 19 + 19 100.0 % @@ -69,31 +69,31 @@ Hit count Sort by hit count - RuleConditionalTransferLightBase._authorizeTransferExecution - 6357 + RuleConditionalTransferLightBase._authorizeTransferExecution + 6246 RuleConditionalTransferLightBase.approveAndTransferIfAllowed - 6 + 10 - RuleConditionalTransferLightBase.bindRuleEngine + RuleConditionalTransferLightBase.bindRuleEngine 13 - RuleConditionalTransferLightBase.bindToken - 45 + RuleConditionalTransferLightBase.bindToken + 58 RuleConditionalTransferLightBase.canReturnTransferRestrictionCode 1 - RuleConditionalTransferLightBase.canTransfer + RuleConditionalTransferLightBase.canTransfer 4 - RuleConditionalTransferLightBase.canTransferFrom + RuleConditionalTransferLightBase.canTransferFrom 1 @@ -105,15 +105,15 @@ 1 - RuleConditionalTransferLightBase.detectTransferRestriction + RuleConditionalTransferLightBase.detectTransferRestriction 7 - RuleConditionalTransferLightBase.detectTransferRestrictionFrom + RuleConditionalTransferLightBase.detectTransferRestrictionFrom 1 - RuleConditionalTransferLightBase.isTransferExecutor + RuleConditionalTransferLightBase.isTransferExecutor 8 @@ -121,15 +121,15 @@ 2 - RuleConditionalTransferLightBase.transferred.0 - 6347 + RuleConditionalTransferLightBase.transferred.0 + 6236 - RuleConditionalTransferLightBase.transferred.1 + RuleConditionalTransferLightBase.transferred.1 7 - RuleConditionalTransferLightBase.unbindRuleEngine + RuleConditionalTransferLightBase.unbindRuleEngine 3 diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.gcov.html index ecd72644..dc21c1b5 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightBase.sol + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightBase.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 52 - 52 + 54 + 54 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 16 @@ -49,8 +49,8 @@ Branches: - 17 - 17 + 19 + 19 100.0 % @@ -181,205 +181,208 @@ 110 : : * @param value The amount to transfer. 111 : : * @return True when the transfer succeeds. 112 : : */ - 113 : 6 : function approveAndTransferIfAllowed(address from, address to, uint256 value) + 113 : 10 : function approveAndTransferIfAllowed(address from, address to, uint256 value) 114 : : public - 115 : : onlyTransferApprover - 116 : : returns (bool) - 117 : : { - 118 : 6 : address token = getTokenBound(); - 119 [ + + ]: 6 : require(token != address(0), RuleConditionalTransferLight_TokenNotBound()); - 120 : : - 121 : 5 : approveTransfer(from, to, value); - 122 : : - 123 : 5 : uint256 allowed = IERC20(token).allowance(from, address(this)); - 124 [ + + ]: 4 : require(allowed >= value, RuleConditionalTransferLight_InsufficientAllowance(token, from, allowed, value)); - 125 : : - 126 : 3 : IERC20(token).safeTransferFrom(from, to, value); - 127 : 2 : return true; - 128 : : } + 115 : : virtual + 116 : : onlyTransferApprover + 117 : : returns (bool) + 118 : : { + 119 : 10 : address token = getTokenBound(); + 120 [ + + ]: 10 : require(token != address(0), RuleConditionalTransferLight_TokenNotBound()); + 121 : : + 122 : 9 : uint256 approvalsBefore = approvedCount(from, to, value); + 123 : 9 : approveTransfer(from, to, value); + 124 : : + 125 : 9 : uint256 allowed = IERC20(token).allowance(from, address(this)); + 126 [ + + ]: 8 : require(allowed >= value, RuleConditionalTransferLight_InsufficientAllowance(token, from, allowed, value)); + 127 : : + 128 : 7 : IERC20(token).safeTransferFrom(from, to, value); 129 : : - 130 : : /** - 131 : : * @inheritdoc IERC3643IComplianceContract - 132 : : */ - 133 : 6347 : function transferred(address from, address to, uint256 value) - 134 : : public - 135 : : override(IERC3643IComplianceContract) - 136 : : onlyTransferExecutor - 137 : : { - 138 : 6342 : _transferred(from, to, value); + 130 : : // The approval above exists ONLY for the token's compliance callback to consume. If the count + 131 : : // did not come back down, no callback reached this rule -- the binding is wrong -- and leaving + 132 : : // the surplus would authorise a later, never-approved transfer of the same tuple. Read after + 133 : : // the external call deliberately: a hostile token can make this fail, never pass spuriously. + 134 [ + + ]: 6 : require( + 135 : : approvedCount(from, to, value) == approvalsBefore, + 136 : : RuleConditionalTransferLight_ApprovalNotConsumed(token, from, to, value) + 137 : : ); + 138 : 5 : return true; 139 : : } 140 : : 141 : : /** - 142 : : * @inheritdoc IRuleEngine + 142 : : * @inheritdoc IERC3643IComplianceContract 143 : : */ - 144 : 7 : function transferred( - 145 : : address, - 146 : : /* spender */ - 147 : : address from, - 148 : : address to, - 149 : : uint256 value - 150 : : ) - 151 : : public - 152 : : override(IRuleEngine) - 153 : : onlyTransferExecutor - 154 : : { - 155 : 6 : _transferred(from, to, value); - 156 : : } - 157 : : - 158 : : /** - 159 : : * @notice Binds the ERC-20 token this rule acts on. Reverts if a token is already bound. - 160 : : * @dev Only ONE token may be bound at a time. To migrate to a new token, call `unbindToken` first. - 161 : : * @dev The bound token is BOTH the ERC-20 that {approveAndTransferIfAllowed} transfers, AND an - 162 : : * authorized caller of `transferred` (the direct-binding topology). If the rule sits behind - 163 : : * a RuleEngine, additionally call {bindRuleEngine} so the engine may call `transferred` too. - 164 : : * @dev ⚠️ Single-token binding alone does NOT guarantee token-scoped approvals: this rule's - 165 : : * approvals are keyed `(from, to, value)` with no token dimension. A multi-tenant - 166 : : * {bindRuleEngine} target would relay several tokens into the same approval bucket — see - 167 : : * the warning on {bindRuleEngine}. - 168 : : * @dev WARNING: `unbindToken` does not clear `approvalCounts`, and does not clear the bound - 169 : : * {ruleEngine} either. Stale approvals from the previous token remain in storage and can be - 170 : : * consumed after rebinding — and the previously bound engine stays authorized to consume - 171 : : * them until {unbindRuleEngine} is called. The operator who controls rebinding also controls - 172 : : * approvals, so the trust model is preserved, but integrators should be aware of this - 173 : : * behavior. When migrating, call {resetApproval} for each affected transfer AND - 174 : : * {unbindRuleEngine} before rebinding. - 175 : : * @param token The ERC-20 token to bind to this rule. - 176 : : */ - 177 : 45 : function bindToken(address token) public override onlyComplianceManager { - 178 [ + + ]: 44 : require(getTokenBound() == address(0), RuleConditionalTransferLight_TokenAlreadyBound()); - 179 : 43 : _bindToken(token); - 180 : : } - 181 : : - 182 : : /** - 183 : : * @notice Authorizes a RuleEngine to call this rule's transfer execution hooks. - 184 : : * @dev Independent of {bindToken}: the engine is authorized to call `transferred`, but is never - 185 : : * treated as the ERC-20 token. Bind the token with {bindToken} and the engine here, and - 186 : : * {approveAndTransferIfAllowed} works under the RuleEngine topology. - 187 : : * Reverts if a RuleEngine is already bound; call {unbindRuleEngine} first to migrate. - 188 : : * - 189 : : * @dev ⚠️ **Bind ONLY an engine that serves this one token.** - 190 : : * This rule's approvals are keyed `(from, to, value)` — they carry **no token dimension**. - 191 : : * A `RuleEngine` is multi-tenant by design (`_boundTokens` is a set), and it relays every - 192 : : * one of its tokens into the same `transferred(from, to, value)` hook, so the rule cannot - 193 : : * tell which token moved. If the bound engine serves several tokens, an approval recorded - 194 : : * for one of them is consumable by ANY of them: - 195 : : * - 196 : : * approveTransfer(alice, bob, 100) // intended for token A - 197 : : * <alice sends 100 of token B> // -> engine -> transferred(alice, bob, 100) - 198 : : * // the token-A approval is consumed - 199 : : * - 200 : : * This is inherent to the single-token rule and is why {RuleConditionalTransferLightMultiToken} - 201 : : * exists. Binding an engine does not change it — it only makes the topology usable, so the - 202 : : * constraint must be respected by the operator. If the engine is (or may become) - 203 : : * multi-tenant, do not use this rule. - 204 : : * - 205 : : * @param ruleEngine_ The RuleEngine allowed to call `transferred`. It MUST serve only the token - 206 : : * bound via {bindToken}. - 207 : : */ - 208 : 13 : function bindRuleEngine(address ruleEngine_) public virtual onlyComplianceManager { - 209 [ + + ]: 12 : require(ruleEngine_ != address(0), RuleConditionalTransferLight_RuleEngineAddressZeroNotAllowed()); - 210 [ + + ]: 11 : require(ruleEngine == address(0), RuleConditionalTransferLight_RuleEngineAlreadyBound()); - 211 : 10 : ruleEngine = ruleEngine_; - 212 : 10 : emit RuleEngineBound(ruleEngine_); - 213 : : } - 214 : : - 215 : : /** - 216 : : * @notice Revokes the bound RuleEngine's authorization to call the transfer execution hooks. - 217 : : * @dev Does NOT clear `approvalCounts` — see the {bindToken} warning and {resetApproval}. - 218 : : */ - 219 : 3 : function unbindRuleEngine() public virtual onlyComplianceManager { - 220 : 2 : address previous = ruleEngine; - 221 [ + + ]: 2 : require(previous != address(0), RuleConditionalTransferLight_RuleEngineNotBound()); - 222 : 1 : ruleEngine = address(0); - 223 : 1 : emit RuleEngineUnbound(previous); - 224 : : } - 225 : : - 226 : : /** - 227 : : * @notice Returns whether `caller` is authorized to call this rule's transfer execution hooks. - 228 : : * @param caller The address to check. - 229 : : * @return True if `caller` is the bound token or the bound RuleEngine. - 230 : : */ - 231 : 8 : function isTransferExecutor(address caller) public view virtual returns (bool) { - 232 : 6365 : return isTokenBound(caller) || (caller != address(0) && caller == ruleEngine); - 233 : : } - 234 : : - 235 : : /** - 236 : : * @inheritdoc IERC1404 - 237 : : */ - 238 : 7 : function detectTransferRestriction(address from, address to, uint256 value) - 239 : : public - 240 : : view - 241 : : override(IERC1404) - 242 : : returns (uint8) - 243 : : { - 244 [ + ]: 13 : if (from == address(0) || to == address(0)) { - 245 : 4 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 246 : : } - 247 : 9 : bytes32 transferHash = _transferHash(from, to, value); - 248 [ + ]: 9 : if (approvalCounts[transferHash] == 0) { - 249 : 6 : return CODE_TRANSFER_REQUEST_NOT_APPROVED; - 250 : : } - 251 : 3 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 252 : : } - 253 : : - 254 : : /** - 255 : : * @inheritdoc IERC1404Extend - 256 : : */ - 257 : 1 : function detectTransferRestrictionFrom( - 258 : : address, - 259 : : /* spender */ - 260 : : address from, - 261 : : address to, - 262 : : uint256 value - 263 : : ) - 264 : : public - 265 : : view - 266 : : override(IERC1404Extend) - 267 : : returns (uint8) - 268 : : { - 269 : 2 : return detectTransferRestriction(from, to, value); - 270 : : } - 271 : : - 272 : : /** - 273 : : * @inheritdoc IERC3643ComplianceRead - 274 : : */ - 275 : 4 : function canTransfer(address from, address to, uint256 value) - 276 : : public - 277 : : view - 278 : : override(IERC3643ComplianceRead) - 279 : : returns (bool) - 280 : : { - 281 : 4 : return detectTransferRestriction(from, to, value) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 282 : : } - 283 : : - 284 : : /** - 285 : : * @inheritdoc IERC7551Compliance - 286 : : */ - 287 : 1 : function canTransferFrom(address spender, address from, address to, uint256 value) - 288 : : public - 289 : : view - 290 : : override(IERC7551Compliance) - 291 : : returns (bool) - 292 : : { - 293 : 1 : return detectTransferRestrictionFrom(spender, from, to, value) - 294 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 295 : : } - 296 : : - 297 : : /*////////////////////////////////////////////////////////////// - 298 : : ACCESS CONTROL - 299 : : //////////////////////////////////////////////////////////////*/ - 300 : : - 301 : : /** - 302 : : * @notice Authorizes transfer execution: the bound token OR the bound RuleEngine may call the - 303 : : * execution hooks. Both topologies are therefore supported without conflating the two - 304 : : * roles of the binding — see {ruleEngine}. - 305 : : */ - 306 : 6357 : function _authorizeTransferExecution() internal view override { - 307 [ + + ]: 6357 : require( - 308 : : isTransferExecutor(_msgSender()), RuleConditionalTransferLight_TransferExecutorUnauthorized(_msgSender()) - 309 : : ); - 310 : : } - 311 : : } + 144 : 6236 : function transferred(address from, address to, uint256 value) + 145 : : public + 146 : : virtual + 147 : : override(IERC3643IComplianceContract) + 148 : : onlyTransferExecutor + 149 : : { + 150 : 6230 : _transferred(from, to, value); + 151 : : } + 152 : : + 153 : : /** + 154 : : * @inheritdoc IRuleEngine + 155 : : */ + 156 : 7 : function transferred( + 157 : : address, + 158 : : /* spender */ + 159 : : address from, + 160 : : address to, + 161 : : uint256 value + 162 : : ) + 163 : : public + 164 : : virtual + 165 : : override(IRuleEngine) + 166 : : onlyTransferExecutor + 167 : : { + 168 : 6 : _transferred(from, to, value); + 169 : : } + 170 : : + 171 : : /** + 172 : : * @notice Binds the ERC-20 token this rule acts on. Reverts if a token is already bound. + 173 : : * @dev Only ONE token may be bound at a time. To migrate to a new token, call `unbindToken` first. + 174 : : * @dev The bound token is BOTH the ERC-20 that {approveAndTransferIfAllowed} transfers, AND an + 175 : : * authorized caller of `transferred` (the direct-binding topology). If the rule sits behind + 176 : : * a RuleEngine, additionally call {bindRuleEngine} so the engine may call `transferred` too. + 177 : : * @dev WARNING: Single-token binding alone does NOT guarantee token-scoped approvals: this rule's + 178 : : * approvals are keyed `(from, to, value)` with no token dimension. A multi-tenant + 179 : : * {bindRuleEngine} target would relay several tokens into the same approval bucket — see + 180 : : * the warning on {bindRuleEngine}. + 181 : : * @dev WARNING: `unbindToken` does not clear `approvalCounts`, and does not clear the bound + 182 : : * {ruleEngine} either. Stale approvals from the previous token remain in storage and can be + 183 : : * consumed after rebinding — and the previously bound engine stays authorized to consume + 184 : : * them until {unbindRuleEngine} is called. The operator who controls rebinding also controls + 185 : : * approvals, so the trust model is preserved, but integrators should be aware of this + 186 : : * behavior. When migrating, call {resetApproval} for each affected transfer AND + 187 : : * {unbindRuleEngine} before rebinding. + 188 : : * @param token The ERC-20 token to bind to this rule. + 189 : : */ + 190 : 58 : function bindToken(address token) public virtual override onlyComplianceManager { + 191 [ + + ]: 57 : require(getTokenBound() == address(0), RuleConditionalTransferLight_TokenAlreadyBound()); + 192 : 56 : _bindToken(token); + 193 : : } + 194 : : + 195 : : /** + 196 : : * @notice Authorizes a RuleEngine to call this rule's transfer execution hooks. + 197 : : * @dev Independent of {bindToken}: the engine may call `transferred` but is never treated as the + 198 : : * ERC-20 token. Bind both and {approveAndTransferIfAllowed} works under the engine topology. + 199 : : * Reverts if an engine is already bound; call {unbindRuleEngine} first to migrate. + 200 : : * + 201 : : * @dev WARNING: **bind ONLY an engine that serves this one token.** Approvals here are keyed + 202 : : * `(from, to, value)` with **no token dimension**, while a `RuleEngine` is multi-tenant by + 203 : : * design and relays every one of its tokens into the same hook. If the engine serves several + 204 : : * tokens, an approval recorded for one is consumable by ANY of them -- approve 100 for token + 205 : : * A, and a 100 transfer of token B consumes it. That is inherent to the single-token rule and + 206 : : * is why {RuleConditionalTransferLightMultiToken} exists; binding an engine does not change + 207 : : * it. If the engine is or may become multi-tenant, do not use this rule. + 208 : : * @param ruleEngine_ The RuleEngine allowed to call `transferred`. It MUST serve only the token + 209 : : * bound via {bindToken}. + 210 : : */ + 211 : 13 : function bindRuleEngine(address ruleEngine_) public virtual onlyComplianceManager { + 212 [ + + ]: 12 : require(ruleEngine_ != address(0), RuleConditionalTransferLight_RuleEngineAddressZeroNotAllowed()); + 213 [ + + ]: 11 : require(ruleEngine == address(0), RuleConditionalTransferLight_RuleEngineAlreadyBound()); + 214 : 10 : ruleEngine = ruleEngine_; + 215 : 10 : emit RuleEngineBound(ruleEngine_); + 216 : : } + 217 : : + 218 : : /** + 219 : : * @notice Revokes the bound RuleEngine's authorization to call the transfer execution hooks. + 220 : : * @dev Does NOT clear `approvalCounts` — see the {bindToken} warning and {resetApproval}. + 221 : : */ + 222 : 3 : function unbindRuleEngine() public virtual onlyComplianceManager { + 223 : 2 : address previous = ruleEngine; + 224 [ + + ]: 2 : require(previous != address(0), RuleConditionalTransferLight_RuleEngineNotBound()); + 225 : 1 : ruleEngine = address(0); + 226 : 1 : emit RuleEngineUnbound(previous); + 227 : : } + 228 : : + 229 : : /** + 230 : : * @notice Returns whether `caller` is authorized to call this rule's transfer execution hooks. + 231 : : * @param caller The address to check. + 232 : : * @return True if `caller` is the bound token or the bound RuleEngine. + 233 : : */ + 234 : 8 : function isTransferExecutor(address caller) public view virtual returns (bool) { + 235 : 6254 : return isTokenBound(caller) || (caller != address(0) && caller == ruleEngine); + 236 : : } + 237 : : + 238 : : /** + 239 : : * @inheritdoc IERC1404 + 240 : : */ + 241 : 7 : function detectTransferRestriction(address from, address to, uint256 value) + 242 : : public + 243 : : view + 244 : : override(IERC1404) + 245 : : returns (uint8) + 246 : : { + 247 [ + ]: 13 : if (from == address(0) || to == address(0)) { + 248 : 4 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 249 : : } + 250 : 9 : bytes32 transferHash = _transferHash(from, to, value); + 251 [ + ]: 9 : if (approvalCounts[transferHash] == 0) { + 252 : 6 : return CODE_TRANSFER_REQUEST_NOT_APPROVED; + 253 : : } + 254 : 3 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 255 : : } + 256 : : + 257 : : /** + 258 : : * @inheritdoc IERC1404Extend + 259 : : */ + 260 : 1 : function detectTransferRestrictionFrom( + 261 : : address, + 262 : : /* spender */ + 263 : : address from, + 264 : : address to, + 265 : : uint256 value + 266 : : ) + 267 : : public + 268 : : view + 269 : : override(IERC1404Extend) + 270 : : returns (uint8) + 271 : : { + 272 : 2 : return detectTransferRestriction(from, to, value); + 273 : : } + 274 : : + 275 : : /** + 276 : : * @inheritdoc IERC3643ComplianceRead + 277 : : */ + 278 : 4 : function canTransfer(address from, address to, uint256 value) + 279 : : public + 280 : : view + 281 : : override(IERC3643ComplianceRead) + 282 : : returns (bool) + 283 : : { + 284 : 4 : return detectTransferRestriction(from, to, value) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 285 : : } + 286 : : + 287 : : /** + 288 : : * @inheritdoc IERC7551Compliance + 289 : : */ + 290 : 1 : function canTransferFrom(address spender, address from, address to, uint256 value) + 291 : : public + 292 : : view + 293 : : override(IERC7551Compliance) + 294 : : returns (bool) + 295 : : { + 296 : 1 : return detectTransferRestrictionFrom(spender, from, to, value) + 297 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 298 : : } + 299 : : + 300 : : /*////////////////////////////////////////////////////////////// + 301 : : ACCESS CONTROL + 302 : : //////////////////////////////////////////////////////////////*/ + 303 : : + 304 : : /** + 305 : : * @notice Authorizes transfer execution: the bound token OR the bound RuleEngine may call the + 306 : : * execution hooks. Both topologies are therefore supported without conflating the two + 307 : : * roles of the binding — see {ruleEngine}. + 308 : : */ + 309 : 6246 : function _authorizeTransferExecution() internal view virtual override { + 310 [ + + ]: 6246 : require( + 311 : : isTransferExecutor(_msgSender()), RuleConditionalTransferLight_TransferExecutorUnauthorized(_msgSender()) + 312 : : ); + 313 : : } + 314 : : } diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func-sort-c.html index d7b4766b..64eb7337 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 91 - 92 + 94 + 95 98.9 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 27 @@ -49,9 +49,9 @@ Branches: - 17 - 21 - 81.0 % + 23 + 23 + 100.0 % @@ -69,27 +69,19 @@ Hit count Sort by hit count - RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval + RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval 0 - RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed + RuleConditionalTransferLightMultiTokenBase.canTransfer 1 - - RuleConditionalTransferLightMultiTokenBase.canTransfer - 1 - - - RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval - 2 - RuleConditionalTransferLightMultiTokenBase.canReturnTransferRestrictionCode 2 - RuleConditionalTransferLightMultiTokenBase.canTransferFrom + RuleConditionalTransferLightMultiTokenBase.canTransferFrom 2 @@ -101,7 +93,7 @@ 2 - RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom + RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom 2 @@ -109,76 +101,84 @@ 2 - RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval + RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval 3 - RuleConditionalTransferLightMultiTokenBase.onlyTransferExecutor + RuleConditionalTransferLightMultiTokenBase.resetApproval 3 - RuleConditionalTransferLightMultiTokenBase.resetApproval - 3 + RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval + 4 - RuleConditionalTransferLightMultiTokenBase.transferred.0 - 3 + RuleConditionalTransferLightMultiTokenBase.onlyTransferExecutor + 4 - RuleConditionalTransferLightMultiTokenBase.transferred.1 + RuleConditionalTransferLightMultiTokenBase.transferred.0 4 - RuleConditionalTransferLightMultiTokenBase.canTransferForToken + RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed 5 - RuleConditionalTransferLightMultiTokenBase.transferred.2 - 6 + RuleConditionalTransferLightMultiTokenBase.canTransferForToken + 5 + + + RuleConditionalTransferLightMultiTokenBase.transferred.1 + 5 - RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution - 13 + RuleConditionalTransferLightMultiTokenBase.transferred.2 + 6 - RuleConditionalTransferLightMultiTokenBase._transferred + RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution 15 - RuleConditionalTransferLightMultiTokenBase.approvedCount + RuleConditionalTransferLightMultiTokenBase._transferred 16 - RuleConditionalTransferLightMultiTokenBase.approveTransfer + RuleConditionalTransferLightMultiTokenBase.approvedCount 24 + + RuleConditionalTransferLightMultiTokenBase.approveTransfer + 33 + RuleConditionalTransferLightMultiTokenBase.onlyTransferApprover - 24 + 33 - RuleConditionalTransferLightMultiTokenBase._approveTransfer - 25 + RuleConditionalTransferLightMultiTokenBase._approveTransfer + 36 - RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange - 38 + RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange + 58 - RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken + RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken 263 - RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction + RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction 265 - RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken + RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken 538 - RuleConditionalTransferLightMultiTokenBase._transferHash - 581 + RuleConditionalTransferLightMultiTokenBase._transferHash + 608
diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func.html index 1d3f5935..53ae1786 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 91 - 92 + 94 + 95 98.9 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 27 @@ -49,9 +49,9 @@ Branches: - 17 - 21 - 81.0 % + 23 + 23 + 100.0 % @@ -69,68 +69,68 @@ Hit count Sort by hit count - RuleConditionalTransferLightMultiTokenBase._approveTransfer - 25 + RuleConditionalTransferLightMultiTokenBase._approveTransfer + 36 - RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange - 38 + RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange + 58 - RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval + RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval 0 - RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution - 13 + RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution + 15 - RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval - 2 + RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval + 3 - RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken + RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken 538 - RuleConditionalTransferLightMultiTokenBase._transferHash - 581 + RuleConditionalTransferLightMultiTokenBase._transferHash + 608 - RuleConditionalTransferLightMultiTokenBase._transferred - 15 + RuleConditionalTransferLightMultiTokenBase._transferred + 16 - RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed - 1 + RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed + 5 RuleConditionalTransferLightMultiTokenBase.approveTransfer - 24 + 33 - RuleConditionalTransferLightMultiTokenBase.approvedCount - 16 + RuleConditionalTransferLightMultiTokenBase.approvedCount + 24 RuleConditionalTransferLightMultiTokenBase.canReturnTransferRestrictionCode 2 - RuleConditionalTransferLightMultiTokenBase.canTransfer + RuleConditionalTransferLightMultiTokenBase.canTransfer 1 - RuleConditionalTransferLightMultiTokenBase.canTransferForToken + RuleConditionalTransferLightMultiTokenBase.canTransferForToken 5 - RuleConditionalTransferLightMultiTokenBase.canTransferFrom + RuleConditionalTransferLightMultiTokenBase.canTransferFrom 2 - RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval - 3 + RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval + 4 RuleConditionalTransferLightMultiTokenBase.created @@ -141,15 +141,15 @@ 2 - RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction + RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction 265 - RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken + RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken 263 - RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom + RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom 2 @@ -158,26 +158,26 @@ RuleConditionalTransferLightMultiTokenBase.onlyTransferApprover - 24 + 33 RuleConditionalTransferLightMultiTokenBase.onlyTransferExecutor - 3 + 4 - RuleConditionalTransferLightMultiTokenBase.resetApproval + RuleConditionalTransferLightMultiTokenBase.resetApproval 3 RuleConditionalTransferLightMultiTokenBase.transferred.0 - 3 + 4 - RuleConditionalTransferLightMultiTokenBase.transferred.1 - 4 + RuleConditionalTransferLightMultiTokenBase.transferred.1 + 5 - RuleConditionalTransferLightMultiTokenBase.transferred.2 + RuleConditionalTransferLightMultiTokenBase.transferred.2 6 diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.gcov.html index ba354c72..da8c1a12 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol + LCOV - lcov2.info - src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 91 - 92 + 94 + 95 98.9 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 27 @@ -49,9 +49,9 @@ Branches: - 17 - 21 - 81.0 % + 23 + 23 + 100.0 % @@ -102,13 +102,13 @@ 31 : : */ 32 : : mapping(bytes32 => uint256) public approvalCounts; 33 : : - 34 : 24 : modifier onlyTransferApprover() { - 35 : 24 : _authorizeTransferApproval(); + 34 : 33 : modifier onlyTransferApprover() { + 35 : 33 : _authorizeTransferApproval(); 36 : : _; 37 : : } 38 : : - 39 : 3 : modifier onlyTransferExecutor() { - 40 : 3 : _authorizeTransferExecution(); + 39 : 4 : modifier onlyTransferExecutor() { + 40 : 4 : _authorizeTransferExecution(); 41 : : _; 42 : : } 43 : : @@ -134,8 +134,8 @@ 63 : : * @notice Consumes one approval for the transfer described by `ctx`, using the caller as the token. 64 : : * @param ctx The fungible transfer context (from, to, value). 65 : : */ - 66 : 3 : function transferred(ITransferContext.FungibleTransferContext calldata ctx) external onlyTransferExecutor { - 67 : 3 : _transferred(_msgSender(), ctx.from, ctx.to, ctx.value); + 66 : 4 : function transferred(ITransferContext.FungibleTransferContext calldata ctx) external onlyTransferExecutor { + 67 : 4 : _transferred(_msgSender(), ctx.from, ctx.to, ctx.value); 68 : : } 69 : : 70 : : /** @@ -167,364 +167,388 @@ 96 : : * @param to The recipient of the transfer to approve. 97 : : * @param value The amount of the transfer to approve. 98 : : */ - 99 : 24 : function approveTransfer(address token, address from, address to, uint256 value) public onlyTransferApprover { - 100 : 24 : _approveTransfer(token, from, to, value); - 101 : : } - 102 : : - 103 : : /** - 104 : : * @notice Cancels one outstanding approval for the given per-token transfer. - 105 : : * @param token The token the transfer applies to. - 106 : : * @param from The sender of the transfer whose approval is cancelled. - 107 : : * @param to The recipient of the transfer whose approval is cancelled. - 108 : : * @param value The amount of the transfer whose approval is cancelled. - 109 : : */ - 110 : 3 : function cancelTransferApproval(address token, address from, address to, uint256 value) - 111 : : public - 112 : : onlyTransferApprover - 113 : : { - 114 : 2 : _cancelTransferApproval(token, from, to, value); - 115 : : } - 116 : : - 117 : : /** - 118 : : * @notice Approves and performs a transferFrom of `token` using this rule as spender. - 119 : : * @dev Requires `from` to have approved this contract on `token`; the token must be bound. - 120 : : * @param token The token to transfer. - 121 : : * @param from The holder to transfer tokens from. - 122 : : * @param to The recipient of the transfer. - 123 : : * @param value The amount to transfer. - 124 : : * @return True when the transfer succeeds. - 125 : : */ - 126 : 1 : function approveAndTransferIfAllowed(address token, address from, address to, uint256 value) - 127 : : public - 128 : : onlyTransferApprover - 129 : : returns (bool) - 130 : : { - 131 [ # + ]: 1 : require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); - 132 : : - 133 : 1 : _approveTransfer(token, from, to, value); - 134 : : - 135 : 1 : uint256 allowed = IERC20(token).allowance(from, address(this)); - 136 [ # + ]: 1 : require( - 137 : : allowed >= value, RuleConditionalTransferLightMultiToken_InsufficientAllowance(token, from, allowed, value) - 138 : : ); - 139 : : - 140 : 1 : IERC20(token).safeTransferFrom(from, to, value); - 141 : 1 : return true; - 142 : : } - 143 : : - 144 : : /** - 145 : : * @inheritdoc IERC3643IComplianceContract - 146 : : */ - 147 : 4 : function transferred(address from, address to, uint256 value) - 148 : : public - 149 : : override(IERC3643IComplianceContract) - 150 : : onlyTransferExecutor - 151 : : { - 152 : 4 : _transferred(_msgSender(), from, to, value); - 153 : : } - 154 : : - 155 : : /** - 156 : : * @inheritdoc IRuleEngine - 157 : : */ - 158 : 6 : function transferred( - 159 : : address, - 160 : : /* spender */ - 161 : : address from, - 162 : : address to, - 163 : : uint256 value - 164 : : ) - 165 : : public - 166 : : override(IRuleEngine) - 167 : : onlyTransferExecutor - 168 : : { - 169 : 6 : _transferred(_msgSender(), from, to, value); - 170 : : } - 171 : : - 172 : : /** - 173 : : * @notice Discards every outstanding approval for the given per-token transfer in one call. - 174 : : * @dev - 175 : : * - Reverts if no approval exists, per the single-item convention (use {cancelTransferApproval} - 176 : : * to remove exactly one). - 177 : : * - Deliberately does NOT require the token to be bound, unlike {approveTransfer}: the primary - 178 : : * use is cleaning up approvals that survived an {unbindToken}, at which point the token is by - 179 : : * definition no longer bound. It is also the only way to clear approvals stranded under a key - 180 : : * that can never be consumed (see `RESULT.md` finding F-4). - 181 : : * @param token The token whose approvals are cleared. - 182 : : * @param from The sender of the transfer whose approvals are cleared. - 183 : : * @param to The recipient of the transfer whose approvals are cleared. - 184 : : * @param value The amount of the transfer whose approvals are cleared. - 185 : : * @return cleared The approval count that was discarded. - 186 : : */ - 187 : 3 : function resetApproval(address token, address from, address to, uint256 value) - 188 : : public - 189 : : virtual - 190 : : onlyTransferApprover - 191 : : returns (uint256 cleared) - 192 : : { - 193 : 2 : bytes32 transferHash = _transferHash(token, from, to, value); - 194 : 2 : cleared = approvalCounts[transferHash]; - 195 [ + + ]: 2 : require(cleared != 0, RuleConditionalTransferLightMultiToken_TransferApprovalNotFound()); - 196 : 1 : approvalCounts[transferHash] = 0; - 197 : 1 : emit TransferApprovalReset(token, from, to, value, cleared); - 198 : : } - 199 : : - 200 : : /** - 201 : : * @notice Returns the number of outstanding approvals for the given per-token transfer. - 202 : : * @param token The token the transfer applies to. - 203 : : * @param from The sender of the transfer. - 204 : : * @param to The recipient of the transfer. - 205 : : * @param value The amount of the transfer. - 206 : : * @return The current approval count for the transfer. - 207 : : */ - 208 : 16 : function approvedCount(address token, address from, address to, uint256 value) public view returns (uint256) { - 209 : 16 : bytes32 transferHash = _transferHash(token, from, to, value); - 210 : 16 : return approvalCounts[transferHash]; - 211 : : } - 212 : : - 213 : : /** - 214 : : * @inheritdoc IERC1404 - 215 : : * @dev CALLER-DEPENDENT. The token key is derived from `msg.sender`, so this view only returns a - 216 : : * meaningful answer when it is invoked BY the bound token. Any other caller — an off-chain - 217 : : * `eth_call` from a wallet, an explorer, an aggregator — always receives - 218 : : * `CODE_TRANSFER_REQUEST_NOT_APPROVED`, even for a transfer that is approved and will succeed. - 219 : : * It is fail-closed, but it carries no signal for third-party pre-flight. - 220 : : * Use {detectTransferRestrictionForToken} instead, which takes the token explicitly. - 221 : : */ - 222 : 265 : function detectTransferRestriction(address from, address to, uint256 value) - 223 : : public - 224 : : view - 225 : : override(IERC1404) - 226 : : returns (uint8) - 227 : : { - 228 : 270 : return _detectTransferRestrictionForToken(_msgSender(), from, to, value); - 229 : : } - 230 : : - 231 : : /** - 232 : : * @notice Caller-explicit pre-flight: returns the restriction code for a transfer of `token`. - 233 : : * @dev Unlike {detectTransferRestriction}, the token is passed in rather than derived from - 234 : : * `msg.sender`, so any caller (notably an off-chain `eth_call`) gets the real answer. - 235 : : * This is the view integrators should use. - 236 : : * @param token The token the transfer applies to. - 237 : : * @param from The sender of the transfer. - 238 : : * @param to The recipient of the transfer. - 239 : : * @param value The amount of the transfer. - 240 : : * @return The restriction code, or TRANSFER_OK when an approval exists. - 241 : : */ - 242 : 263 : function detectTransferRestrictionForToken(address token, address from, address to, uint256 value) - 243 : : public - 244 : : view - 245 : : virtual - 246 : : returns (uint8) - 247 : : { - 248 : 263 : return _detectTransferRestrictionForToken(token, from, to, value); - 249 : : } - 250 : : - 251 : : /** - 252 : : * @notice Caller-explicit pre-flight: whether a transfer of `token` is currently approved. - 253 : : * @dev The boolean counterpart of {detectTransferRestrictionForToken}. Prefer this over - 254 : : * {canTransfer}, which is caller-dependent. - 255 : : * @param token The token the transfer applies to. - 256 : : * @param from The sender of the transfer. - 257 : : * @param to The recipient of the transfer. - 258 : : * @param value The amount of the transfer. - 259 : : * @return True when the transfer is approved for `token`. - 260 : : */ - 261 : 5 : function canTransferForToken(address token, address from, address to, uint256 value) - 262 : : public - 263 : : view - 264 : : virtual - 265 : : returns (bool) - 266 : : { - 267 : 5 : return _detectTransferRestrictionForToken(token, from, to, value) - 268 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 269 : : } - 270 : : - 271 : : /** - 272 : : * @inheritdoc IERC1404Extend - 273 : : */ - 274 : 2 : function detectTransferRestrictionFrom( - 275 : : address, - 276 : : /* spender */ - 277 : : address from, - 278 : : address to, - 279 : : uint256 value - 280 : : ) - 281 : : public - 282 : : view - 283 : : override(IERC1404Extend) - 284 : : returns (uint8) - 285 : : { - 286 : 4 : return detectTransferRestriction(from, to, value); - 287 : : } - 288 : : - 289 : : /** - 290 : : * @inheritdoc IERC3643ComplianceRead - 291 : : * @dev CALLER-DEPENDENT, for the same reason as {detectTransferRestriction}: a caller that is not - 292 : : * the bound token always reads `false`. Use {canTransferForToken} for an off-chain pre-flight. - 293 : : */ - 294 : 1 : function canTransfer(address from, address to, uint256 value) - 295 : : public - 296 : : view - 297 : : override(IERC3643ComplianceRead) - 298 : : returns (bool) - 299 : : { - 300 : 1 : return detectTransferRestriction(from, to, value) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 301 : : } - 302 : : - 303 : : /** - 304 : : * @inheritdoc IERC7551Compliance - 305 : : */ - 306 : 2 : function canTransferFrom(address spender, address from, address to, uint256 value) - 307 : : public - 308 : : view - 309 : : override(IERC7551Compliance) - 310 : : returns (bool) - 311 : : { - 312 : 2 : return detectTransferRestrictionFrom(spender, from, to, value) - 313 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 314 : : } - 315 : : - 316 : : /** - 317 : : * @notice Computes the restriction code for a transfer of `token`, independently of the caller. - 318 : : * @dev Single source of truth for the read path: {detectTransferRestriction} feeds it - 319 : : * `_msgSender()`, while {detectTransferRestrictionForToken} feeds it an explicit token, so - 320 : : * the two can never disagree. Mints and burns are exempt; an unbound token has no - 321 : : * consumable approvals and is therefore always restricted (fail-closed). - 322 : : * @param token The token the transfer applies to. - 323 : : * @param from The sender of the transfer. - 324 : : * @param to The recipient of the transfer. - 325 : : * @param value The amount of the transfer. - 326 : : * @return The restriction code, or TRANSFER_OK when an approval exists. - 327 : : */ - 328 : 538 : function _detectTransferRestrictionForToken(address token, address from, address to, uint256 value) - 329 : : internal - 330 : : view - 331 : : virtual - 332 : : returns (uint8) - 333 : : { - 334 [ + ]: 538 : if (from == address(0) || to == address(0)) { - 335 : 2 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 336 : : } - 337 : : - 338 [ + ]: 536 : if (!isTokenBound(token)) { - 339 : 7 : return CODE_TRANSFER_REQUEST_NOT_APPROVED; - 340 : : } - 341 : : - 342 [ + ]: 529 : if (approvalCounts[_transferHash(token, from, to, value)] == 0) { - 343 : 519 : return CODE_TRANSFER_REQUEST_NOT_APPROVED; - 344 : : } - 345 : : - 346 : 10 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 347 : : } - 348 : : - 349 : : /** - 350 : : * @notice Authorizes changes to compliance binding: restricted to the compliance manager. - 351 : : * @dev NOT `view`, unlike every other access-control hook in this codebase. This is structural, - 352 : : * not an oversight: the implementation delegates to `_onlyComplianceManager()`, which - 353 : : * `lib/RuleEngine`'s {ERC3643ComplianceModule} declares as `internal virtual` (non-`view`). - 354 : : * Solidity checks mutability against a virtual's DECLARED type, not the installed override, - 355 : : * so calling it from a `view` function is a compile error — even though every override of it - 356 : : * in this repo is `view`. It can only become `view` once the upstream declaration does. - 357 : : * (The single-token rules avoid this by overriding this hook directly with `onlyRole(...)` - 358 : : * instead of delegating, which is why they are already `view`.) - 359 : : */ - 360 : 38 : function _authorizeComplianceBindingChange(address) internal virtual override { - 361 : 38 : _onlyComplianceManager(); - 362 : : } - 363 : : - 364 : : /** - 365 : : * @notice Records a new approval for the given per-token transfer; reverts if the token is not bound. - 366 : : * @param token The token the transfer applies to. - 367 : : * @param from The sender of the transfer. - 368 : : * @param to The recipient of the transfer. - 369 : : * @param value The amount of the transfer. - 370 : : */ - 371 : 25 : function _approveTransfer(address token, address from, address to, uint256 value) internal virtual { - 372 [ + + ]: 25 : require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); - 373 : 23 : bytes32 transferHash = _transferHash(token, from, to, value); - 374 : 23 : approvalCounts[transferHash] += 1; - 375 : 23 : emit TransferApproved(token, from, to, value, approvalCounts[transferHash]); - 376 : : } - 377 : : - 378 : : /** - 379 : : * @notice Cancels one outstanding approval for the given per-token transfer; reverts if none exists. - 380 : : * @param token The token the transfer applies to. - 381 : : * @param from The sender of the transfer. - 382 : : * @param to The recipient of the transfer. - 383 : : * @param value The amount of the transfer. - 384 : : */ - 385 : 2 : function _cancelTransferApproval(address token, address from, address to, uint256 value) internal virtual { - 386 [ # + ]: 2 : require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); - 387 : 2 : bytes32 transferHash = _transferHash(token, from, to, value); - 388 : 2 : uint256 count = approvalCounts[transferHash]; - 389 : : - 390 [ + + ]: 2 : require(count != 0, RuleConditionalTransferLightMultiToken_TransferApprovalNotFound()); - 391 : : - 392 : 1 : approvalCounts[transferHash] = count - 1; - 393 : 1 : emit TransferApprovalCancelled(token, from, to, value, approvalCounts[transferHash]); - 394 : : } - 395 : : - 396 : : /** - 397 : : * @notice Consumes one approval for the given per-token transfer; reverts if none exists. - 398 : : * @dev No-op when either endpoint is the zero address (mint/burn). - 399 : : * @param token The token the transfer applies to. - 400 : : * @param from The sender of the transfer. - 401 : : * @param to The recipient of the transfer. - 402 : : * @param value The amount of the transfer. - 403 : : */ - 404 : 15 : function _transferred(address token, address from, address to, uint256 value) internal virtual { - 405 [ + ]: 15 : if (from == address(0) || to == address(0)) { - 406 : 15 : return; - 407 : : } - 408 : : - 409 : 9 : bytes32 transferHash = _transferHash(token, from, to, value); - 410 : 9 : uint256 count = approvalCounts[transferHash]; - 411 : : - 412 [ + + ]: 9 : require(count != 0, RuleConditionalTransferLightMultiToken_TransferNotApproved()); - 413 : : - 414 : 6 : approvalCounts[transferHash] = count - 1; - 415 : 6 : emit TransferExecuted(token, from, to, value, approvalCounts[transferHash]); - 416 : : } - 417 : : - 418 : : /** - 419 : : * @notice Authorizes transfer execution: only a bound token may call the execution hooks. - 420 : : */ - 421 : 13 : function _authorizeTransferExecution() internal view virtual { - 422 [ # + ]: 13 : require( - 423 : : isTokenBound(_msgSender()), - 424 : : RuleConditionalTransferLightMultiToken_TransferExecutorUnauthorized(_msgSender()) - 425 : : ); - 426 : : } - 427 : : - 428 : : /** - 429 : : * @notice Authorizes the caller to approve or cancel transfers; reverts if unauthorized. - 430 : : */ - 431 : 0 : function _authorizeTransferApproval() internal view virtual; + 99 : 33 : function approveTransfer(address token, address from, address to, uint256 value) + 100 : : public + 101 : : virtual + 102 : : onlyTransferApprover + 103 : : { + 104 : 32 : _approveTransfer(token, from, to, value); + 105 : : } + 106 : : + 107 : : /** + 108 : : * @notice Cancels one outstanding approval for the given per-token transfer. + 109 : : * @param token The token the transfer applies to. + 110 : : * @param from The sender of the transfer whose approval is cancelled. + 111 : : * @param to The recipient of the transfer whose approval is cancelled. + 112 : : * @param value The amount of the transfer whose approval is cancelled. + 113 : : */ + 114 : 4 : function cancelTransferApproval(address token, address from, address to, uint256 value) + 115 : : public + 116 : : virtual + 117 : : onlyTransferApprover + 118 : : { + 119 : 3 : _cancelTransferApproval(token, from, to, value); + 120 : : } + 121 : : + 122 : : /** + 123 : : * @notice Approves and performs a transferFrom of `token` using this rule as spender. + 124 : : * @dev Requires `from` to have approved this contract on `token`; the token must be bound. + 125 : : * @param token The token to transfer. + 126 : : * @param from The holder to transfer tokens from. + 127 : : * @param to The recipient of the transfer. + 128 : : * @param value The amount to transfer. + 129 : : * @return True when the transfer succeeds. + 130 : : */ + 131 : 5 : function approveAndTransferIfAllowed(address token, address from, address to, uint256 value) + 132 : : public + 133 : : virtual + 134 : : onlyTransferApprover + 135 : : returns (bool) + 136 : : { + 137 [ + + ]: 5 : require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); + 138 : : + 139 : 4 : uint256 approvalsBefore = approvedCount(token, from, to, value); + 140 : 4 : _approveTransfer(token, from, to, value); + 141 : : + 142 : 4 : uint256 allowed = IERC20(token).allowance(from, address(this)); + 143 [ + + ]: 4 : require( + 144 : : allowed >= value, RuleConditionalTransferLightMultiToken_InsufficientAllowance(token, from, allowed, value) + 145 : : ); + 146 : : + 147 : 3 : IERC20(token).safeTransferFrom(from, to, value); + 148 : : + 149 : : // See the single-token twin: the approval exists only for the token's compliance callback, so + 150 : : // a count that did not come back down means no callback reached this rule and the surplus + 151 : : // would otherwise stay spendable. + 152 [ + + ]: 3 : require( + 153 : : approvedCount(token, from, to, value) == approvalsBefore, + 154 : : RuleConditionalTransferLightMultiToken_ApprovalNotConsumed(token, from, to, value) + 155 : : ); + 156 : 2 : return true; + 157 : : } + 158 : : + 159 : : /** + 160 : : * @inheritdoc IERC3643IComplianceContract + 161 : : */ + 162 : 5 : function transferred(address from, address to, uint256 value) + 163 : : public + 164 : : virtual + 165 : : override(IERC3643IComplianceContract) + 166 : : onlyTransferExecutor + 167 : : { + 168 : 4 : _transferred(_msgSender(), from, to, value); + 169 : : } + 170 : : + 171 : : /** + 172 : : * @inheritdoc IRuleEngine + 173 : : */ + 174 : 6 : function transferred( + 175 : : address, + 176 : : /* spender */ + 177 : : address from, + 178 : : address to, + 179 : : uint256 value + 180 : : ) + 181 : : public + 182 : : virtual + 183 : : override(IRuleEngine) + 184 : : onlyTransferExecutor + 185 : : { + 186 : 6 : _transferred(_msgSender(), from, to, value); + 187 : : } + 188 : : + 189 : : /** + 190 : : * @notice Discards every outstanding approval for the given per-token transfer in one call. + 191 : : * @dev + 192 : : * - Reverts if no approval exists, per the single-item convention (use {cancelTransferApproval} + 193 : : * to remove exactly one). + 194 : : * - Deliberately does NOT require the token to be bound, unlike {approveTransfer}: the primary + 195 : : * use is cleaning up approvals that survived an {unbindToken}, at which point the token is by + 196 : : * definition no longer bound. It is also the only way to clear approvals stranded under a key + 197 : : * that can never be consumed (see `CLAUDE_AUDIT.md` finding F-4). + 198 : : * @param token The token whose approvals are cleared. + 199 : : * @param from The sender of the transfer whose approvals are cleared. + 200 : : * @param to The recipient of the transfer whose approvals are cleared. + 201 : : * @param value The amount of the transfer whose approvals are cleared. + 202 : : * @return cleared The approval count that was discarded. + 203 : : */ + 204 : 3 : function resetApproval(address token, address from, address to, uint256 value) + 205 : : public + 206 : : virtual + 207 : : onlyTransferApprover + 208 : : returns (uint256 cleared) + 209 : : { + 210 : 2 : bytes32 transferHash = _transferHash(token, from, to, value); + 211 : 2 : cleared = approvalCounts[transferHash]; + 212 [ + + ]: 2 : require(cleared != 0, RuleConditionalTransferLightMultiToken_TransferApprovalNotFound()); + 213 : 1 : approvalCounts[transferHash] = 0; + 214 : 1 : emit TransferApprovalReset(token, from, to, value, cleared); + 215 : : } + 216 : : + 217 : : /** + 218 : : * @notice Returns the number of outstanding approvals for the given per-token transfer. + 219 : : * @param token The token the transfer applies to. + 220 : : * @param from The sender of the transfer. + 221 : : * @param to The recipient of the transfer. + 222 : : * @param value The amount of the transfer. + 223 : : * @return The current approval count for the transfer. + 224 : : */ + 225 : 24 : function approvedCount(address token, address from, address to, uint256 value) public view returns (uint256) { + 226 : 31 : bytes32 transferHash = _transferHash(token, from, to, value); + 227 : 31 : return approvalCounts[transferHash]; + 228 : : } + 229 : : + 230 : : /** + 231 : : * @inheritdoc IERC1404 + 232 : : * @dev CALLER-DEPENDENT. The token key is derived from `msg.sender`, so this view only returns a + 233 : : * meaningful answer when it is invoked BY the bound token. Any other caller — an off-chain + 234 : : * `eth_call` from a wallet, an explorer, an aggregator — always receives + 235 : : * `CODE_TRANSFER_REQUEST_NOT_APPROVED`, even for a transfer that is approved and will succeed. + 236 : : * It is fail-closed, but it carries no signal for third-party pre-flight. + 237 : : * Use {detectTransferRestrictionForToken} instead, which takes the token explicitly. + 238 : : */ + 239 : 265 : function detectTransferRestriction(address from, address to, uint256 value) + 240 : : public + 241 : : view + 242 : : override(IERC1404) + 243 : : returns (uint8) + 244 : : { + 245 : 270 : return _detectTransferRestrictionForToken(_msgSender(), from, to, value); + 246 : : } + 247 : : + 248 : : /** + 249 : : * @notice Caller-explicit pre-flight: returns the restriction code for a transfer of `token`. + 250 : : * @dev Unlike {detectTransferRestriction}, the token is passed in rather than derived from + 251 : : * `msg.sender`, so any caller (notably an off-chain `eth_call`) gets the real answer. + 252 : : * This is the view integrators should use. + 253 : : * @param token The token the transfer applies to. + 254 : : * @param from The sender of the transfer. + 255 : : * @param to The recipient of the transfer. + 256 : : * @param value The amount of the transfer. + 257 : : * @return The restriction code, or TRANSFER_OK when an approval exists. + 258 : : */ + 259 : 263 : function detectTransferRestrictionForToken(address token, address from, address to, uint256 value) + 260 : : public + 261 : : view + 262 : : virtual + 263 : : returns (uint8) + 264 : : { + 265 : 263 : return _detectTransferRestrictionForToken(token, from, to, value); + 266 : : } + 267 : : + 268 : : /** + 269 : : * @notice Caller-explicit pre-flight: whether a transfer of `token` is currently approved. + 270 : : * @dev The boolean counterpart of {detectTransferRestrictionForToken}. Prefer this over + 271 : : * {canTransfer}, which is caller-dependent. + 272 : : * @param token The token the transfer applies to. + 273 : : * @param from The sender of the transfer. + 274 : : * @param to The recipient of the transfer. + 275 : : * @param value The amount of the transfer. + 276 : : * @return True when the transfer is approved for `token`. + 277 : : */ + 278 : 5 : function canTransferForToken(address token, address from, address to, uint256 value) + 279 : : public + 280 : : view + 281 : : virtual + 282 : : returns (bool) + 283 : : { + 284 : 5 : return _detectTransferRestrictionForToken(token, from, to, value) + 285 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 286 : : } + 287 : : + 288 : : /** + 289 : : * @inheritdoc IERC1404Extend + 290 : : */ + 291 : 2 : function detectTransferRestrictionFrom( + 292 : : address, + 293 : : /* spender */ + 294 : : address from, + 295 : : address to, + 296 : : uint256 value + 297 : : ) + 298 : : public + 299 : : view + 300 : : override(IERC1404Extend) + 301 : : returns (uint8) + 302 : : { + 303 : 4 : return detectTransferRestriction(from, to, value); + 304 : : } + 305 : : + 306 : : /** + 307 : : * @inheritdoc IERC3643ComplianceRead + 308 : : * @dev CALLER-DEPENDENT, for the same reason as {detectTransferRestriction}: a caller that is not + 309 : : * the bound token always reads `false`. Use {canTransferForToken} for an off-chain pre-flight. + 310 : : */ + 311 : 1 : function canTransfer(address from, address to, uint256 value) + 312 : : public + 313 : : view + 314 : : override(IERC3643ComplianceRead) + 315 : : returns (bool) + 316 : : { + 317 : 1 : return detectTransferRestriction(from, to, value) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 318 : : } + 319 : : + 320 : : /** + 321 : : * @inheritdoc IERC7551Compliance + 322 : : */ + 323 : 2 : function canTransferFrom(address spender, address from, address to, uint256 value) + 324 : : public + 325 : : view + 326 : : override(IERC7551Compliance) + 327 : : returns (bool) + 328 : : { + 329 : 2 : return detectTransferRestrictionFrom(spender, from, to, value) + 330 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 331 : : } + 332 : : + 333 : : /** + 334 : : * @notice Authorizes changes to compliance binding: restricted to the compliance manager. + 335 : : * @dev NOT `view`, unlike every other access-control hook in this codebase. This is structural, + 336 : : * not an oversight: the implementation delegates to `_onlyComplianceManager()`, which + 337 : : * `lib/RuleEngine`'s {ERC3643ComplianceModule} declares as `internal virtual` (non-`view`). + 338 : : * Solidity checks mutability against a virtual's DECLARED type, not the installed override, + 339 : : * so calling it from a `view` function is a compile error — even though every override of it + 340 : : * in this repo is `view`. It can only become `view` once the upstream declaration does. + 341 : : * (The single-token rules avoid this by overriding this hook directly with `onlyRole(...)` + 342 : : * instead of delegating, which is why they are already `view`.) + 343 : : */ + 344 : 58 : function _authorizeComplianceBindingChange(address) internal virtual override { + 345 : 58 : _onlyComplianceManager(); + 346 : : } + 347 : : + 348 : : /** + 349 : : * @notice Records a new approval for the given per-token transfer; reverts if the token is not bound. + 350 : : * @param token The token the transfer applies to. + 351 : : * @param from The sender of the transfer. + 352 : : * @param to The recipient of the transfer. + 353 : : * @param value The amount of the transfer. + 354 : : */ + 355 : 36 : function _approveTransfer(address token, address from, address to, uint256 value) internal virtual { + 356 [ + + ]: 36 : require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); + 357 : 34 : bytes32 transferHash = _transferHash(token, from, to, value); + 358 : 34 : uint256 newCount = approvalCounts[transferHash] + 1; + 359 : 34 : approvalCounts[transferHash] = newCount; + 360 : 34 : emit TransferApproved(token, from, to, value, newCount); + 361 : : } + 362 : : + 363 : : /** + 364 : : * @notice Cancels one outstanding approval for the given per-token transfer; reverts if none exists. + 365 : : * @param token The token the transfer applies to. + 366 : : * @param from The sender of the transfer. + 367 : : * @param to The recipient of the transfer. + 368 : : * @param value The amount of the transfer. + 369 : : */ + 370 : 3 : function _cancelTransferApproval(address token, address from, address to, uint256 value) internal virtual { + 371 [ + + ]: 3 : require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); + 372 : 2 : bytes32 transferHash = _transferHash(token, from, to, value); + 373 : 2 : uint256 count = approvalCounts[transferHash]; + 374 : : + 375 [ + + ]: 2 : require(count != 0, RuleConditionalTransferLightMultiToken_TransferApprovalNotFound()); + 376 : : + 377 : 1 : approvalCounts[transferHash] = count - 1; + 378 : 1 : emit TransferApprovalCancelled(token, from, to, value, approvalCounts[transferHash]); + 379 : : } + 380 : : + 381 : : /** + 382 : : * @notice Consumes one approval for the given per-token transfer; reverts if none exists. + 383 : : * @dev No-op when either endpoint is the zero address (mint/burn). + 384 : : * @param token The token the transfer applies to. + 385 : : * @param from The sender of the transfer. + 386 : : * @param to The recipient of the transfer. + 387 : : * @param value The amount of the transfer. + 388 : : */ + 389 : 16 : function _transferred(address token, address from, address to, uint256 value) internal virtual { + 390 [ + ]: 16 : if (from == address(0) || to == address(0)) { + 391 : 16 : return; + 392 : : } + 393 : : + 394 : 10 : bytes32 transferHash = _transferHash(token, from, to, value); + 395 : 10 : uint256 count = approvalCounts[transferHash]; + 396 : : + 397 [ + + ]: 10 : require(count != 0, RuleConditionalTransferLightMultiToken_TransferNotApproved()); + 398 : : + 399 : 7 : approvalCounts[transferHash] = count - 1; + 400 : 7 : emit TransferExecuted(token, from, to, value, approvalCounts[transferHash]); + 401 : : } + 402 : : + 403 : : /** + 404 : : * @notice Computes the restriction code for a transfer of `token`, independently of the caller. + 405 : : * @dev Single source of truth for the read path: {detectTransferRestriction} feeds it + 406 : : * `_msgSender()`, while {detectTransferRestrictionForToken} feeds it an explicit token, so + 407 : : * the two can never disagree. Mints and burns are exempt; an unbound token has no + 408 : : * consumable approvals and is therefore always restricted (fail-closed). + 409 : : * @param token The token the transfer applies to. + 410 : : * @param from The sender of the transfer. + 411 : : * @param to The recipient of the transfer. + 412 : : * @param value The amount of the transfer. + 413 : : * @return The restriction code, or TRANSFER_OK when an approval exists. + 414 : : */ + 415 : 538 : function _detectTransferRestrictionForToken(address token, address from, address to, uint256 value) + 416 : : internal + 417 : : view + 418 : : virtual + 419 : : returns (uint8) + 420 : : { + 421 [ + ]: 538 : if (from == address(0) || to == address(0)) { + 422 : 2 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 423 : : } + 424 : : + 425 [ + ]: 536 : if (!isTokenBound(token)) { + 426 : 7 : return CODE_TRANSFER_REQUEST_NOT_APPROVED; + 427 : : } + 428 : : + 429 [ + ]: 529 : if (approvalCounts[_transferHash(token, from, to, value)] == 0) { + 430 : 519 : return CODE_TRANSFER_REQUEST_NOT_APPROVED; + 431 : : } 432 : : - 433 : : /** - 434 : : * @notice Computes the storage key identifying a (token, from, to, value) transfer. - 435 : : * @param token The token the transfer applies to. - 436 : : * @param from The sender of the transfer. - 437 : : * @param to The recipient of the transfer. - 438 : : * @param value The amount of the transfer. - 439 : : * @return hash The keccak256 hash uniquely identifying the transfer. - 440 : : */ - 441 : 581 : function _transferHash(address token, address from, address to, uint256 value) - 442 : : internal - 443 : : pure - 444 : : virtual - 445 : : returns (bytes32 hash) - 446 : : { - 447 : : assembly ("memory-safe") { - 448 : 581 : let ptr := mload(0x40) - 449 : 581 : mstore(ptr, shl(96, token)) - 450 : 581 : mstore(add(ptr, 0x20), shl(96, from)) - 451 : 581 : mstore(add(ptr, 0x40), shl(96, to)) - 452 : 581 : mstore(add(ptr, 0x60), value) - 453 : 581 : hash := keccak256(ptr, 0x80) - 454 : : } - 455 : : } - 456 : : } + 433 : 10 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 434 : : } + 435 : : + 436 : : /** + 437 : : * @notice Authorizes transfer execution: only a bound token may call the execution hooks. + 438 : : */ + 439 : 15 : function _authorizeTransferExecution() internal view virtual { + 440 [ + + ]: 15 : require( + 441 : : isTokenBound(_msgSender()), + 442 : : RuleConditionalTransferLightMultiToken_TransferExecutorUnauthorized(_msgSender()) + 443 : : ); + 444 : : } + 445 : : + 446 : : /** + 447 : : * @notice Authorizes the caller to approve or cancel transfers; reverts if unauthorized. + 448 : : */ + 449 : 0 : function _authorizeTransferApproval() internal view virtual; + 450 : : + 451 : : /** + 452 : : * @notice Computes the storage key identifying a (token, from, to, value) transfer. + 453 : : * @dev Same project-specific encoding as the single-token rule with `token` prepended: **128 + 454 : : * bytes, four words, each address LEFT-aligned and right-padded with 12 zero bytes.** + 455 : : * + 456 : : * WARNING: NEITHER `abi.encodePacked` NOR `abi.encode`. See + 457 : : * {RuleConditionalTransferLightApprovalBase._transferHash} for why that matters and for the + 458 : : * off-chain formulations that reproduce it. Use {approvedCount} unless you need the storage slot. + 459 : : * @param token The token the transfer applies to. + 460 : : * @param from The sender of the transfer. + 461 : : * @param to The recipient of the transfer. + 462 : : * @param value The amount of the transfer. + 463 : : * @return hash The keccak256 hash uniquely identifying the transfer. + 464 : : */ + 465 : 608 : function _transferHash(address token, address from, address to, uint256 value) + 466 : : internal + 467 : : pure + 468 : : virtual + 469 : : returns (bytes32 hash) + 470 : : { + 471 : : assembly ("memory-safe") { + 472 : 608 : let ptr := mload(0x40) + 473 : 608 : mstore(ptr, shl(96, token)) + 474 : 608 : mstore(add(ptr, 0x20), shl(96, from)) + 475 : 608 : mstore(add(ptr, 0x40), shl(96, to)) + 476 : 608 : mstore(add(ptr, 0x60), value) + 477 : 608 : hash := keccak256(ptr, 0x80) + 478 : : } + 479 : : } + 480 : : } diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func-sort-c.html index 4dfabb5d..00710c63 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleMintAllowanceBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleMintAllowanceBase.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 56 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 20 @@ -108,29 +108,29 @@ RuleMintAllowanceBase.onlyAllowanceOperator 4 - - RuleMintAllowanceBase.detectTransferRestriction - 5 - RuleMintAllowanceBase.canTransferFrom 6 - RuleMintAllowanceBase.detectTransferRestrictionFrom + RuleMintAllowanceBase.detectTransferRestriction 9 + + RuleMintAllowanceBase.detectTransferRestrictionFrom + 12 + RuleMintAllowanceBase._detectTransferRestrictionFrom - 15 + 18 RuleMintAllowanceBase.bindToken - 323 + 324 RuleMintAllowanceBase.decreaseMintAllowance - 3078 + 3055 RuleMintAllowanceBase.increaseMintAllowance @@ -146,11 +146,11 @@ RuleMintAllowanceBase._transferredFrom - 6848 + 6814 RuleMintAllowanceBase.transferred.1 - 6849 + 6815
diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func.html index 867ea8c4..b46ddf09 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleMintAllowanceBase.sol - functions + LCOV - lcov2.info - src/rules/operation/abstract/RuleMintAllowanceBase.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 56 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 20 @@ -74,7 +74,7 @@ RuleMintAllowanceBase._detectTransferRestrictionFrom - 15 + 18 RuleMintAllowanceBase._setMintAllowance @@ -86,11 +86,11 @@ RuleMintAllowanceBase._transferredFrom - 6848 + 6814 RuleMintAllowanceBase.bindToken - 323 + 324 RuleMintAllowanceBase.canReturnTransferRestrictionCode @@ -114,7 +114,7 @@ RuleMintAllowanceBase.decreaseMintAllowance - 3078 + 3055 RuleMintAllowanceBase.destroyed @@ -122,11 +122,11 @@ RuleMintAllowanceBase.detectTransferRestriction - 5 + 9 RuleMintAllowanceBase.detectTransferRestrictionFrom - 9 + 12 RuleMintAllowanceBase.increaseMintAllowance @@ -150,7 +150,7 @@ RuleMintAllowanceBase.transferred.1 - 6849 + 6815
diff --git a/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.gcov.html b/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.gcov.html index 6ec75d51..0d7f95e6 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/RuleMintAllowanceBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract/RuleMintAllowanceBase.sol + LCOV - lcov2.info - src/rules/operation/abstract/RuleMintAllowanceBase.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 56 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 20 @@ -175,12 +175,12 @@ 104 : : * @param minter The minter whose allowance is decreased. 105 : : * @param amount The amount to subtract from the allowance. 106 : : */ - 107 : 3078 : function decreaseMintAllowance(address minter, uint256 amount) public virtual onlyAllowanceOperator { - 108 : 3077 : uint256 current = mintAllowance[minter]; - 109 [ + + ]: 3077 : require(amount <= current, RuleMintAllowance_DecreaseBelowZero(minter, current, amount)); - 110 : 3076 : uint256 newAllowance = current - amount; - 111 : 3076 : mintAllowance[minter] = newAllowance; - 112 : 3076 : emit MintAllowanceDecreased(minter, amount, newAllowance); + 107 : 3055 : function decreaseMintAllowance(address minter, uint256 amount) public virtual onlyAllowanceOperator { + 108 : 3054 : uint256 current = mintAllowance[minter]; + 109 [ + + ]: 3054 : require(amount <= current, RuleMintAllowance_DecreaseBelowZero(minter, current, amount)); + 110 : 3053 : uint256 newAllowance = current - amount; + 111 : 3053 : mintAllowance[minter] = newAllowance; + 112 : 3053 : emit MintAllowanceDecreased(minter, amount, newAllowance); 113 : : } 114 : : 115 : : /** @@ -210,9 +210,9 @@ 139 : : * behavior. Call {clearMintAllowances} before rebinding to discard the previous quotas. 140 : : * @param token The caller (RuleEngine/token) to bind to this rule. 141 : : */ - 142 : 323 : function bindToken(address token) public virtual override onlyComplianceManager { - 143 [ + + ]: 321 : require(getTokenBound() == address(0), RuleMintAllowance_TokenAlreadyBound()); - 144 : 319 : _bindToken(token); + 142 : 324 : function bindToken(address token) public virtual override onlyComplianceManager { + 143 [ + + ]: 322 : require(getTokenBound() == address(0), RuleMintAllowance_TokenAlreadyBound()); + 144 : 320 : _bindToken(token); 145 : : } 146 : : 147 : : /** @@ -239,13 +239,13 @@ 168 : : * @param to The recipient address. 169 : : * @param value The amount transferred. 170 : : */ - 171 : 6849 : function transferred(address spender, address from, address to, uint256 value) + 171 : 6815 : function transferred(address spender, address from, address to, uint256 value) 172 : : public 173 : : virtual 174 : : override(IRuleEngine) 175 : : onlyBoundToken 176 : : { - 177 : 6848 : _transferredFrom(spender, from, to, value); + 177 : 6814 : _transferredFrom(spender, from, to, value); 178 : : } 179 : : 180 : : /** @@ -268,27 +268,27 @@ 197 : : * 3-arg call. Call `detectTransferRestrictionFrom` to check a minter's quota. 198 : : * @return The restriction code, always TRANSFER_OK. 199 : : */ - 200 : 5 : function detectTransferRestriction(address, address, uint256) + 200 : 9 : function detectTransferRestriction(address, address, uint256) 201 : : public 202 : : view 203 : : virtual 204 : : override(IERC1404) 205 : : returns (uint8) 206 : : { - 207 : 5 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 207 : 9 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); 208 : : } 209 : : 210 : : /** 211 : : * @inheritdoc IERC1404Extend 212 : : */ - 213 : 9 : function detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 213 : 12 : function detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) 214 : : public 215 : : view 216 : : virtual 217 : : override(IERC1404Extend) 218 : : returns (uint8) 219 : : { - 220 : 15 : return _detectTransferRestrictionFrom(spender, from, to, value); + 220 : 18 : return _detectTransferRestrictionFrom(spender, from, to, value); 221 : : } 222 : : 223 : : /** @@ -338,15 +338,15 @@ 267 : : * @param from The sender address (must be address(0) to deduct). 268 : : * @param value The amount minted. 269 : : */ - 270 : 6848 : function _transferredFrom(address spender, address from, address, uint256 value) internal virtual { - 271 [ + ]: 6848 : if (from != address(0)) { - 272 : 6848 : return; + 270 : 6814 : function _transferredFrom(address spender, address from, address, uint256 value) internal virtual { + 271 [ + ]: 6814 : if (from != address(0)) { + 272 : 6814 : return; 273 : : } - 274 : 3577 : uint256 current = mintAllowance[spender]; - 275 [ + + ]: 3577 : require(value <= current, RuleMintAllowance_AllowanceExceeded(address(this), spender, current, value)); - 276 : 3325 : uint256 remaining = current - value; - 277 : 3325 : mintAllowance[spender] = remaining; - 278 : 3325 : emit MintAllowanceConsumed(spender, value, remaining); + 274 : 3543 : uint256 current = mintAllowance[spender]; + 275 [ + + ]: 3543 : require(value <= current, RuleMintAllowance_AllowanceExceeded(address(this), spender, current, value)); + 276 : 3294 : uint256 remaining = current - value; + 277 : 3294 : mintAllowance[spender] = remaining; + 278 : 3294 : emit MintAllowanceConsumed(spender, value, remaining); 279 : : } 280 : : 281 : : /** @@ -367,14 +367,14 @@ 296 : : * @param value The amount to be minted. 297 : : * @return The restriction code (CODE_MINTER_ALLOWANCE_EXCEEDED or TRANSFER_OK). 298 : : */ - 299 : 15 : function _detectTransferRestrictionFrom(address spender, address from, address, uint256 value) + 299 : 18 : function _detectTransferRestrictionFrom(address spender, address from, address, uint256 value) 300 : : internal 301 : : view 302 : : virtual 303 : : returns (uint8) 304 : : { - 305 [ + ]: 15 : if (from == address(0) && mintAllowance[spender] < value) { - 306 : 7 : return CODE_MINTER_ALLOWANCE_EXCEEDED; + 305 [ + ]: 18 : if (from == address(0) && mintAllowance[spender] < value) { + 306 : 10 : return CODE_MINTER_ALLOWANCE_EXCEEDED; 307 : : } 308 : 8 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); 309 : : } diff --git a/doc/coverage/coverage/src/rules/operation/abstract/index-sort-b.html b/doc/coverage/coverage/src/rules/operation/abstract/index-sort-b.html index cfa13eaf..7121ff30 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract + LCOV - lcov2.info - src/rules/operation/abstract @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 240 - 244 + 246 + 250 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 73 @@ -49,9 +49,9 @@ Branches: - 50 - 54 - 92.6 % + 58 + 58 + 100.0 % @@ -81,25 +81,13 @@ Functions Sort by function coverage Branches Sort by branch coverage - - RuleConditionalTransferLightMultiTokenBase.sol - -
98.9%98.9%
- - 98.9 % - 91 / 92 - 96.4 % - 27 / 28 - 81.0 % - 17 / 21 - RuleConditionalTransferLightApprovalBase.sol -
95.3%95.3%
+
95.5%95.5%
- 95.3 % - 41 / 43 + 95.5 % + 42 / 44 83.3 % 10 / 12 100.0 % @@ -123,11 +111,23 @@
100.0%
100.0 % - 52 / 52 + 54 / 54 100.0 % 16 / 16 100.0 % - 17 / 17 + 19 / 19 + + + RuleConditionalTransferLightMultiTokenBase.sol + +
98.9%98.9%
+ + 98.9 % + 94 / 95 + 96.4 % + 27 / 28 + 100.0 % + 23 / 23 diff --git a/doc/coverage/coverage/src/rules/operation/abstract/index-sort-f.html b/doc/coverage/coverage/src/rules/operation/abstract/index-sort-f.html index 67d2fea8..07288db2 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract + LCOV - lcov2.info - src/rules/operation/abstract @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 240 - 244 + 246 + 250 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 73 @@ -49,9 +49,9 @@ Branches: - 50 - 54 - 92.6 % + 58 + 58 + 100.0 % @@ -84,10 +84,10 @@ RuleConditionalTransferLightApprovalBase.sol -
95.3%95.3%
+
95.5%95.5%
- 95.3 % - 41 / 43 + 95.5 % + 42 / 44 83.3 % 10 / 12 100.0 % @@ -111,11 +111,11 @@
98.9%98.9%
98.9 % - 91 / 92 + 94 / 95 96.4 % 27 / 28 - 81.0 % - 17 / 21 + 100.0 % + 23 / 23 RuleConditionalTransferLightBase.sol @@ -123,11 +123,11 @@
100.0%
100.0 % - 52 / 52 + 54 / 54 100.0 % 16 / 16 100.0 % - 17 / 17 + 19 / 19 diff --git a/doc/coverage/coverage/src/rules/operation/abstract/index-sort-l.html b/doc/coverage/coverage/src/rules/operation/abstract/index-sort-l.html index 4f7d8dfe..b10b31f0 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract + LCOV - lcov2.info - src/rules/operation/abstract @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 240 - 244 + 246 + 250 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 73 @@ -49,9 +49,9 @@ Branches: - 50 - 54 - 92.6 % + 58 + 58 + 100.0 % @@ -84,10 +84,10 @@ RuleConditionalTransferLightApprovalBase.sol -
95.3%95.3%
+
95.5%95.5%
- 95.3 % - 41 / 43 + 95.5 % + 42 / 44 83.3 % 10 / 12 100.0 % @@ -111,11 +111,11 @@
98.9%98.9%
98.9 % - 91 / 92 + 94 / 95 96.4 % 27 / 28 - 81.0 % - 17 / 21 + 100.0 % + 23 / 23 RuleConditionalTransferLightBase.sol @@ -123,11 +123,11 @@
100.0%
100.0 % - 52 / 52 + 54 / 54 100.0 % 16 / 16 100.0 % - 17 / 17 + 19 / 19 diff --git a/doc/coverage/coverage/src/rules/operation/abstract/index.html b/doc/coverage/coverage/src/rules/operation/abstract/index.html index 92e56ceb..e3afeebe 100644 --- a/doc/coverage/coverage/src/rules/operation/abstract/index.html +++ b/doc/coverage/coverage/src/rules/operation/abstract/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation/abstract + LCOV - lcov2.info - src/rules/operation/abstract @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 240 - 244 + 246 + 250 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 73 @@ -49,9 +49,9 @@ Branches: - 50 - 54 - 92.6 % + 58 + 58 + 100.0 % @@ -84,10 +84,10 @@ RuleConditionalTransferLightApprovalBase.sol -
95.3%95.3%
+
95.5%95.5%
- 95.3 % - 41 / 43 + 95.5 % + 42 / 44 83.3 % 10 / 12 100.0 % @@ -99,11 +99,11 @@
100.0%
100.0 % - 52 / 52 + 54 / 54 100.0 % 16 / 16 100.0 % - 17 / 17 + 19 / 19 RuleConditionalTransferLightMultiTokenBase.sol @@ -111,11 +111,11 @@
98.9%98.9%
98.9 % - 91 / 92 + 94 / 95 96.4 % 27 / 28 - 81.0 % - 17 / 21 + 100.0 % + 23 / 23 RuleMintAllowanceBase.sol diff --git a/doc/coverage/coverage/src/rules/operation/index-sort-b.html b/doc/coverage/coverage/src/rules/operation/index-sort-b.html index 8359c442..5a35c1f8 100644 --- a/doc/coverage/coverage/src/rules/operation/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/operation/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation + LCOV - lcov2.info - src/rules/operation @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 47 50 - 94.0 % + 50 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 22 - 86.4 % + 22 + 100.0 % @@ -82,62 +82,62 @@ Branches Sort by branch coverage - RuleConditionalTransferLightMultiTokenOwnable2Step.sol + RuleConditionalTransferLightMultiToken.sol -
75.0%75.0%
+
100.0%
- 75.0 % - 6 / 8 - 33.3 % - 1 / 3 + 100.0 % + 8 / 8 + 100.0 % + 3 / 3 - 0 / 0 - RuleMintAllowanceOwnable2Step.sol + RuleConditionalTransferLight.sol
100.0%
100.0 % - 8 / 8 + 9 / 9 100.0 % 4 / 4 - 0 / 0 - RuleConditionalTransferLightOwnable2Step.sol + RuleMintAllowanceOwnable2Step.sol -
88.9%88.9%
+
100.0%
- 88.9 % - 8 / 9 - 75.0 % - 3 / 4 + 100.0 % + 8 / 8 + 100.0 % + 4 / 4 - 0 / 0 - RuleConditionalTransferLight.sol + RuleConditionalTransferLightMultiTokenOwnable2Step.sol
100.0%
100.0 % - 9 / 9 + 8 / 8 100.0 % - 4 / 4 + 3 / 3 - 0 / 0 - RuleConditionalTransferLightMultiToken.sol + RuleConditionalTransferLightOwnable2Step.sol
100.0%
100.0 % - 8 / 8 + 9 / 9 100.0 % - 3 / 3 + 4 / 4 - 0 / 0 diff --git a/doc/coverage/coverage/src/rules/operation/index-sort-f.html b/doc/coverage/coverage/src/rules/operation/index-sort-f.html index 959a4ade..21da84b6 100644 --- a/doc/coverage/coverage/src/rules/operation/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/operation/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation + LCOV - lcov2.info - src/rules/operation @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 47 50 - 94.0 % + 50 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 22 - 86.4 % + 22 + 100.0 % @@ -82,38 +82,38 @@ Branches Sort by branch coverage - RuleConditionalTransferLightMultiTokenOwnable2Step.sol + RuleConditionalTransferLightMultiToken.sol -
75.0%75.0%
+
100.0%
- 75.0 % - 6 / 8 - 33.3 % - 1 / 3 + 100.0 % + 8 / 8 + 100.0 % + 3 / 3 - 0 / 0 - RuleConditionalTransferLightOwnable2Step.sol + RuleConditionalTransferLightMultiTokenOwnable2Step.sol -
88.9%88.9%
+
100.0%
- 88.9 % - 8 / 9 - 75.0 % - 3 / 4 + 100.0 % + 8 / 8 + 100.0 % + 3 / 3 - 0 / 0 - RuleConditionalTransferLightMultiToken.sol + RuleConditionalTransferLight.sol
100.0%
100.0 % - 8 / 8 + 9 / 9 100.0 % - 3 / 3 + 4 / 4 - 0 / 0 @@ -130,7 +130,7 @@ 0 / 0 - RuleConditionalTransferLight.sol + RuleConditionalTransferLightOwnable2Step.sol
100.0%
diff --git a/doc/coverage/coverage/src/rules/operation/index-sort-l.html b/doc/coverage/coverage/src/rules/operation/index-sort-l.html index 3116ed0d..15c33919 100644 --- a/doc/coverage/coverage/src/rules/operation/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/operation/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation + LCOV - lcov2.info - src/rules/operation @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 47 50 - 94.0 % + 50 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 22 - 86.4 % + 22 + 100.0 % @@ -82,26 +82,14 @@ Branches Sort by branch coverage - RuleConditionalTransferLightMultiTokenOwnable2Step.sol - -
75.0%75.0%
- - 75.0 % - 6 / 8 - 33.3 % - 1 / 3 - - - 0 / 0 - - - RuleConditionalTransferLightOwnable2Step.sol + RuleConditionalTransferLightMultiToken.sol -
88.9%88.9%
+
100.0%
- 88.9 % - 8 / 9 - 75.0 % - 3 / 4 + 100.0 % + 8 / 8 + 100.0 % + 3 / 3 - 0 / 0 @@ -118,7 +106,7 @@ 0 / 0 - RuleConditionalTransferLightMultiToken.sol + RuleConditionalTransferLightMultiTokenOwnable2Step.sol
100.0%
@@ -153,6 +141,18 @@ - 0 / 0 + + RuleConditionalTransferLightOwnable2Step.sol + +
100.0%
+ + 100.0 % + 9 / 9 + 100.0 % + 4 / 4 + - + 0 / 0 +
diff --git a/doc/coverage/coverage/src/rules/operation/index.html b/doc/coverage/coverage/src/rules/operation/index.html index 570fecde..7e211c90 100644 --- a/doc/coverage/coverage/src/rules/operation/index.html +++ b/doc/coverage/coverage/src/rules/operation/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/operation + LCOV - lcov2.info - src/rules/operation @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 47 50 - 94.0 % + 50 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 22 - 86.4 % + 22 + 100.0 % @@ -108,24 +108,24 @@ RuleConditionalTransferLightMultiTokenOwnable2Step.sol -
75.0%75.0%
+
100.0%
- 75.0 % - 6 / 8 - 33.3 % - 1 / 3 + 100.0 % + 8 / 8 + 100.0 % + 3 / 3 - 0 / 0 RuleConditionalTransferLightOwnable2Step.sol -
88.9%88.9%
+
100.0%
- 88.9 % - 8 / 9 - 75.0 % - 3 / 4 + 100.0 % + 9 / 9 + 100.0 % + 4 / 4 - 0 / 0 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.func-sort-c.html new file mode 100644 index 00000000..ee1a1be2 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.func-sort-c.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/RuleAddressSet - AddressSetBatchLib.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:44100.0 %
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
AddressSetBatchLib.removeBatch270
AddressSetBatchLib.addBatch556
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.func.html new file mode 100644 index 00000000..5dcd2dbd --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.func.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/RuleAddressSet - AddressSetBatchLib.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:44100.0 %
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
AddressSetBatchLib.addBatch556
AddressSetBatchLib.removeBatch270
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.gcov.html new file mode 100644 index 00000000..df9e36de --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol.gcov.html @@ -0,0 +1,160 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/RuleAddressSet - AddressSetBatchLib.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:44100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+       5                 :            : 
+       6                 :            : /**
+       7                 :            :  * @title AddressSetBatchLib
+       8                 :            :  * @notice The batch add/remove loops shared by every address-list rule in this library.
+       9                 :            :  * @dev Extracted because the same two loops were written three times and had already drifted: only
+      10                 :            :  * the {RuleAddressSetInternal} copy was covered by a zero-address test (`CLAUDE_ANALYSIS.md` D-1).
+      11                 :            :  * Only the loops live here; single-address `add` / `remove` / `contains` / `length` stay as one-line
+      12                 :            :  * delegations to {EnumerableSet}, where a library would add indirection without removing duplication.
+      13                 :            :  *
+      14                 :            :  * @dev **The zero-address guard is a function-pointer parameter** so each rule keeps its own error
+      15                 :            :  * (`RuleAddressSet_ZeroAddressNotAllowed` vs `RuleERC2980_ZeroAddressNotAllowed`), per the
+      16                 :            :  * one-error-namespace-per-rule convention. Being a required parameter makes it MANDATORY: the call
+      17                 :            :  * does not compile without one. Returning a "zero found" flag instead would make the guard optional
+      18                 :            :  * in practice, and a caller that forgot it would list `address(0)` -- exactly what it prevents. The
+      19                 :            :  * pointer resolves at compile time and the library is `internal`, so this is a jump, not a
+      20                 :            :  * `DELEGATECALL`.
+      21                 :            :  */
+      22                 :            : library AddressSetBatchLib {
+      23                 :            :     using EnumerableSet for EnumerableSet.AddressSet;
+      24                 :            : 
+      25                 :            :     /**
+      26                 :            :      * @notice Adds every address in `addressesToAdd` to `set`, skipping entries already present.
+      27                 :            :      * @dev Duplicates are skipped and counted rather than rejected: an idempotent no-op that the
+      28                 :            :      * caller's batch event still describes truthfully. `address(0)` is NOT skipped -- `guard` is
+      29                 :            :      * invoked for every entry and is expected to revert on it, rejecting the whole batch. Silently
+      30                 :            :      * dropping the sentinel would make the caller's `Add*` event, which echoes the input array,
+      31                 :            :      * report a member that is not in the set.
+      32                 :            :      * @param set The address set to modify.
+      33                 :            :      * @param addressesToAdd The addresses to add.
+      34                 :            :      * @param guard Per-entry validation supplied by the calling rule; reverts with that rule's own
+      35                 :            :      * error. Invoked before the entry is inserted.
+      36                 :            :      * @return added The number of addresses newly inserted.
+      37                 :            :      * @return skipped The number of addresses already present.
+      38                 :            :      */
+      39                 :        556 :     function addBatch(
+      40                 :            :         EnumerableSet.AddressSet storage set,
+      41                 :            :         address[] calldata addressesToAdd,
+      42                 :            :         function(address) internal pure guard
+      43                 :            :     ) internal returns (uint256 added, uint256 skipped) {
+      44                 :        556 :         for (uint256 i = 0; i < addressesToAdd.length; ++i) {
+      45                 :       1628 :             guard(addressesToAdd[i]);
+      46         [ +  + ]:       1622 :             if (set.add(addressesToAdd[i])) {
+      47                 :       1137 :                 added += 1;
+      48                 :            :             } else {
+      49                 :        485 :                 skipped += 1;
+      50                 :            :             }
+      51                 :            :         }
+      52                 :            :     }
+      53                 :            : 
+      54                 :            :     /**
+      55                 :            :      * @notice Removes every address in `addressesToRemove` from `set`, skipping absent entries.
+      56                 :            :      * @dev No guard: removal has no invalid input. Removing an address that is not present is an
+      57                 :            :      * idempotent no-op, counted in `skipped`.
+      58                 :            :      * @param set The address set to modify.
+      59                 :            :      * @param addressesToRemove The addresses to remove.
+      60                 :            :      * @return removed The number of addresses actually removed.
+      61                 :            :      * @return skipped The number of addresses that were not present.
+      62                 :            :      */
+      63                 :        270 :     function removeBatch(EnumerableSet.AddressSet storage set, address[] calldata addressesToRemove)
+      64                 :            :         internal
+      65                 :            :         returns (uint256 removed, uint256 skipped)
+      66                 :            :     {
+      67                 :        270 :         for (uint256 i = 0; i < addressesToRemove.length; ++i) {
+      68         [ +  + ]:        794 :             if (set.remove(addressesToRemove[i])) {
+      69                 :        531 :                 removed += 1;
+      70                 :            :             } else {
+      71                 :        263 :                 skipped += 1;
+      72                 :            :             }
+      73                 :            :         }
+      74                 :            :     }
+      75                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func-sort-c.html index 1d6c5270..b48739cd 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: + 33 35 - 37 - 94.6 % + 94.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 13 @@ -69,64 +69,64 @@ Hit count Sort by hit count - RuleAddressSet._authorizeAddressListAdd + RuleAddressSet._authorizeAddressListAdd 0 - RuleAddressSet._authorizeAddressListRemove + RuleAddressSet._authorizeAddressListRemove 0 - RuleAddressSet.contains + RuleAddressSet.contains 4 - RuleAddressSet._msgData - 6 + RuleAddressSet._msgData + 8 - RuleAddressSet.onlyAddressListRemove - 12 + RuleAddressSet.onlyAddressListRemove + 18 - RuleAddressSet.removeAddress - 12 + RuleAddressSet.removeAddress + 18 - RuleAddressSet.isAddressListed - 79 + RuleAddressSet.isAddressListed + 89 - RuleAddressSet.addAddress - 132 + RuleAddressSet.areAddressesListed + 179 - RuleAddressSet.areAddressesListed - 155 + RuleAddressSet.removeAddresses + 262 - RuleAddressSet.removeAddresses - 260 + RuleAddressSet.addAddress + 420 - RuleAddressSet.addAddresses - 278 + RuleAddressSet.addAddresses + 539 - RuleAddressSet.onlyAddressListAdd - 278 + RuleAddressSet.onlyAddressListAdd + 539 - RuleAddressSet.listedAddressCount - 543 + RuleAddressSet.listedAddressCount + 548 - RuleAddressSet._msgSender - 994 + RuleAddressSet._msgSender + 1691 - RuleAddressSet._contextSuffixLength - 1002 + RuleAddressSet._contextSuffixLength + 1703
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func.html index da92974e..78436742 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: + 33 35 - 37 - 94.6 % + 94.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 13 @@ -69,64 +69,64 @@ Hit count Sort by hit count - RuleAddressSet._authorizeAddressListAdd + RuleAddressSet._authorizeAddressListAdd 0 - RuleAddressSet._authorizeAddressListRemove + RuleAddressSet._authorizeAddressListRemove 0 - RuleAddressSet._contextSuffixLength - 1002 + RuleAddressSet._contextSuffixLength + 1703 - RuleAddressSet._msgData - 6 + RuleAddressSet._msgData + 8 - RuleAddressSet._msgSender - 994 + RuleAddressSet._msgSender + 1691 - RuleAddressSet.addAddress - 132 + RuleAddressSet.addAddress + 420 - RuleAddressSet.addAddresses - 278 + RuleAddressSet.addAddresses + 539 - RuleAddressSet.areAddressesListed - 155 + RuleAddressSet.areAddressesListed + 179 - RuleAddressSet.contains + RuleAddressSet.contains 4 - RuleAddressSet.isAddressListed - 79 + RuleAddressSet.isAddressListed + 89 - RuleAddressSet.listedAddressCount - 543 + RuleAddressSet.listedAddressCount + 548 - RuleAddressSet.onlyAddressListAdd - 278 + RuleAddressSet.onlyAddressListAdd + 539 - RuleAddressSet.onlyAddressListRemove - 12 + RuleAddressSet.onlyAddressListRemove + 18 - RuleAddressSet.removeAddress - 12 + RuleAddressSet.removeAddress + 18 - RuleAddressSet.removeAddresses - 260 + RuleAddressSet.removeAddresses + 262
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.gcov.html index 82998c9e..77a397b0 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: + 33 35 - 37 - 94.6 % + 94.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 13 @@ -75,177 +75,180 @@ 4 : : import {MetaTxModuleStandalone, ERC2771Context} from "../../../../modules/MetaTxModuleStandalone.sol"; 5 : : import {RuleAddressSetInternal} from "./RuleAddressSetInternal.sol"; 6 : : import {RuleAddressSetInvariantStorage} from "./invariantStorage/RuleAddressSetInvariantStorage.sol"; - 7 : : /* ==== Interfaces === */ - 8 : : import {IIdentityRegistryContains} from "../../../interfaces/IIdentityRegistry.sol"; - 9 : : import {IAddressList} from "../../../interfaces/IAddressList.sol"; - 10 : : /** - 11 : : * @title Rule Address Set - 12 : : * @notice Manages a permissioned set of addresses related to rule logic. - 13 : : * @dev - 14 : : * - Provides controlled functions for adding and removing addresses. - 15 : : * - Integrates `AccessControl` for role-based access. - 16 : : * - Supports gasless transactions via ERC-2771 meta-transactions. - 17 : : * - Extends internal logic defined in {RuleAddressSetInternal}. - 18 : : */ - 19 : : - 20 : : abstract contract RuleAddressSet is - 21 : : MetaTxModuleStandalone, - 22 : : RuleAddressSetInvariantStorage, - 23 : : RuleAddressSetInternal, - 24 : : IAddressList - 25 : : { - 26 : : /*////////////////////////////////////////////////////////////// - 27 : : CONSTRUCTOR - 28 : : //////////////////////////////////////////////////////////////*/ - 29 : : - 30 : : /** - 31 : : * @notice Initializes the RuleAddressSet contract. - 32 : : * @param forwarderIrrevocable Address of the ERC2771 forwarder (for meta-transactions). - 33 : : */ - 34 : : constructor(address forwarderIrrevocable) MetaTxModuleStandalone(forwarderIrrevocable) {} - 35 : : - 36 : : /*////////////////////////////////////////////////////////////// - 37 : : ACCESS CONTROL - 38 : : //////////////////////////////////////////////////////////////*/ - 39 : : - 40 : 278 : modifier onlyAddressListAdd() { - 41 : 278 : _authorizeAddressListAdd(); - 42 : : _; - 43 : : } - 44 : : - 45 : 12 : modifier onlyAddressListRemove() { - 46 : 12 : _authorizeAddressListRemove(); - 47 : : _; - 48 : : } - 49 : : - 50 : : /*////////////////////////////////////////////////////////////// - 51 : : PUBLIC FUNCTIONS - 52 : : //////////////////////////////////////////////////////////////*/ - 53 : : - 54 : : /** - 55 : : * @notice Adds multiple addresses to the set. - 56 : : * @dev - 57 : : * - Does not revert if an address is already listed. - 58 : : * - Accessible only by accounts with the `ADDRESS_LIST_ADD_ROLE`. - 59 : : * @param targetAddresses Array of addresses to be added. - 60 : : */ - 61 : 278 : function addAddresses(address[] calldata targetAddresses) public onlyAddressListAdd { - 62 : 277 : _addAddresses(targetAddresses); - 63 : 275 : emit AddAddresses(targetAddresses); - 64 : : } - 65 : : - 66 : : /** - 67 : : * @notice Removes multiple addresses from the set. - 68 : : * @dev - 69 : : * - Does not revert if an address is not listed. - 70 : : * - Accessible only by accounts with the `ADDRESS_LIST_REMOVE_ROLE`. - 71 : : * @param targetAddresses Array of addresses to remove. - 72 : : */ - 73 : 260 : function removeAddresses(address[] calldata targetAddresses) public onlyAddressListRemove { - 74 : 259 : _removeAddresses(targetAddresses); - 75 : 259 : emit RemoveAddresses(targetAddresses); - 76 : : } - 77 : : - 78 : : /** - 79 : : * @notice Adds a single address to the set. - 80 : : * @dev - 81 : : * - Reverts if the address is already listed. - 82 : : * - Accessible only by accounts with the `ADDRESS_LIST_ADD_ROLE`. - 83 : : * @param targetAddress The address to be added. - 84 : : */ - 85 : 132 : function addAddress(address targetAddress) public onlyAddressListAdd { - 86 [ + + ]: 127 : require(targetAddress != address(0), RuleAddressSet_ZeroAddressNotAllowed()); - 87 [ + + ]: 125 : require(!_isAddressListed(targetAddress), RuleAddressSet_AddressAlreadyListed()); - 88 : 124 : _addAddress(targetAddress); - 89 : 124 : emit AddAddress(targetAddress); - 90 : : } - 91 : : - 92 : : /** - 93 : : * @notice Removes a single address from the set. - 94 : : * @dev - 95 : : * - Reverts if the address is not listed. - 96 : : * - Accessible only by accounts with the `ADDRESS_LIST_REMOVE_ROLE`. - 97 : : * @param targetAddress The address to be removed. - 98 : : */ - 99 : 12 : function removeAddress(address targetAddress) public onlyAddressListRemove { - 100 [ + + ]: 7 : require(_isAddressListed(targetAddress), RuleAddressSet_AddressNotFound()); - 101 : 6 : _removeAddress(targetAddress); - 102 : 6 : emit RemoveAddress(targetAddress); - 103 : : } - 104 : : - 105 : : /** - 106 : : * @notice Returns the total number of currently listed addresses. - 107 : : * @return count The number of listed addresses. - 108 : : */ - 109 : 543 : function listedAddressCount() public view returns (uint256 count) { - 110 : 543 : count = _listedAddressCount(); - 111 : : } - 112 : : - 113 : : /** - 114 : : * @notice Checks whether a specific address is currently listed. - 115 : : * @param targetAddress The address to check. - 116 : : * @return isListed True if listed, false otherwise. - 117 : : */ - 118 : 4 : function contains(address targetAddress) public view override(IIdentityRegistryContains) returns (bool isListed) { - 119 : 4 : isListed = _isAddressListed(targetAddress); - 120 : : } - 121 : : - 122 : : /** - 123 : : * @notice Checks whether a specific address is currently listed. - 124 : : * @param targetAddress The address to check. - 125 : : * @return isListed True if listed, false otherwise. - 126 : : */ - 127 : 79 : function isAddressListed(address targetAddress) public view returns (bool isListed) { - 128 : 577 : isListed = _isAddressListed(targetAddress); - 129 : : } - 130 : : - 131 : : /** - 132 : : * @notice Checks multiple addresses in a single call. - 133 : : * @param targetAddresses Array of addresses to check. - 134 : : * @return results Array of booleans corresponding to listing status. - 135 : : */ - 136 : 155 : function areAddressesListed(address[] memory targetAddresses) public view returns (bool[] memory results) { - 137 : 155 : results = new bool[](targetAddresses.length); - 138 : 155 : for (uint256 i = 0; i < targetAddresses.length; ++i) { - 139 : 345 : results[i] = _isAddressListed(targetAddresses[i]); - 140 : : } - 141 : : } - 142 : : - 143 : : /*////////////////////////////////////////////////////////////// - 144 : : INTERNAL FUNCTIONS - 145 : : //////////////////////////////////////////////////////////////*/ - 146 : : - 147 : : /** - 148 : : * @notice Authorizes the caller to add addresses to the set; reverts if unauthorized. - 149 : : */ - 150 : 0 : function _authorizeAddressListAdd() internal view virtual; - 151 : : - 152 : : /** - 153 : : * @notice Authorizes the caller to remove addresses from the set; reverts if unauthorized. - 154 : : */ - 155 : 0 : function _authorizeAddressListRemove() internal view virtual; - 156 : : - 157 : : /** - 158 : : * @inheritdoc ERC2771Context - 159 : : */ - 160 : 994 : function _msgSender() internal view virtual override(ERC2771Context) returns (address sender) { - 161 : 994 : return ERC2771Context._msgSender(); - 162 : : } - 163 : : - 164 : : /** - 165 : : * @inheritdoc ERC2771Context - 166 : : */ - 167 : 6 : function _msgData() internal view virtual override(ERC2771Context) returns (bytes calldata) { - 168 : 6 : return ERC2771Context._msgData(); - 169 : : } - 170 : : - 171 : : /** - 172 : : * @inheritdoc ERC2771Context - 173 : : */ - 174 : 1002 : function _contextSuffixLength() internal view virtual override(ERC2771Context) returns (uint256) { - 175 : 1002 : return ERC2771Context._contextSuffixLength(); - 176 : : } - 177 : : } + 7 : : import {RuleAddressSetRolesStorage} from "./invariantStorage/RuleAddressSetRolesStorage.sol"; + 8 : : /* ==== Interfaces === */ + 9 : : import {IIdentityRegistryContains} from "../../../interfaces/IIdentityRegistry.sol"; + 10 : : import {IAddressList} from "../../../interfaces/IAddressList.sol"; + 11 : : /** + 12 : : * @title Rule Address Set + 13 : : * @notice Manages a permissioned set of addresses related to rule logic. + 14 : : * @dev + 15 : : * - Provides controlled functions for adding and removing addresses. + 16 : : * - Integrates `AccessControl` for role-based access. + 17 : : * - Supports gasless transactions via ERC-2771 meta-transactions. + 18 : : * - Extends internal logic defined in {RuleAddressSetInternal}. + 19 : : */ + 20 : : + 21 : : abstract contract RuleAddressSet is + 22 : : MetaTxModuleStandalone, + 23 : : RuleAddressSetInvariantStorage, + 24 : : RuleAddressSetRolesStorage, + 25 : : RuleAddressSetInternal, + 26 : : IAddressList + 27 : : { + 28 : : /*////////////////////////////////////////////////////////////// + 29 : : CONSTRUCTOR + 30 : : //////////////////////////////////////////////////////////////*/ + 31 : : + 32 : : /** + 33 : : * @notice Initializes the RuleAddressSet contract. + 34 : : * @param forwarderIrrevocable Address of the ERC2771 forwarder (for meta-transactions). + 35 : : */ + 36 : : constructor(address forwarderIrrevocable) MetaTxModuleStandalone(forwarderIrrevocable) {} + 37 : : + 38 : : /*////////////////////////////////////////////////////////////// + 39 : : ACCESS CONTROL + 40 : : //////////////////////////////////////////////////////////////*/ + 41 : : + 42 : 539 : modifier onlyAddressListAdd() { + 43 : 539 : _authorizeAddressListAdd(); + 44 : : _; + 45 : : } + 46 : : + 47 : 18 : modifier onlyAddressListRemove() { + 48 : 18 : _authorizeAddressListRemove(); + 49 : : _; + 50 : : } + 51 : : + 52 : : /*////////////////////////////////////////////////////////////// + 53 : : PUBLIC FUNCTIONS + 54 : : //////////////////////////////////////////////////////////////*/ + 55 : : + 56 : : /** + 57 : : * @notice Adds multiple addresses to the set. + 58 : : * @dev + 59 : : * - Does not revert if an address is already listed; duplicates are skipped. + 60 : : * - REVERTS on `address(0)`, rejecting the WHOLE batch. The mint/burn sentinel is never a list + 61 : : * member, and skipping it would make the {AddAddresses} event -- which echoes the input array + 62 : : * -- name it as one. Filter the input before submitting a large batch. + 63 : : * - Accessible only by accounts with the `ADDRESS_LIST_ADD_ROLE`. + 64 : : * @param targetAddresses Array of addresses to be added. + 65 : : */ + 66 : 539 : function addAddresses(address[] calldata targetAddresses) public virtual onlyAddressListAdd { + 67 : 538 : (uint256 added, uint256 skipped) = _addAddresses(targetAddresses); + 68 : 536 : emit AddAddresses(targetAddresses, added, skipped); + 69 : : } + 70 : : + 71 : : /** + 72 : : * @notice Removes multiple addresses from the set. + 73 : : * @dev + 74 : : * - Does not revert if an address is not listed. + 75 : : * - Accessible only by accounts with the `ADDRESS_LIST_REMOVE_ROLE`. + 76 : : * @param targetAddresses Array of addresses to remove. + 77 : : */ + 78 : 262 : function removeAddresses(address[] calldata targetAddresses) public virtual onlyAddressListRemove { + 79 : 261 : (uint256 removed, uint256 skipped) = _removeAddresses(targetAddresses); + 80 : 261 : emit RemoveAddresses(targetAddresses, removed, skipped); + 81 : : } + 82 : : + 83 : : /** + 84 : : * @notice Adds a single address to the set. + 85 : : * @dev + 86 : : * - Reverts if the address is already listed. + 87 : : * - Accessible only by accounts with the `ADDRESS_LIST_ADD_ROLE`. + 88 : : * @param targetAddress The address to be added. + 89 : : */ + 90 : 420 : function addAddress(address targetAddress) public virtual onlyAddressListAdd { + 91 [ + + ]: 413 : require(targetAddress != address(0), RuleAddressSet_ZeroAddressNotAllowed()); + 92 [ + + ]: 410 : require(_addAddress(targetAddress), RuleAddressSet_AddressAlreadyListed()); + 93 : 409 : emit AddAddress(targetAddress); + 94 : : } + 95 : : + 96 : : /** + 97 : : * @notice Removes a single address from the set. + 98 : : * @dev + 99 : : * - Reverts if the address is not listed. + 100 : : * - Accessible only by accounts with the `ADDRESS_LIST_REMOVE_ROLE`. + 101 : : * @param targetAddress The address to be removed. + 102 : : */ + 103 : 18 : function removeAddress(address targetAddress) public virtual onlyAddressListRemove { + 104 [ + + ]: 11 : require(_removeAddress(targetAddress), RuleAddressSet_AddressNotFound()); + 105 : 10 : emit RemoveAddress(targetAddress); + 106 : : } + 107 : : + 108 : : /** + 109 : : * @notice Returns the total number of currently listed addresses. + 110 : : * @return count The number of listed addresses. + 111 : : */ + 112 : 548 : function listedAddressCount() public view returns (uint256 count) { + 113 : 548 : count = _listedAddressCount(); + 114 : : } + 115 : : + 116 : : /** + 117 : : * @notice Checks whether a specific address is currently listed. + 118 : : * @param targetAddress The address to check. + 119 : : * @return isListed True if listed, false otherwise. + 120 : : */ + 121 : 4 : function contains(address targetAddress) public view override(IIdentityRegistryContains) returns (bool isListed) { + 122 : 4 : isListed = _isAddressListed(targetAddress); + 123 : : } + 124 : : + 125 : : /** + 126 : : * @notice Checks whether a specific address is currently listed. + 127 : : * @param targetAddress The address to check. + 128 : : * @return isListed True if listed, false otherwise. + 129 : : */ + 130 : 89 : function isAddressListed(address targetAddress) public view returns (bool isListed) { + 131 : 876 : isListed = _isAddressListed(targetAddress); + 132 : : } + 133 : : + 134 : : /** + 135 : : * @notice Checks multiple addresses in a single call. + 136 : : * @param targetAddresses Array of addresses to check. + 137 : : * @return results Array of booleans corresponding to listing status. + 138 : : */ + 139 : 179 : function areAddressesListed(address[] memory targetAddresses) public view returns (bool[] memory results) { + 140 : 179 : results = new bool[](targetAddresses.length); + 141 : 179 : for (uint256 i = 0; i < targetAddresses.length; ++i) { + 142 : 395 : results[i] = _isAddressListed(targetAddresses[i]); + 143 : : } + 144 : : } + 145 : : + 146 : : /*////////////////////////////////////////////////////////////// + 147 : : INTERNAL FUNCTIONS + 148 : : //////////////////////////////////////////////////////////////*/ + 149 : : + 150 : : /** + 151 : : * @notice Authorizes the caller to add addresses to the set; reverts if unauthorized. + 152 : : */ + 153 : 0 : function _authorizeAddressListAdd() internal view virtual; + 154 : : + 155 : : /** + 156 : : * @notice Authorizes the caller to remove addresses from the set; reverts if unauthorized. + 157 : : */ + 158 : 0 : function _authorizeAddressListRemove() internal view virtual; + 159 : : + 160 : : /** + 161 : : * @inheritdoc ERC2771Context + 162 : : */ + 163 : 1691 : function _msgSender() internal view virtual override(ERC2771Context) returns (address sender) { + 164 : 1691 : return ERC2771Context._msgSender(); + 165 : : } + 166 : : + 167 : : /** + 168 : : * @inheritdoc ERC2771Context + 169 : : */ + 170 : 8 : function _msgData() internal view virtual override(ERC2771Context) returns (bytes calldata) { + 171 : 8 : return ERC2771Context._msgData(); + 172 : : } + 173 : : + 174 : : /** + 175 : : * @inheritdoc ERC2771Context + 176 : : */ + 177 : 1703 : function _contextSuffixLength() internal view virtual override(ERC2771Context) returns (uint256) { + 178 : 1703 : return ERC2771Context._contextSuffixLength(); + 179 : : } + 180 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func-sort-c.html index 575a4c8a..68afaadb 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol - functions @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 19 - 19 + 14 + 14 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 6 - 6 + 7 + 7 100.0 % @@ -49,8 +49,8 @@ Branches: - 6 - 6 + 2 + 2 100.0 % @@ -69,28 +69,32 @@ Hit count Sort by hit count - RuleAddressSetInternal._removeAddress - 6 + RuleAddressSetInternal._removeAddress + 24 - RuleAddressSetInternal._addAddress - 124 + RuleAddressSetInternal._removeAddresses + 263 - RuleAddressSetInternal._removeAddresses - 259 + RuleAddressSetInternal._addAddress + 511 - RuleAddressSetInternal._addAddresses - 277 + RuleAddressSetInternal._addAddresses + 543 - RuleAddressSetInternal._listedAddressCount - 543 + RuleAddressSetInternal._listedAddressCount + 557 + + + RuleAddressSetInternal._isAddressListed + 1467 - RuleAddressSetInternal._isAddressListed - 1093 + RuleAddressSetInternal._requireNotZeroAddress + 1602
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func.html index 71364b69..a14cf775 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol - functions @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 19 - 19 + 14 + 14 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 6 - 6 + 7 + 7 100.0 % @@ -49,8 +49,8 @@ Branches: - 6 - 6 + 2 + 2 100.0 % @@ -69,28 +69,32 @@ Hit count Sort by hit count - RuleAddressSetInternal._addAddress - 124 + RuleAddressSetInternal._addAddress + 511 - RuleAddressSetInternal._addAddresses - 277 + RuleAddressSetInternal._addAddresses + 543 - RuleAddressSetInternal._isAddressListed - 1093 + RuleAddressSetInternal._isAddressListed + 1467 - RuleAddressSetInternal._listedAddressCount - 543 + RuleAddressSetInternal._listedAddressCount + 557 + + + RuleAddressSetInternal._removeAddress + 24 - RuleAddressSetInternal._removeAddress - 6 + RuleAddressSetInternal._removeAddresses + 263 - RuleAddressSetInternal._removeAddresses - 259 + RuleAddressSetInternal._requireNotZeroAddress + 1602
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.gcov.html index 05cae7be..749c4557 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 19 - 19 + 14 + 14 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 6 - 6 + 7 + 7 100.0 % @@ -49,8 +49,8 @@ Branches: - 6 - 6 + 2 + 2 100.0 % @@ -74,113 +74,124 @@ 3 : : 4 : : /* ==== OpenZeppelin === */ 5 : : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - 6 : : import {RuleAddressSetInvariantStorage} from "./invariantStorage/RuleAddressSetInvariantStorage.sol"; - 7 : : - 8 : : /** - 9 : : * @title Rule Address Set (Internal) - 10 : : * @notice Internal utility for managing a set of rule-related addresses. - 11 : : * @dev - 12 : : * - Uses OpenZeppelin's EnumerableSet for efficient enumeration. - 13 : : * - Designed for internal inheritance and logic composition. - 14 : : * - Batch operations do not revert when individual entries are invalid. - 15 : : */ - 16 : : abstract contract RuleAddressSetInternal is RuleAddressSetInvariantStorage { - 17 : : using EnumerableSet for EnumerableSet.AddressSet; - 18 : : - 19 : : /*////////////////////////////////////////////////////////////// - 20 : : STATE VARIABLES - 21 : : //////////////////////////////////////////////////////////////*/ - 22 : : - 23 : : /** - 24 : : * @dev Storage for all listed addresses. - 25 : : */ - 26 : : EnumerableSet.AddressSet private _listedAddresses; - 27 : : - 28 : : /*////////////////////////////////////////////////////////////// - 29 : : INTERNAL FUNCTIONS - 30 : : //////////////////////////////////////////////////////////////*/ - 31 : : - 32 : : /** - 33 : : * @notice Adds multiple addresses to the set. - 34 : : * @dev - 35 : : * - Does not revert if an address is already listed. - 36 : : * - Skips existing entries silently. - 37 : : * @param addressesToAdd The array of addresses to add. - 38 : : * @return added The number of newly added addresses. - 39 : : * @return skipped The number of addresses that were already listed. - 40 : : */ - 41 : 277 : function _addAddresses(address[] calldata addressesToAdd) internal returns (uint256 added, uint256 skipped) { - 42 : 277 : for (uint256 i = 0; i < addressesToAdd.length; ++i) { - 43 : : // The zero address is the mint/burn sentinel, never a participant. It is REJECTED - 44 : : // rather than skipped: the batch convention skips *duplicates* (an idempotent no-op that - 45 : : // the emitted event still describes truthfully), but silently dropping address(0) would - 46 : : // make `AddAddresses` report a member that is not in the set — re-polluting the very - 47 : : // off-chain view this guard exists to keep clean. Mint/burn is governed by - 48 : : // allowMint/allowBurn, never by list membership. - 49 [ + + ]: 813 : require(addressesToAdd[i] != address(0), RuleAddressSet_ZeroAddressNotAllowed()); - 50 [ + + ]: 811 : if (_listedAddresses.add(addressesToAdd[i])) { - 51 : 551 : added += 1; - 52 : : } else { - 53 : 260 : skipped += 1; - 54 : : } - 55 : : } - 56 : : } - 57 : : - 58 : : /** - 59 : : * @notice Removes multiple addresses from the set. - 60 : : * @dev - 61 : : * - Does not revert if an address is not found. - 62 : : * - Skips non-existing entries silently. - 63 : : * @param addressesToRemove The array of addresses to remove. - 64 : : * @return removed The number of addresses removed. - 65 : : * @return skipped The number of addresses that were not listed. - 66 : : */ - 67 : 259 : function _removeAddresses(address[] calldata addressesToRemove) - 68 : : internal - 69 : : returns (uint256 removed, uint256 skipped) - 70 : : { - 71 : 259 : for (uint256 i = 0; i < addressesToRemove.length; ++i) { - 72 [ + + ]: 775 : if (_listedAddresses.remove(addressesToRemove[i])) { - 73 : 518 : removed += 1; - 74 : : } else { - 75 : 257 : skipped += 1; - 76 : : } - 77 : : } - 78 : : } - 79 : : - 80 : : /** - 81 : : * @notice Adds a single address to the set. - 82 : : * @param targetAddress The address to add. - 83 : : */ - 84 : 124 : function _addAddress(address targetAddress) internal virtual { - 85 : 124 : _listedAddresses.add(targetAddress); - 86 : : } - 87 : : - 88 : : /** - 89 : : * @notice Removes a single address from the set. - 90 : : * @param targetAddress The address to remove. - 91 : : */ - 92 : 6 : function _removeAddress(address targetAddress) internal virtual { - 93 : 6 : _listedAddresses.remove(targetAddress); - 94 : : } - 95 : : - 96 : : /** - 97 : : * @notice Returns the total number of listed addresses. - 98 : : * @return count The number of listed addresses. - 99 : : */ - 100 : 543 : function _listedAddressCount() internal view virtual returns (uint256 count) { - 101 : 543 : count = _listedAddresses.length(); - 102 : : } - 103 : : - 104 : : /** - 105 : : * @notice Checks if an address is listed. - 106 : : * @param targetAddress The address to check. - 107 : : * @return isListed True if the address is listed, false otherwise. - 108 : : */ - 109 : 1093 : function _isAddressListed(address targetAddress) internal view virtual returns (bool isListed) { - 110 : 1093 : isListed = _listedAddresses.contains(targetAddress); - 111 : : } - 112 : : } + 6 : : import {AddressSetBatchLib} from "./AddressSetBatchLib.sol"; + 7 : : import {RuleAddressSetInvariantStorage} from "./invariantStorage/RuleAddressSetInvariantStorage.sol"; + 8 : : + 9 : : /** + 10 : : * @title Rule Address Set (Internal) + 11 : : * @notice Internal utility for managing a set of rule-related addresses. + 12 : : * @dev + 13 : : * - Uses OpenZeppelin's EnumerableSet for efficient enumeration. + 14 : : * - Designed for internal inheritance and logic composition. + 15 : : * - Batch operations do not revert when individual entries are invalid. + 16 : : */ + 17 : : abstract contract RuleAddressSetInternal is RuleAddressSetInvariantStorage { + 18 : : using EnumerableSet for EnumerableSet.AddressSet; + 19 : : using AddressSetBatchLib for EnumerableSet.AddressSet; + 20 : : + 21 : : /*////////////////////////////////////////////////////////////// + 22 : : STATE VARIABLES + 23 : : //////////////////////////////////////////////////////////////*/ + 24 : : + 25 : : /** + 26 : : * @dev Storage for all listed addresses. + 27 : : */ + 28 : : EnumerableSet.AddressSet private _listedAddresses; + 29 : : + 30 : : /*////////////////////////////////////////////////////////////// + 31 : : INTERNAL FUNCTIONS + 32 : : //////////////////////////////////////////////////////////////*/ + 33 : : + 34 : : /** + 35 : : * @notice Adds multiple addresses to the set. + 36 : : * @dev + 37 : : * - Does not revert if an address is already listed. + 38 : : * - Skips existing entries silently. + 39 : : * - REVERTS on `address(0)`, rejecting the whole batch; see the inline comment below. + 40 : : * @param addressesToAdd The array of addresses to add. + 41 : : * @return added The number of newly added addresses. + 42 : : * @return skipped The number of addresses that were already listed. + 43 : : */ + 44 : 543 : function _addAddresses(address[] calldata addressesToAdd) + 45 : : internal + 46 : : virtual + 47 : : returns (uint256 added, uint256 skipped) + 48 : : { + 49 : 543 : return _listedAddresses.addBatch(addressesToAdd, _requireNotZeroAddress); + 50 : : } + 51 : : + 52 : : /** + 53 : : * @notice Per-entry guard for {_addAddresses}; reverts on the zero address. + 54 : : * @dev The zero address is the mint/burn sentinel, never a participant. It is REJECTED rather + 55 : : * than skipped: the batch convention skips *duplicates* (an idempotent no-op that the emitted + 56 : : * event still describes truthfully), but silently dropping address(0) would make `AddAddresses` + 57 : : * report a member that is not in the set — re-polluting the very off-chain view this guard + 58 : : * exists to keep clean. Mint/burn is governed by allowMint/allowBurn, never by list membership. + 59 : : * + 60 : : * Passed to {AddressSetBatchLib.addBatch} as a function pointer so the shared loop can reject + 61 : : * the sentinel with THIS rule's error rather than a generic one. + 62 : : * @param targetAddress The candidate address. + 63 : : */ + 64 : 1602 : function _requireNotZeroAddress(address targetAddress) internal pure virtual { + 65 [ + + ]: 1602 : require(targetAddress != address(0), RuleAddressSet_ZeroAddressNotAllowed()); + 66 : : } + 67 : : + 68 : : /** + 69 : : * @notice Removes multiple addresses from the set. + 70 : : * @dev + 71 : : * - Does not revert if an address is not found. + 72 : : * - Skips non-existing entries silently. + 73 : : * @param addressesToRemove The array of addresses to remove. + 74 : : * @return removed The number of addresses removed. + 75 : : * @return skipped The number of addresses that were not listed. + 76 : : */ + 77 : 263 : function _removeAddresses(address[] calldata addressesToRemove) + 78 : : internal + 79 : : virtual + 80 : : returns (uint256 removed, uint256 skipped) + 81 : : { + 82 : 263 : return _listedAddresses.removeBatch(addressesToRemove); + 83 : : } + 84 : : + 85 : : /** + 86 : : * @notice Adds a single address to the set. + 87 : : * @dev Forwards {EnumerableSet}'s "did this change anything" result so the caller can reject a + 88 : : * duplicate without a second lookup: the membership test the caller would otherwise perform is + 89 : : * the same one `add` already does internally (`CLAUDE_ANALYSIS.md` B-4). + 90 : : * @param targetAddress The address to add. + 91 : : * @return True when the address was not already listed. + 92 : : */ + 93 : 511 : function _addAddress(address targetAddress) internal virtual returns (bool) { + 94 : 511 : return _listedAddresses.add(targetAddress); + 95 : : } + 96 : : + 97 : : /** + 98 : : * @notice Removes a single address from the set. + 99 : : * @dev Forwards {EnumerableSet}'s result; see {_addAddress}. + 100 : : * @param targetAddress The address to remove. + 101 : : * @return True when the address was listed and has been removed. + 102 : : */ + 103 : 24 : function _removeAddress(address targetAddress) internal virtual returns (bool) { + 104 : 24 : return _listedAddresses.remove(targetAddress); + 105 : : } + 106 : : + 107 : : /** + 108 : : * @notice Returns the total number of listed addresses. + 109 : : * @return count The number of listed addresses. + 110 : : */ + 111 : 557 : function _listedAddressCount() internal view virtual returns (uint256 count) { + 112 : 557 : count = _listedAddresses.length(); + 113 : : } + 114 : : + 115 : : /** + 116 : : * @notice Checks if an address is listed. + 117 : : * @param targetAddress The address to check. + 118 : : * @return isListed True if the address is listed, false otherwise. + 119 : : */ + 120 : 1467 : function _isAddressListed(address targetAddress) internal view virtual returns (bool isListed) { + 121 : 1467 : isListed = _listedAddresses.contains(targetAddress); + 122 : : } + 123 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-b.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-b.html index d6eb2834..b4b583e1 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 54 - 56 - 96.4 % + 58 + 60 + 96.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 - 21 - 90.5 % + 22 + 24 + 91.7 % @@ -87,19 +87,31 @@
100.0%
100.0 % - 19 / 19 + 14 / 14 100.0 % - 6 / 6 + 7 / 7 100.0 % - 6 / 6 + 2 / 2 + + + AddressSetBatchLib.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 2 / 2 + 100.0 % + 4 / 4 RuleAddressSet.sol -
94.6%94.6%
+
94.3%94.3%
- 94.6 % - 35 / 37 + 94.3 % + 33 / 35 86.7 % 13 / 15 100.0 % diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-f.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-f.html index ab1542bf..f51e0d61 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 54 - 56 - 96.4 % + 58 + 60 + 96.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 - 21 - 90.5 % + 22 + 24 + 91.7 % @@ -84,26 +84,38 @@ RuleAddressSet.sol -
94.6%94.6%
+
94.3%94.3%
- 94.6 % - 35 / 37 + 94.3 % + 33 / 35 86.7 % 13 / 15 100.0 % 6 / 6 + + AddressSetBatchLib.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 2 / 2 + 100.0 % + 4 / 4 + RuleAddressSetInternal.sol
100.0%
100.0 % - 19 / 19 + 14 / 14 100.0 % - 6 / 6 + 7 / 7 100.0 % - 6 / 6 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-l.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-l.html index 383dd18a..2db6a7df 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 54 - 56 - 96.4 % + 58 + 60 + 96.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 - 21 - 90.5 % + 22 + 24 + 91.7 % @@ -84,26 +84,38 @@ RuleAddressSet.sol -
94.6%94.6%
+
94.3%94.3%
- 94.6 % - 35 / 37 + 94.3 % + 33 / 35 86.7 % 13 / 15 100.0 % 6 / 6 + + AddressSetBatchLib.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 2 / 2 + 100.0 % + 4 / 4 + RuleAddressSetInternal.sol
100.0%
100.0 % - 19 / 19 + 14 / 14 100.0 % - 6 / 6 + 7 / 7 100.0 % - 6 / 6 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index.html index 85e20769..8f8b1539 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleAddressSet/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleAddressSet + LCOV - lcov2.info - src/rules/validation/abstract/RuleAddressSet @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 54 - 56 - 96.4 % + 58 + 60 + 96.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 19 - 21 - 90.5 % + 22 + 24 + 91.7 % @@ -81,13 +81,25 @@ Functions Sort by function coverage Branches Sort by branch coverage + + AddressSetBatchLib.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 2 / 2 + 100.0 % + 4 / 4 + RuleAddressSet.sol -
94.6%94.6%
+
94.3%94.3%
- 94.6 % - 35 / 37 + 94.3 % + 33 / 35 86.7 % 13 / 15 100.0 % @@ -99,11 +111,11 @@
100.0%
100.0 % - 19 / 19 + 14 / 14 100.0 % - 6 / 6 + 7 / 7 100.0 % - 6 / 6 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func-sort-c.html index 56fb628c..225482f5 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol - functions @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -69,52 +69,56 @@ Hit count Sort by hit count - RuleERC2980Internal._removeFrozenlistAddresses - 2 - - - RuleERC2980Internal._removeWhitelistAddresses + RuleERC2980Internal._removeFrozenlistAddresses 3 - RuleERC2980Internal._addFrozenlistAddresses + RuleERC2980Internal._frozenlistCount 4 - RuleERC2980Internal._addWhitelistAddresses + RuleERC2980Internal._removeWhitelistAddresses 4 - RuleERC2980Internal._frozenlistCount - 4 + RuleERC2980Internal._removeFrozenlistAddress + 5 - RuleERC2980Internal._removeFrozenlistAddress - 4 + RuleERC2980Internal._removeWhitelistAddress + 5 - RuleERC2980Internal._removeWhitelistAddress - 4 + RuleERC2980Internal._whitelistCount + 5 - RuleERC2980Internal._whitelistCount - 5 + RuleERC2980Internal._addFrozenlistAddresses + 6 + + + RuleERC2980Internal._addWhitelistAddresses + 7 + + + RuleERC2980Internal._addFrozenlistAddress + 22 - RuleERC2980Internal._addFrozenlistAddress - 19 + RuleERC2980Internal._requireNotZeroAddress + 25 - RuleERC2980Internal._addWhitelistAddress - 44 + RuleERC2980Internal._addWhitelistAddress + 51 - RuleERC2980Internal._isWhitelisted - 115 + RuleERC2980Internal._isWhitelisted + 80 - RuleERC2980Internal._isFrozen - 163 + RuleERC2980Internal._isFrozen + 165
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func.html index a4a1774a..cfa70999 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol - functions @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -69,51 +69,55 @@ Hit count Sort by hit count - RuleERC2980Internal._addFrozenlistAddress - 19 + RuleERC2980Internal._addFrozenlistAddress + 22 - RuleERC2980Internal._addFrozenlistAddresses - 4 + RuleERC2980Internal._addFrozenlistAddresses + 6 - RuleERC2980Internal._addWhitelistAddress - 44 + RuleERC2980Internal._addWhitelistAddress + 51 - RuleERC2980Internal._addWhitelistAddresses - 4 + RuleERC2980Internal._addWhitelistAddresses + 7 - RuleERC2980Internal._frozenlistCount + RuleERC2980Internal._frozenlistCount 4 - RuleERC2980Internal._isFrozen - 163 + RuleERC2980Internal._isFrozen + 165 - RuleERC2980Internal._isWhitelisted - 115 + RuleERC2980Internal._isWhitelisted + 80 - RuleERC2980Internal._removeFrozenlistAddress - 4 + RuleERC2980Internal._removeFrozenlistAddress + 5 - RuleERC2980Internal._removeFrozenlistAddresses - 2 + RuleERC2980Internal._removeFrozenlistAddresses + 3 - RuleERC2980Internal._removeWhitelistAddress + RuleERC2980Internal._removeWhitelistAddress + 5 + + + RuleERC2980Internal._removeWhitelistAddresses 4 - RuleERC2980Internal._removeWhitelistAddresses - 3 + RuleERC2980Internal._requireNotZeroAddress + 25 - RuleERC2980Internal._whitelistCount + RuleERC2980Internal._whitelistCount 5 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.gcov.html index 8996e6f7..96c79375 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -74,194 +74,184 @@ 3 : : 4 : : /* ==== OpenZeppelin === */ 5 : : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - 6 : : import {RuleERC2980InvariantStorage} from "./invariantStorage/RuleERC2980InvariantStorage.sol"; - 7 : : - 8 : : /** - 9 : : * @title RuleERC2980Internal - 10 : : * @notice Internal storage and helpers for two independent address sets: - 11 : : * a whitelist and a frozenlist, following the same pattern as {RuleAddressSetInternal}. - 12 : : * @dev - 13 : : * - Whitelist: only whitelisted addresses may receive tokens. - 14 : : * - Frozenlist: frozen addresses may neither send nor receive tokens. - 15 : : * - Batch operations do not revert when individual entries are already present or absent. - 16 : : */ - 17 : : abstract contract RuleERC2980Internal is RuleERC2980InvariantStorage { - 18 : : using EnumerableSet for EnumerableSet.AddressSet; - 19 : : - 20 : : /*////////////////////////////////////////////////////////////// - 21 : : STATE VARIABLES - 22 : : //////////////////////////////////////////////////////////////*/ - 23 : : - 24 : : /** - 25 : : * @dev Addresses allowed to receive tokens. - 26 : : */ - 27 : : EnumerableSet.AddressSet private _whitelist; - 28 : : - 29 : : /** - 30 : : * @dev Addresses completely blocked from sending and receiving tokens. - 31 : : */ - 32 : : EnumerableSet.AddressSet private _frozenlist; - 33 : : - 34 : : /*////////////////////////////////////////////////////////////// - 35 : : WHITELIST — INTERNAL - 36 : : //////////////////////////////////////////////////////////////*/ - 37 : : - 38 : : /** - 39 : : * @notice Adds multiple addresses to the whitelist, skipping any already present. - 40 : : * @param addressesToAdd Addresses to add to the whitelist. - 41 : : * @return added Number of addresses newly added. - 42 : : * @return skipped Number of addresses that were already whitelisted. - 43 : : */ - 44 : 4 : function _addWhitelistAddresses(address[] calldata addressesToAdd) - 45 : : internal - 46 : : returns (uint256 added, uint256 skipped) - 47 : : { - 48 : 4 : for (uint256 i = 0; i < addressesToAdd.length; ++i) { - 49 : : // The zero address is the mint/burn sentinel, never a participant. REJECTED rather than - 50 : : // skipped, so the emitted batch event can never report it as a list member. - 51 [ # + ]: 6 : require(addressesToAdd[i] != address(0), RuleERC2980_ZeroAddressNotAllowed()); - 52 [ + + ]: 6 : if (_whitelist.add(addressesToAdd[i])) { - 53 : 5 : added += 1; - 54 : : } else { - 55 : 1 : skipped += 1; - 56 : : } - 57 : : } - 58 : : } - 59 : : - 60 : : /** - 61 : : * @notice Removes multiple addresses from the whitelist, skipping any that are absent. - 62 : : * @param addressesToRemove Addresses to remove from the whitelist. - 63 : : * @return removed Number of addresses actually removed. - 64 : : * @return skipped Number of addresses that were not whitelisted. - 65 : : */ - 66 : 3 : function _removeWhitelistAddresses(address[] calldata addressesToRemove) - 67 : : internal - 68 : : returns (uint256 removed, uint256 skipped) - 69 : : { - 70 : 3 : for (uint256 i = 0; i < addressesToRemove.length; ++i) { - 71 [ + + ]: 3 : if (_whitelist.remove(addressesToRemove[i])) { - 72 : 2 : removed += 1; - 73 : : } else { - 74 : 1 : skipped += 1; - 75 : : } - 76 : : } - 77 : : } - 78 : : - 79 : : /** - 80 : : * @notice Adds a single address to the whitelist. - 81 : : * @param targetAddress Address to add to the whitelist. - 82 : : */ - 83 : 44 : function _addWhitelistAddress(address targetAddress) internal virtual { - 84 : 44 : _whitelist.add(targetAddress); - 85 : : } - 86 : : - 87 : : /** - 88 : : * @notice Removes a single address from the whitelist. - 89 : : * @param targetAddress Address to remove from the whitelist. - 90 : : */ - 91 : 4 : function _removeWhitelistAddress(address targetAddress) internal virtual { - 92 : 4 : _whitelist.remove(targetAddress); - 93 : : } - 94 : : - 95 : : /*////////////////////////////////////////////////////////////// - 96 : : FROZENLIST — INTERNAL - 97 : : //////////////////////////////////////////////////////////////*/ - 98 : : - 99 : : /** - 100 : : * @notice Adds multiple addresses to the frozenlist, skipping any already present. - 101 : : * @param addressesToAdd Addresses to add to the frozenlist. - 102 : : * @return added Number of addresses newly added. - 103 : : * @return skipped Number of addresses that were already frozen. - 104 : : */ - 105 : 4 : function _addFrozenlistAddresses(address[] calldata addressesToAdd) - 106 : : internal - 107 : : returns (uint256 added, uint256 skipped) - 108 : : { - 109 : 4 : for (uint256 i = 0; i < addressesToAdd.length; ++i) { - 110 : : // The zero address is the mint/burn sentinel, never a participant. REJECTED rather than - 111 : : // skipped, so the emitted batch event can never report it as a list member. - 112 [ # + ]: 6 : require(addressesToAdd[i] != address(0), RuleERC2980_ZeroAddressNotAllowed()); - 113 [ + + ]: 6 : if (_frozenlist.add(addressesToAdd[i])) { - 114 : 5 : added += 1; - 115 : : } else { - 116 : 1 : skipped += 1; - 117 : : } - 118 : : } - 119 : : } - 120 : : - 121 : : /** - 122 : : * @notice Removes multiple addresses from the frozenlist, skipping any that are absent. - 123 : : * @param addressesToRemove Addresses to remove from the frozenlist. - 124 : : * @return removed Number of addresses actually removed. - 125 : : * @return skipped Number of addresses that were not frozen. - 126 : : */ - 127 : 2 : function _removeFrozenlistAddresses(address[] calldata addressesToRemove) - 128 : : internal - 129 : : returns (uint256 removed, uint256 skipped) - 130 : : { - 131 : 2 : for (uint256 i = 0; i < addressesToRemove.length; ++i) { - 132 [ + + ]: 2 : if (_frozenlist.remove(addressesToRemove[i])) { - 133 : 1 : removed += 1; - 134 : : } else { - 135 : 1 : skipped += 1; - 136 : : } - 137 : : } - 138 : : } - 139 : : - 140 : : /** - 141 : : * @notice Adds a single address to the frozenlist. - 142 : : * @param targetAddress Address to add to the frozenlist. - 143 : : */ - 144 : 19 : function _addFrozenlistAddress(address targetAddress) internal virtual { - 145 : 19 : _frozenlist.add(targetAddress); - 146 : : } - 147 : : - 148 : : /** - 149 : : * @notice Removes a single address from the frozenlist. - 150 : : * @param targetAddress Address to remove from the frozenlist. - 151 : : */ - 152 : 4 : function _removeFrozenlistAddress(address targetAddress) internal virtual { - 153 : 4 : _frozenlist.remove(targetAddress); - 154 : : } - 155 : : - 156 : : /*////////////////////////////////////////////////////////////// - 157 : : VIEW — INTERNAL - 158 : : //////////////////////////////////////////////////////////////*/ - 159 : : - 160 : : /** - 161 : : * @notice Returns whether an address is whitelisted. - 162 : : * @param targetAddress Address to check. - 163 : : * @return True if the address is whitelisted. - 164 : : */ - 165 : 115 : function _isWhitelisted(address targetAddress) internal view virtual returns (bool) { - 166 : 115 : return _whitelist.contains(targetAddress); - 167 : : } - 168 : : - 169 : : /** - 170 : : * @notice Returns the number of whitelisted addresses. - 171 : : * @return The count of whitelisted addresses. - 172 : : */ - 173 : 5 : function _whitelistCount() internal view virtual returns (uint256) { - 174 : 5 : return _whitelist.length(); - 175 : : } - 176 : : - 177 : : /** - 178 : : * @notice Returns whether an address is frozen. - 179 : : * @param targetAddress Address to check. - 180 : : * @return True if the address is frozen. - 181 : : */ - 182 : 163 : function _isFrozen(address targetAddress) internal view virtual returns (bool) { - 183 : 163 : return _frozenlist.contains(targetAddress); - 184 : : } - 185 : : - 186 : : /** - 187 : : * @notice Returns the number of frozen addresses. - 188 : : * @return The count of frozen addresses. - 189 : : */ - 190 : 4 : function _frozenlistCount() internal view virtual returns (uint256) { - 191 : 4 : return _frozenlist.length(); - 192 : : } - 193 : : } + 6 : : import {AddressSetBatchLib} from "../RuleAddressSet/AddressSetBatchLib.sol"; + 7 : : import {RuleERC2980InvariantStorage} from "./invariantStorage/RuleERC2980InvariantStorage.sol"; + 8 : : + 9 : : /** + 10 : : * @title RuleERC2980Internal + 11 : : * @notice Internal storage and helpers for two independent address sets: + 12 : : * a whitelist and a frozenlist, following the same pattern as {RuleAddressSetInternal}. + 13 : : * @dev + 14 : : * - Whitelist: only whitelisted addresses may receive tokens. + 15 : : * - Frozenlist: frozen addresses may neither send nor receive tokens. + 16 : : * - Batch operations do not revert when individual entries are already present or absent. + 17 : : */ + 18 : : abstract contract RuleERC2980Internal is RuleERC2980InvariantStorage { + 19 : : using EnumerableSet for EnumerableSet.AddressSet; + 20 : : using AddressSetBatchLib for EnumerableSet.AddressSet; + 21 : : + 22 : : /*////////////////////////////////////////////////////////////// + 23 : : STATE VARIABLES + 24 : : //////////////////////////////////////////////////////////////*/ + 25 : : + 26 : : /** + 27 : : * @dev Addresses allowed to receive tokens. + 28 : : */ + 29 : : EnumerableSet.AddressSet private _whitelist; + 30 : : + 31 : : /** + 32 : : * @dev Addresses completely blocked from sending and receiving tokens. + 33 : : */ + 34 : : EnumerableSet.AddressSet private _frozenlist; + 35 : : + 36 : : /*////////////////////////////////////////////////////////////// + 37 : : WHITELIST — INTERNAL + 38 : : //////////////////////////////////////////////////////////////*/ + 39 : : + 40 : : /** + 41 : : * @notice Adds multiple addresses to the whitelist, skipping any already present. + 42 : : * @dev REVERTS on `address(0)`, rejecting the whole batch; see the inline comment below. + 43 : : * @param addressesToAdd Addresses to add to the whitelist. + 44 : : * @return added Number of addresses newly added. + 45 : : * @return skipped Number of addresses that were already whitelisted. + 46 : : */ + 47 : 7 : function _addWhitelistAddresses(address[] calldata addressesToAdd) + 48 : : internal + 49 : : virtual + 50 : : returns (uint256 added, uint256 skipped) + 51 : : { + 52 : 7 : return _whitelist.addBatch(addressesToAdd, _requireNotZeroAddress); + 53 : : } + 54 : : + 55 : : /** + 56 : : * @notice Removes multiple addresses from the whitelist, skipping any that are absent. + 57 : : * @param addressesToRemove Addresses to remove from the whitelist. + 58 : : * @return removed Number of addresses actually removed. + 59 : : * @return skipped Number of addresses that were not whitelisted. + 60 : : */ + 61 : 4 : function _removeWhitelistAddresses(address[] calldata addressesToRemove) + 62 : : internal + 63 : : virtual + 64 : : returns (uint256 removed, uint256 skipped) + 65 : : { + 66 : 4 : return _whitelist.removeBatch(addressesToRemove); + 67 : : } + 68 : : + 69 : : /** + 70 : : * @notice Adds a single address to the whitelist. + 71 : : * @param targetAddress Address to add to the whitelist. + 72 : : */ + 73 : 51 : function _addWhitelistAddress(address targetAddress) internal virtual returns (bool) { + 74 : 51 : return _whitelist.add(targetAddress); + 75 : : } + 76 : : + 77 : : /** + 78 : : * @notice Removes a single address from the whitelist. + 79 : : * @param targetAddress Address to remove from the whitelist. + 80 : : */ + 81 : 5 : function _removeWhitelistAddress(address targetAddress) internal virtual returns (bool) { + 82 : 5 : return _whitelist.remove(targetAddress); + 83 : : } + 84 : : + 85 : : /*////////////////////////////////////////////////////////////// + 86 : : FROZENLIST — INTERNAL + 87 : : //////////////////////////////////////////////////////////////*/ + 88 : : + 89 : : /** + 90 : : * @notice Adds multiple addresses to the frozenlist, skipping any already present. + 91 : : * @dev REVERTS on `address(0)`, rejecting the whole batch; see the inline comment below. + 92 : : * @param addressesToAdd Addresses to add to the frozenlist. + 93 : : * @return added Number of addresses newly added. + 94 : : * @return skipped Number of addresses that were already frozen. + 95 : : */ + 96 : 6 : function _addFrozenlistAddresses(address[] calldata addressesToAdd) + 97 : : internal + 98 : : virtual + 99 : : returns (uint256 added, uint256 skipped) + 100 : : { + 101 : 6 : return _frozenlist.addBatch(addressesToAdd, _requireNotZeroAddress); + 102 : : } + 103 : : + 104 : : /** + 105 : : * @notice Removes multiple addresses from the frozenlist, skipping any that are absent. + 106 : : * @param addressesToRemove Addresses to remove from the frozenlist. + 107 : : * @return removed Number of addresses actually removed. + 108 : : * @return skipped Number of addresses that were not frozen. + 109 : : */ + 110 : 3 : function _removeFrozenlistAddresses(address[] calldata addressesToRemove) + 111 : : internal + 112 : : virtual + 113 : : returns (uint256 removed, uint256 skipped) + 114 : : { + 115 : 3 : return _frozenlist.removeBatch(addressesToRemove); + 116 : : } + 117 : : + 118 : : /** + 119 : : * @notice Adds a single address to the frozenlist. + 120 : : * @param targetAddress Address to add to the frozenlist. + 121 : : */ + 122 : 22 : function _addFrozenlistAddress(address targetAddress) internal virtual returns (bool) { + 123 : 22 : return _frozenlist.add(targetAddress); + 124 : : } + 125 : : + 126 : : /** + 127 : : * @notice Removes a single address from the frozenlist. + 128 : : * @param targetAddress Address to remove from the frozenlist. + 129 : : */ + 130 : 5 : function _removeFrozenlistAddress(address targetAddress) internal virtual returns (bool) { + 131 : 5 : return _frozenlist.remove(targetAddress); + 132 : : } + 133 : : + 134 : : /** + 135 : : * @notice Per-entry guard for both batch adders; reverts on the zero address. + 136 : : * @dev The zero address is the mint/burn sentinel, never a participant. REJECTED rather than + 137 : : * skipped, so the emitted batch event can never report it as a list member. Passed to + 138 : : * {AddressSetBatchLib.addBatch} as a function pointer so the shared loop rejects the sentinel + 139 : : * with THIS rule's error rather than a generic one. + 140 : : * @param targetAddress The candidate address. + 141 : : */ + 142 : 25 : function _requireNotZeroAddress(address targetAddress) internal pure virtual { + 143 [ + + ]: 25 : require(targetAddress != address(0), RuleERC2980_ZeroAddressNotAllowed()); + 144 : : } + 145 : : + 146 : : /*////////////////////////////////////////////////////////////// + 147 : : VIEW — INTERNAL + 148 : : //////////////////////////////////////////////////////////////*/ + 149 : : + 150 : : /** + 151 : : * @notice Returns whether an address is whitelisted. + 152 : : * @param targetAddress Address to check. + 153 : : * @return True if the address is whitelisted. + 154 : : */ + 155 : 80 : function _isWhitelisted(address targetAddress) internal view virtual returns (bool) { + 156 : 80 : return _whitelist.contains(targetAddress); + 157 : : } + 158 : : + 159 : : /** + 160 : : * @notice Returns the number of whitelisted addresses. + 161 : : * @return The count of whitelisted addresses. + 162 : : */ + 163 : 5 : function _whitelistCount() internal view virtual returns (uint256) { + 164 : 5 : return _whitelist.length(); + 165 : : } + 166 : : + 167 : : /** + 168 : : * @notice Returns whether an address is frozen. + 169 : : * @param targetAddress Address to check. + 170 : : * @return True if the address is frozen. + 171 : : */ + 172 : 165 : function _isFrozen(address targetAddress) internal view virtual returns (bool) { + 173 : 165 : return _frozenlist.contains(targetAddress); + 174 : : } + 175 : : + 176 : : /** + 177 : : * @notice Returns the number of frozen addresses. + 178 : : * @return The count of frozen addresses. + 179 : : */ + 180 : 4 : function _frozenlistCount() internal view virtual returns (uint256) { + 181 : 4 : return _frozenlist.length(); + 182 : : } + 183 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-b.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-b.html index fb816b00..47b48dcc 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980 + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980 @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -87,11 +87,11 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 + 100.0 % + 13 / 13 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-f.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-f.html index a6d4ca12..623e955b 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980 + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980 @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -87,11 +87,11 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 + 100.0 % + 13 / 13 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-l.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-l.html index 5efbf7a9..ddd279c0 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980 + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980 @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -87,11 +87,11 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 + 100.0 % + 13 / 13 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index.html b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index.html index 7583b40c..00ced175 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/RuleERC2980/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/RuleERC2980 + LCOV - lcov2.info - src/rules/validation/abstract/RuleERC2980 @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 38 - 38 + 26 + 26 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 12 + 13 + 13 100.0 % @@ -49,9 +49,9 @@ Branches: - 10 - 12 - 83.3 % + 2 + 2 + 100.0 % @@ -87,11 +87,11 @@
100.0%
100.0 % - 38 / 38 + 26 / 26 + 100.0 % + 13 / 13 100.0 % - 12 / 12 - 83.3 % - 10 / 12 + 2 / 2 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func-sort-c.html index 0808b412..277355af 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleBlacklistBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleBlacklistBase.sol - functions @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 34 - 34 + 38 + 38 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 9 - 9 + 10 + 10 100.0 % @@ -69,40 +69,44 @@ Hit count Sort by hit count - RuleBlacklistBase.canReturnTransferRestrictionCode - 4 + RuleBlacklistBase.isAllowList + 3 - RuleBlacklistBase.messageForTransferRestriction - 12 + RuleBlacklistBase.canReturnTransferRestrictionCode + 7 - RuleBlacklistBase.transferred.0 - 18 + RuleBlacklistBase.messageForTransferRestriction + 13 - RuleBlacklistBase._transferred + RuleBlacklistBase.transferred.0 29 - RuleBlacklistBase.transferred.1 + RuleBlacklistBase._transferred 46 - RuleBlacklistBase._transferredFrom - 54 + RuleBlacklistBase.transferred.1 + 94 - RuleBlacklistBase.supportsInterface - 65 + RuleBlacklistBase._transferredFrom + 102 - RuleBlacklistBase._detectTransferRestrictionFrom - 80 + RuleBlacklistBase.supportsInterface + 109 - RuleBlacklistBase._detectTransferRestriction - 140 + RuleBlacklistBase._detectTransferRestrictionFrom + 134 + + + RuleBlacklistBase._detectTransferRestriction + 230
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func.html index 57023b37..1461b9ba 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleBlacklistBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleBlacklistBase.sol - functions @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 34 - 34 + 38 + 38 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 9 - 9 + 10 + 10 100.0 % @@ -69,40 +69,44 @@ Hit count Sort by hit count - RuleBlacklistBase._detectTransferRestriction - 140 + RuleBlacklistBase._detectTransferRestriction + 230 - RuleBlacklistBase._detectTransferRestrictionFrom - 80 + RuleBlacklistBase._detectTransferRestrictionFrom + 134 - RuleBlacklistBase._transferred - 29 + RuleBlacklistBase._transferred + 46 - RuleBlacklistBase._transferredFrom - 54 + RuleBlacklistBase._transferredFrom + 102 - RuleBlacklistBase.canReturnTransferRestrictionCode - 4 + RuleBlacklistBase.canReturnTransferRestrictionCode + 7 - RuleBlacklistBase.messageForTransferRestriction - 12 + RuleBlacklistBase.isAllowList + 3 - RuleBlacklistBase.supportsInterface - 65 + RuleBlacklistBase.messageForTransferRestriction + 13 - RuleBlacklistBase.transferred.0 - 18 + RuleBlacklistBase.supportsInterface + 109 - RuleBlacklistBase.transferred.1 - 46 + RuleBlacklistBase.transferred.0 + 29 + + + RuleBlacklistBase.transferred.1 + 94
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.gcov.html index fe77b99d..5615747c 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleBlacklistBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleBlacklistBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleBlacklistBase.sol @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 34 - 34 + 38 + 38 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 9 - 9 + 10 + 10 100.0 % @@ -76,179 +76,197 @@ 5 : : import {RuleNFTAdapter} from "../core/RuleNFTAdapter.sol"; 6 : : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; 7 : : import {RuleBlacklistInvariantStorage} from "../RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol"; - 8 : : import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; - 9 : : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; - 10 : : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; - 11 : : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; - 12 : : import {IRule} from "RuleEngine/interfaces/IRule.sol"; - 13 : : - 14 : : /** - 15 : : * @title RuleBlacklistBase - 16 : : * @notice Core blacklist logic without access-control policy. - 17 : : */ - 18 : : abstract contract RuleBlacklistBase is RuleAddressSet, RuleNFTAdapter, RuleBlacklistInvariantStorage { - 19 : : /*////////////////////////////////////////////////////////////// - 20 : : CONSTRUCTOR - 21 : : //////////////////////////////////////////////////////////////*/ - 22 : : - 23 : : /** - 24 : : * @notice Deploys the blacklist rule base. - 25 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. - 26 : : */ - 27 : : constructor(address forwarderIrrevocable) RuleAddressSet(forwarderIrrevocable) {} + 8 : : import {IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; + 9 : : import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; + 10 : : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; + 11 : : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; + 12 : : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; + 13 : : import {IRule} from "RuleEngine/interfaces/IRule.sol"; + 14 : : + 15 : : /** + 16 : : * @title RuleBlacklistBase + 17 : : * @notice Core blacklist logic without access-control policy. + 18 : : */ + 19 : : abstract contract RuleBlacklistBase is + 20 : : RuleAddressSet, + 21 : : RuleNFTAdapter, + 22 : : RuleBlacklistInvariantStorage, + 23 : : IAddressListPolarity + 24 : : { + 25 : : /*////////////////////////////////////////////////////////////// + 26 : : CONSTRUCTOR + 27 : : //////////////////////////////////////////////////////////////*/ 28 : : - 29 : : /*////////////////////////////////////////////////////////////// - 30 : : PUBLIC FUNCTIONS - 31 : : //////////////////////////////////////////////////////////////*/ - 32 : : - 33 : : /** - 34 : : * @inheritdoc IERC3643IComplianceContract - 35 : : * @dev Validation only; does not modify state. - 36 : : */ - 37 : 18 : function transferred(address from, address to, uint256 value) - 38 : : public - 39 : : view - 40 : : virtual - 41 : : override(IERC3643IComplianceContract) - 42 : : { - 43 : 18 : _transferred(from, to, value); - 44 : : } - 45 : : - 46 : : /** - 47 : : * @inheritdoc IRuleEngine - 48 : : * @dev Validation only; does not modify state. - 49 : : */ - 50 : 46 : function transferred(address spender, address from, address to, uint256 value) - 51 : : public - 52 : : view - 53 : : virtual - 54 : : override(IRuleEngine) - 55 : : { - 56 : 46 : _transferredFrom(spender, from, to, value); - 57 : : } - 58 : : - 59 : : /** - 60 : : * @inheritdoc IRule - 61 : : */ - 62 : 4 : function canReturnTransferRestrictionCode(uint8 restrictionCode) - 63 : : public - 64 : : pure - 65 : : virtual - 66 : : override(IRule) - 67 : : returns (bool) - 68 : : { - 69 : 4 : return restrictionCode == CODE_ADDRESS_FROM_IS_BLACKLISTED || restrictionCode == CODE_ADDRESS_TO_IS_BLACKLISTED - 70 : 1 : || restrictionCode == CODE_ADDRESS_SPENDER_IS_BLACKLISTED; - 71 : : } - 72 : : - 73 : : /** - 74 : : * @inheritdoc IERC1404 - 75 : : */ - 76 : 12 : function messageForTransferRestriction(uint8 restrictionCode) - 77 : : public - 78 : : pure - 79 : : virtual - 80 : : override(IERC1404) - 81 : : returns (string memory) - 82 : : { - 83 [ + + ]: 12 : if (restrictionCode == CODE_ADDRESS_FROM_IS_BLACKLISTED) { - 84 : 5 : return TEXT_ADDRESS_FROM_IS_BLACKLISTED; - 85 [ + + ]: 7 : } else if (restrictionCode == CODE_ADDRESS_TO_IS_BLACKLISTED) { - 86 : 3 : return TEXT_ADDRESS_TO_IS_BLACKLISTED; - 87 [ + + ]: 4 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_IS_BLACKLISTED) { - 88 : 1 : return TEXT_ADDRESS_SPENDER_IS_BLACKLISTED; - 89 : : } else { - 90 : 3 : return TEXT_CODE_NOT_FOUND; - 91 : : } - 92 : : } - 93 : : - 94 : : /** - 95 : : * @inheritdoc RuleTransferValidation - 96 : : */ - 97 : 65 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { - 98 : : // Advertise IAddressList: this rule manages an address set and is callable through - 99 : : // the IAddressList interface. - 100 : 65 : return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID - 101 : 63 : || RuleTransferValidation.supportsInterface(interfaceId); - 102 : : } - 103 : : - 104 : : /*////////////////////////////////////////////////////////////// - 105 : : INTERNAL FUNCTIONS - 106 : : //////////////////////////////////////////////////////////////*/ - 107 : : - 108 : : /** - 109 : : * @notice Detects whether a direct transfer is restricted by the blacklist. - 110 : : * @param from The sender address. - 111 : : * @param to The recipient address. - 112 : : * @return The restriction code, or TRANSFER_OK if neither party is blacklisted. - 113 : : */ - 114 : 140 : function _detectTransferRestriction( - 115 : : address from, - 116 : : address to, - 117 : : uint256 /* value */ - 118 : : ) - 119 : : internal - 120 : : view - 121 : : override - 122 : : returns (uint8) - 123 : : { - 124 [ + + ]: 140 : if (isAddressListed(from)) { - 125 : 40 : return CODE_ADDRESS_FROM_IS_BLACKLISTED; - 126 [ + ]: 100 : } else if (isAddressListed(to)) { - 127 : 19 : return CODE_ADDRESS_TO_IS_BLACKLISTED; - 128 : : } - 129 : 81 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 130 : : } - 131 : : - 132 : : /** - 133 : : * @notice Detects whether a delegated transfer is restricted by the blacklist. - 134 : : * @param spender The delegated spender address. - 135 : : * @param from The sender address. - 136 : : * @param to The recipient address. - 137 : : * @param value The amount transferred. - 138 : : * @return The restriction code, or TRANSFER_OK if no party is blacklisted. - 139 : : */ - 140 : 80 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 141 : : internal - 142 : : view - 143 : : override - 144 : : returns (uint8) - 145 : : { - 146 [ + ]: 80 : if (isAddressListed(spender)) { - 147 : 8 : return CODE_ADDRESS_SPENDER_IS_BLACKLISTED; - 148 : : } - 149 : 72 : return _detectTransferRestriction(from, to, value); - 150 : : } - 151 : : - 152 : : /** - 153 : : * @notice Reverts if a direct transfer is blocked by the blacklist. - 154 : : * @param from The sender address. - 155 : : * @param to The recipient address. - 156 : : * @param value The amount transferred. - 157 : : */ - 158 : 29 : function _transferred(address from, address to, uint256 value) internal view virtual override { - 159 : 29 : uint8 code = _detectTransferRestriction(from, to, value); - 160 [ + + ]: 29 : require( - 161 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), - 162 : : RuleBlacklist_InvalidTransfer(address(this), from, to, value, code) - 163 : : ); - 164 : : } - 165 : : - 166 : : /** - 167 : : * @notice Reverts if a delegated transfer is blocked by the blacklist. - 168 : : * @param spender The delegated spender address. - 169 : : * @param from The sender address. - 170 : : * @param to The recipient address. - 171 : : * @param value The amount transferred. - 172 : : */ - 173 : 54 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { - 174 : 54 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 175 [ + + ]: 54 : require( - 176 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), - 177 : : RuleBlacklist_InvalidTransferFrom(address(this), spender, from, to, value, code) - 178 : : ); - 179 : : } - 180 : : } + 29 : : /** + 30 : : * @notice Deploys the blacklist rule base. + 31 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. + 32 : : */ + 33 : : constructor(address forwarderIrrevocable) RuleAddressSet(forwarderIrrevocable) {} + 34 : : + 35 : : /*////////////////////////////////////////////////////////////// + 36 : : PUBLIC FUNCTIONS + 37 : : //////////////////////////////////////////////////////////////*/ + 38 : : + 39 : : /** + 40 : : * @inheritdoc IERC3643IComplianceContract + 41 : : * @dev Validation only; does not modify state. + 42 : : */ + 43 : 29 : function transferred(address from, address to, uint256 value) + 44 : : public + 45 : : view + 46 : : virtual + 47 : : override(IERC3643IComplianceContract) + 48 : : { + 49 : 29 : _transferred(from, to, value); + 50 : : } + 51 : : + 52 : : /** + 53 : : * @inheritdoc IRuleEngine + 54 : : * @dev Validation only; does not modify state. + 55 : : */ + 56 : 94 : function transferred(address spender, address from, address to, uint256 value) + 57 : : public + 58 : : view + 59 : : virtual + 60 : : override(IRuleEngine) + 61 : : { + 62 : 94 : _transferredFrom(spender, from, to, value); + 63 : : } + 64 : : + 65 : : /** + 66 : : * @inheritdoc IRule + 67 : : */ + 68 : 7 : function canReturnTransferRestrictionCode(uint8 restrictionCode) + 69 : : public + 70 : : pure + 71 : : virtual + 72 : : override(IRule) + 73 : : returns (bool) + 74 : : { + 75 : 7 : return restrictionCode == CODE_ADDRESS_FROM_IS_BLACKLISTED || restrictionCode == CODE_ADDRESS_TO_IS_BLACKLISTED + 76 : 3 : || restrictionCode == CODE_ADDRESS_SPENDER_IS_BLACKLISTED; + 77 : : } + 78 : : + 79 : : /** + 80 : : * @inheritdoc IERC1404 + 81 : : */ + 82 : 13 : function messageForTransferRestriction(uint8 restrictionCode) + 83 : : public + 84 : : pure + 85 : : virtual + 86 : : override(IERC1404) + 87 : : returns (string memory) + 88 : : { + 89 [ + + ]: 13 : if (restrictionCode == CODE_ADDRESS_FROM_IS_BLACKLISTED) { + 90 : 6 : return TEXT_ADDRESS_FROM_IS_BLACKLISTED; + 91 [ + + ]: 7 : } else if (restrictionCode == CODE_ADDRESS_TO_IS_BLACKLISTED) { + 92 : 3 : return TEXT_ADDRESS_TO_IS_BLACKLISTED; + 93 [ + + ]: 4 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_IS_BLACKLISTED) { + 94 : 1 : return TEXT_ADDRESS_SPENDER_IS_BLACKLISTED; + 95 : : } else { + 96 : 3 : return TEXT_CODE_NOT_FOUND; + 97 : : } + 98 : : } + 99 : : + 100 : : /** + 101 : : * @inheritdoc RuleTransferValidation + 102 : : */ + 103 : 109 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { + 104 : : // Advertise IAddressList: this rule manages an address set and is callable through + 105 : : // the IAddressList interface. + 106 : 109 : return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + 107 : 107 : || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + 108 : 105 : || interfaceId == AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID + 109 : 103 : || RuleTransferValidation.supportsInterface(interfaceId); + 110 : : } + 111 : : + 112 : : /** + 113 : : * @inheritdoc IAddressListPolarity + 114 : : * @dev Listed addresses are the BLOCKED ones. A consumer that reads membership as eligibility must refuse this rule. + 115 : : */ + 116 : 3 : function isAllowList() public pure virtual override returns (bool) { + 117 : 3 : return false; + 118 : : } + 119 : : + 120 : : /*////////////////////////////////////////////////////////////// + 121 : : INTERNAL FUNCTIONS + 122 : : //////////////////////////////////////////////////////////////*/ + 123 : : + 124 : : /** + 125 : : * @notice Detects whether a direct transfer is restricted by the blacklist. + 126 : : * @param from The sender address. + 127 : : * @param to The recipient address. + 128 : : * @return The restriction code, or TRANSFER_OK if neither party is blacklisted. + 129 : : */ + 130 : 230 : function _detectTransferRestriction( + 131 : : address from, + 132 : : address to, + 133 : : uint256 /* value */ + 134 : : ) + 135 : : internal + 136 : : view + 137 : : virtual + 138 : : override + 139 : : returns (uint8) + 140 : : { + 141 [ + + ]: 230 : if (isAddressListed(from)) { + 142 : 54 : return CODE_ADDRESS_FROM_IS_BLACKLISTED; + 143 [ + ]: 176 : } else if (isAddressListed(to)) { + 144 : 23 : return CODE_ADDRESS_TO_IS_BLACKLISTED; + 145 : : } + 146 : 153 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 147 : : } + 148 : : + 149 : : /** + 150 : : * @notice Detects whether a delegated transfer is restricted by the blacklist. + 151 : : * @param spender The delegated spender address. + 152 : : * @param from The sender address. + 153 : : * @param to The recipient address. + 154 : : * @param value The amount transferred. + 155 : : * @return The restriction code, or TRANSFER_OK if no party is blacklisted. + 156 : : */ + 157 : 134 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 158 : : internal + 159 : : view + 160 : : virtual + 161 : : override + 162 : : returns (uint8) + 163 : : { + 164 [ + ]: 134 : if (isAddressListed(spender)) { + 165 : 8 : return CODE_ADDRESS_SPENDER_IS_BLACKLISTED; + 166 : : } + 167 : 126 : return _detectTransferRestriction(from, to, value); + 168 : : } + 169 : : + 170 : : /** + 171 : : * @notice Reverts if a direct transfer is blocked by the blacklist. + 172 : : * @param from The sender address. + 173 : : * @param to The recipient address. + 174 : : * @param value The amount transferred. + 175 : : */ + 176 : 46 : function _transferred(address from, address to, uint256 value) internal view virtual override { + 177 : 46 : uint8 code = _detectTransferRestriction(from, to, value); + 178 [ + + ]: 46 : require( + 179 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), + 180 : : RuleBlacklist_InvalidTransfer(address(this), from, to, value, code) + 181 : : ); + 182 : : } + 183 : : + 184 : : /** + 185 : : * @notice Reverts if a delegated transfer is blocked by the blacklist. + 186 : : * @param spender The delegated spender address. + 187 : : * @param from The sender address. + 188 : : * @param to The recipient address. + 189 : : * @param value The amount transferred. + 190 : : */ + 191 : 102 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { + 192 : 102 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + 193 [ + + ]: 102 : require( + 194 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), + 195 : : RuleBlacklist_InvalidTransferFrom(address(this), spender, from, to, value, code) + 196 : : ); + 197 : : } + 198 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.func-sort-c.html new file mode 100644 index 00000000..79be27ba --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.func-sort-c.html @@ -0,0 +1,121 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleChainlinkPoRBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:4646100.0 %
Date:2026-08-19 15:38:25Functions:1010100.0 %
Branches:1717100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoRBase._detectTransferRestrictionFrom5
RuleChainlinkPoRBase.canReturnTransferRestrictionCode9
RuleChainlinkPoRBase.messageForTransferRestriction9
RuleChainlinkPoRBase._transferred16
RuleChainlinkPoRBase._transferredFrom16
RuleChainlinkPoRBase.transferred.016
RuleChainlinkPoRBase.transferred.116
RuleChainlinkPoRBase._detectTransferRestrictionOnNotify19
RuleChainlinkPoRBase.constructor624
RuleChainlinkPoRBase._detectTransferRestriction629
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.func.html new file mode 100644 index 00000000..7c470918 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.func.html @@ -0,0 +1,121 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleChainlinkPoRBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:4646100.0 %
Date:2026-08-19 15:38:25Functions:1010100.0 %
Branches:1717100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoRBase._detectTransferRestriction629
RuleChainlinkPoRBase._detectTransferRestrictionFrom5
RuleChainlinkPoRBase._detectTransferRestrictionOnNotify19
RuleChainlinkPoRBase._transferred16
RuleChainlinkPoRBase._transferredFrom16
RuleChainlinkPoRBase.canReturnTransferRestrictionCode9
RuleChainlinkPoRBase.constructor624
RuleChainlinkPoRBase.messageForTransferRestriction9
RuleChainlinkPoRBase.transferred.016
RuleChainlinkPoRBase.transferred.116
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.gcov.html new file mode 100644 index 00000000..4bc66608 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol.gcov.html @@ -0,0 +1,297 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleChainlinkPoRBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:4646100.0 %
Date:2026-08-19 15:38:25Functions:1010100.0 %
Branches:1717100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol";
+       5                 :            : import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol";
+       6                 :            : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol";
+       7                 :            : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol";
+       8                 :            : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol";
+       9                 :            : import {ChainlinkPoRFeedManager} from "../core/ChainlinkPoRFeedManager.sol";
+      10                 :            : 
+      11                 :            : /**
+      12                 :            :  * @title RuleChainlinkPoRBase
+      13                 :            :  * @notice Caps minting at the reserves reported by a Chainlink Proof of Reserve feed. The limit
+      14                 :            :  * equals the reported reserves exactly -- no margin or buffer.
+      15                 :            :  * @dev Only mints are gated: transfers do not change total supply and burns only reduce it.
+      16                 :            :  *
+      17                 :            :  * @dev The rule half: constructor, ERC-1404 / ERC-3643 surface, and the mapping from a backed supply
+      18                 :            :  * to a restriction code. The feed itself -- which feed, which token, staleness, scaling and the
+      19                 :            :  * revert-free read -- lives in {ChainlinkPoRFeedManager}.
+      20                 :            :  *
+      21                 :            :  * @dev The read path must never revert, and every failure is fail-closed (the mint is blocked): an
+      22                 :            :  * unreadable or over-precision feed yields {CODE_RESERVES_FEED_UNAVAILABLE}, a negative or
+      23                 :            :  * incomplete answer {CODE_RESERVES_ANSWER_INVALID}, an old one {CODE_RESERVES_FEED_STALE}, and an
+      24                 :            :  * unreadable `totalSupply()` {CODE_TOTAL_SUPPLY_UNAVAILABLE}. The token is trusted to report an
+      25                 :            :  * accurate supply, but not to stay callable -- that is guarded.
+      26                 :            :  */
+      27                 :            : abstract contract RuleChainlinkPoRBase is RuleTransferValidation, ChainlinkPoRFeedManager {
+      28                 :            :     /*//////////////////////////////////////////////////////////////
+      29                 :            :                              CONSTRUCTOR
+      30                 :            :     //////////////////////////////////////////////////////////////*/
+      31                 :            : 
+      32                 :            :     /**
+      33                 :            :      * @notice Initializes the rule with the protected token and the reserve feed.
+      34                 :            :      * @dev Configuration is delegated to {ChainlinkPoRFeedManager}'s internals, which are
+      35                 :            :      * constructor-agnostic; an upgradeable variant would call the same three from an initializer.
+      36                 :            :      * @param tokenContract_ Address of the token whose `totalSupply` is checked; must not be the zero address.
+      37                 :            :      * @param tokenDecimals_ Decimals of that token; must be at most {MAX_TOKEN_DECIMALS} and, when
+      38                 :            :      * the token exposes `decimals()`, must match it. `0` is valid and common for CMTAT equity tokens.
+      39                 :            :      * @param reservesFeed_ Proof of Reserve data feed; must be a contract exposing `AggregatorV3Interface`.
+      40                 :            :      * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check.
+      41                 :            :      */
+      42                 :        624 :     constructor(
+      43                 :            :         address tokenContract_,
+      44                 :            :         uint8 tokenDecimals_,
+      45                 :            :         AggregatorV3Interface reservesFeed_,
+      46                 :            :         uint256 maxStalenessSeconds_
+      47                 :            :     ) {
+      48                 :        624 :         _setReservesFeed(reservesFeed_);
+      49                 :        622 :         _setTokenMetadata(tokenContract_, tokenDecimals_);
+      50                 :        620 :         _setMaxStalenessSeconds(maxStalenessSeconds_);
+      51                 :            :     }
+      52                 :            : 
+      53                 :            :     /*//////////////////////////////////////////////////////////////
+      54                 :            :                         EXTERNAL FUNCTIONS
+      55                 :            :     //////////////////////////////////////////////////////////////*/
+      56                 :            : 
+      57                 :            :     /**
+      58                 :            :      * @notice Returns whether this rule can produce the given restriction code.
+      59                 :            :      * @param restrictionCode Restriction code to test.
+      60                 :            :      * @return True if `restrictionCode` is one of this rule's codes.
+      61                 :            :      */
+      62                 :          9 :     function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) {
+      63                 :          9 :         return restrictionCode == CODE_RESERVES_EXCEEDED || restrictionCode == CODE_RESERVES_FEED_STALE
+      64                 :          5 :             || restrictionCode == CODE_RESERVES_ANSWER_INVALID || restrictionCode == CODE_RESERVES_FEED_UNAVAILABLE
+      65                 :          2 :             || restrictionCode == CODE_TOTAL_SUPPLY_UNAVAILABLE;
+      66                 :            :     }
+      67                 :            : 
+      68                 :            :     /*//////////////////////////////////////////////////////////////
+      69                 :            :                         PUBLIC FUNCTIONS
+      70                 :            :     //////////////////////////////////////////////////////////////*/
+      71                 :            : 
+      72                 :            :     /**
+      73                 :            :      * @inheritdoc IERC3643IComplianceContract
+      74                 :            :      */
+      75                 :         16 :     function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) {
+      76                 :         16 :         _transferred(from, to, value);
+      77                 :            :     }
+      78                 :            : 
+      79                 :            :     /**
+      80                 :            :      * @inheritdoc IRuleEngine
+      81                 :            :      */
+      82                 :         16 :     function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) {
+      83                 :         16 :         _transferredFrom(spender, from, to, value);
+      84                 :            :     }
+      85                 :            : 
+      86                 :            :     /**
+      87                 :            :      * @inheritdoc IERC1404
+      88                 :            :      */
+      89                 :          9 :     function messageForTransferRestriction(uint8 restrictionCode)
+      90                 :            :         public
+      91                 :            :         pure
+      92                 :            :         override(IERC1404)
+      93                 :            :         returns (string memory)
+      94                 :            :     {
+      95         [ +  + ]:          9 :         if (restrictionCode == CODE_RESERVES_EXCEEDED) {
+      96                 :          2 :             return TEXT_RESERVES_EXCEEDED;
+      97         [ +  + ]:          7 :         } else if (restrictionCode == CODE_RESERVES_FEED_STALE) {
+      98                 :          2 :             return TEXT_RESERVES_FEED_STALE;
+      99         [ +  + ]:          5 :         } else if (restrictionCode == CODE_RESERVES_ANSWER_INVALID) {
+     100                 :          2 :             return TEXT_RESERVES_ANSWER_INVALID;
+     101         [ +  + ]:          3 :         } else if (restrictionCode == CODE_RESERVES_FEED_UNAVAILABLE) {
+     102                 :          1 :             return TEXT_RESERVES_FEED_UNAVAILABLE;
+     103            [ + ]:          2 :         } else if (restrictionCode == CODE_TOTAL_SUPPLY_UNAVAILABLE) {
+     104                 :          1 :             return TEXT_TOTAL_SUPPLY_UNAVAILABLE;
+     105                 :            :         }
+     106                 :          1 :         return TEXT_CODE_NOT_FOUND;
+     107                 :            :     }
+     108                 :            : 
+     109                 :            :     /*//////////////////////////////////////////////////////////////
+     110                 :            :                         INTERNAL FUNCTIONS
+     111                 :            :     //////////////////////////////////////////////////////////////*/
+     112                 :            : 
+     113                 :            :     /**
+     114                 :            :      * @inheritdoc RuleTransferValidation
+     115                 :            :      */
+     116                 :        629 :     function _detectTransferRestriction(
+     117                 :            :         address from,
+     118                 :            :         address,
+     119                 :            :         /* to */
+     120                 :            :         uint256 value
+     121                 :            :     )
+     122                 :            :         internal
+     123                 :            :         view
+     124                 :            :         virtual
+     125                 :            :         override
+     126                 :            :         returns (uint8)
+     127                 :            :     {
+     128                 :            :         // Only mints change the total supply upwards; transfers and burns are never gated.
+     129            [ + ]:        629 :         if (from != address(0)) {
+     130                 :          9 :             return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK);
+     131                 :            :         }
+     132                 :        620 :         (uint8 restrictionCode, uint256 backedSupply) = _maxBackedSupply();
+     133            [ + ]:        620 :         if (restrictionCode != uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK)) {
+     134                 :        150 :             return restrictionCode;
+     135                 :            :         }
+     136                 :        470 :         (bool supplyAvailable, uint256 currentSupply) = _currentSupply();
+     137            [ + ]:        470 :         if (!supplyAvailable) {
+     138                 :          4 :             return CODE_TOTAL_SUPPLY_UNAVAILABLE;
+     139                 :            :         }
+     140                 :            :         // The comparison, the overflow-safety and the pre-update accounting assumption all live in
+     141                 :            :         // {CapAccounting}; the reserve figure is simply this rule's cap.
+     142            [ + ]:        466 :         if (_capExceededBy(currentSupply, backedSupply, value)) {
+     143                 :        241 :             return CODE_RESERVES_EXCEEDED;
+     144                 :            :         }
+     145                 :        225 :         return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK);
+     146                 :            :     }
+     147                 :            : 
+     148                 :            :     /**
+     149                 :            :      * @inheritdoc RuleTransferValidation
+     150                 :            :      */
+     151                 :          5 :     function _detectTransferRestrictionFrom(address, address from, address to, uint256 value)
+     152                 :            :         internal
+     153                 :            :         view
+     154                 :            :         virtual
+     155                 :            :         override
+     156                 :            :         returns (uint8)
+     157                 :            :     {
+     158                 :          5 :         return _detectTransferRestriction(from, to, value);
+     159                 :            :     }
+     160                 :            : 
+     161                 :            :     /**
+     162                 :            :      * @notice Restriction code for the NOTIFICATION phase, i.e. what the write hook enforces.
+     163                 :            :      * @dev **The seam an ERC-3643 variant overrides.** Defaults to the pre-flight check, correct for a token
+     164                 :            :      * that notifies BEFORE moving the value (CMTAT). A token that notifies AFTERWARDS reports an observation
+     165                 :            :      * that already includes `value`, and counting it again halves the effective cap; such a variant overrides
+     166                 :            :      * this with `_detectTransferRestriction(from, to, 0)`.
+     167                 :            :      *
+     168                 :            :      * The read path is deliberately NOT routed through here: a pre-flight view always runs before the movement
+     169                 :            :      * on either kind of token, so it must always count `value`.
+     170                 :            :      * @param from Sender address.
+     171                 :            :      * @param to Recipient address.
+     172                 :            :      * @param value Amount moved.
+     173                 :            :      * @return The restriction code the write hook will enforce.
+     174                 :            :      */
+     175                 :         19 :     function _detectTransferRestrictionOnNotify(address from, address to, uint256 value)
+     176                 :            :         internal
+     177                 :            :         view
+     178                 :            :         virtual
+     179                 :            :         returns (uint8)
+     180                 :            :     {
+     181                 :         19 :         return _detectTransferRestriction(from, to, value);
+     182                 :            :     }
+     183                 :            : 
+     184                 :            :     /**
+     185                 :            :      * @notice Enforces the reserve backing for a direct transfer, reverting on violation.
+     186                 :            :      * @param from Sender address; the zero address denotes a mint whose backing is checked.
+     187                 :            :      * @param to Recipient address.
+     188                 :            :      * @param value Transfer amount.
+     189                 :            :      */
+     190                 :         16 :     function _transferred(address from, address to, uint256 value) internal view virtual {
+     191                 :         16 :         uint8 code = _detectTransferRestrictionOnNotify(from, to, value);
+     192         [ +  + ]:         16 :         require(
+     193                 :            :             code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK),
+     194                 :            :             RuleChainlinkPoR_InvalidTransfer(address(this), from, to, value, code)
+     195                 :            :         );
+     196                 :            :     }
+     197                 :            : 
+     198                 :            :     /**
+     199                 :            :      * @notice Enforces the reserve backing for a `transferFrom`, reverting on violation.
+     200                 :            :      * @param spender Approved spender initiating the transfer; the minter on the mint path.
+     201                 :            :      * @param from Sender address; the zero address denotes a mint whose backing is checked.
+     202                 :            :      * @param to Recipient address.
+     203                 :            :      * @param value Transfer amount.
+     204                 :            :      */
+     205                 :         16 :     function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual {
+     206                 :         16 :         uint8 code = _detectTransferRestrictionOnNotify(from, to, value);
+     207         [ +  + ]:         16 :         require(
+     208                 :            :             code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK),
+     209                 :            :             RuleChainlinkPoR_InvalidTransferFrom(address(this), spender, from, to, value, code)
+     210                 :            :         );
+     211                 :            :     }
+     212                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func-sort-c.html index 5176cd91..fbb07196 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleERC2980Base.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleERC2980Base.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 127 - 132 - 96.2 % + 123 + 128 + 96.1 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 37 @@ -69,71 +69,67 @@ Hit count Sort by hit count - RuleERC2980Base._authorizeFrozenlistAdd + RuleERC2980Base._authorizeFrozenlistAdd 0 - RuleERC2980Base._authorizeFrozenlistRemove + RuleERC2980Base._authorizeFrozenlistRemove 0 - RuleERC2980Base._authorizeMintBurnManager + RuleERC2980Base._authorizeMintBurnManager 0 - RuleERC2980Base._authorizeWhitelistAdd + RuleERC2980Base._authorizeWhitelistAdd 0 - RuleERC2980Base._authorizeWhitelistRemove + RuleERC2980Base._authorizeWhitelistRemove 0 - RuleERC2980Base.areFrozen + RuleERC2980Base.areFrozen 1 - RuleERC2980Base.areWhitelisted + RuleERC2980Base.areWhitelisted 1 - RuleERC2980Base._msgData + RuleERC2980Base._msgData 2 RuleERC2980Base.onlyFrozenlistRemove - 2 + 3 RuleERC2980Base.removeFrozenlistAddresses - 2 - - - RuleERC2980Base.setAllowBurn 3 - RuleERC2980Base.supportsInterface + RuleERC2980Base.setAllowBurn 3 - RuleERC2980Base.frozenlistAddressCount - 4 + RuleERC2980Base.supportsInterface + 3 - RuleERC2980Base.removeWhitelistAddresses + RuleERC2980Base.frozenlistAddressCount 4 - RuleERC2980Base.transferred.1 + RuleERC2980Base.transferred.1 4 - RuleERC2980Base.canReturnTransferRestrictionCode + RuleERC2980Base.canReturnTransferRestrictionCode 5 - RuleERC2980Base.isVerified + RuleERC2980Base.isVerified 5 @@ -141,100 +137,104 @@ 5 - RuleERC2980Base.setAllowMint + RuleERC2980Base.removeWhitelistAddresses 5 - RuleERC2980Base.whitelistAddressCount + RuleERC2980Base.setAllowMint 5 - RuleERC2980Base.addFrozenlistAddresses - 6 + RuleERC2980Base.whitelistAddressCount + 5 - RuleERC2980Base.addWhitelistAddresses - 6 + RuleERC2980Base.messageForTransferRestriction + 7 - RuleERC2980Base.onlyFrozenlistAdd - 6 + RuleERC2980Base.onlyWhitelistRemove + 7 - RuleERC2980Base.onlyWhitelistAdd - 6 + RuleERC2980Base.removeFrozenlistAddress + 7 - RuleERC2980Base.transferred.0 - 6 + RuleERC2980Base.removeWhitelistAddress + 7 - RuleERC2980Base.frozenlist - 7 + RuleERC2980Base.addFrozenlistAddresses + 8 - RuleERC2980Base.messageForTransferRestriction - 7 + RuleERC2980Base.onlyFrozenlistAdd + 8 - RuleERC2980Base.onlyWhitelistRemove - 7 + RuleERC2980Base.transferred.0 + 8 - RuleERC2980Base.removeFrozenlistAddress - 7 + RuleERC2980Base.addWhitelistAddresses + 9 - RuleERC2980Base.removeWhitelistAddress - 7 + RuleERC2980Base.onlyWhitelistAdd + 9 - RuleERC2980Base._transferredFrom - 11 + RuleERC2980Base.frozenlist + 10 - RuleERC2980Base.whitelist + RuleERC2980Base._transferredFrom 11 - RuleERC2980Base.isFrozen + RuleERC2980Base.isFrozen 12 - RuleERC2980Base._transferred - 13 + RuleERC2980Base.isWhitelisted + 16 - RuleERC2980Base.isWhitelisted - 15 + RuleERC2980Base.whitelist + 17 - RuleERC2980Base._detectTransferRestrictionFrom + RuleERC2980Base._transferred + 21 + + + RuleERC2980Base._detectTransferRestrictionFrom 24 RuleERC2980Base.addFrozenlistAddress - 24 + 26 - RuleERC2980Base.addWhitelistAddress - 49 + RuleERC2980Base.addWhitelistAddress + 55 - RuleERC2980Base._detectTransferRestriction - 62 + RuleERC2980Base._detectTransferRestriction + 78 RuleERC2980Base.constructor - 75 + 87 - RuleERC2980Base._msgSender - 293 + RuleERC2980Base._msgSender + 320 - RuleERC2980Base._contextSuffixLength - 295 + RuleERC2980Base._contextSuffixLength + 322
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func.html index 11056765..0363d107 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleERC2980Base.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleERC2980Base.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 127 - 132 - 96.2 % + 123 + 128 + 96.1 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 37 @@ -69,116 +69,116 @@ Hit count Sort by hit count - RuleERC2980Base._authorizeFrozenlistAdd + RuleERC2980Base._authorizeFrozenlistAdd 0 - RuleERC2980Base._authorizeFrozenlistRemove + RuleERC2980Base._authorizeFrozenlistRemove 0 - RuleERC2980Base._authorizeMintBurnManager + RuleERC2980Base._authorizeMintBurnManager 0 - RuleERC2980Base._authorizeWhitelistAdd + RuleERC2980Base._authorizeWhitelistAdd 0 - RuleERC2980Base._authorizeWhitelistRemove + RuleERC2980Base._authorizeWhitelistRemove 0 - RuleERC2980Base._contextSuffixLength - 295 + RuleERC2980Base._contextSuffixLength + 322 - RuleERC2980Base._detectTransferRestriction - 62 + RuleERC2980Base._detectTransferRestriction + 78 - RuleERC2980Base._detectTransferRestrictionFrom + RuleERC2980Base._detectTransferRestrictionFrom 24 - RuleERC2980Base._msgData + RuleERC2980Base._msgData 2 - RuleERC2980Base._msgSender - 293 + RuleERC2980Base._msgSender + 320 - RuleERC2980Base._transferred - 13 + RuleERC2980Base._transferred + 21 - RuleERC2980Base._transferredFrom + RuleERC2980Base._transferredFrom 11 RuleERC2980Base.addFrozenlistAddress - 24 + 26 RuleERC2980Base.addFrozenlistAddresses - 6 + 8 - RuleERC2980Base.addWhitelistAddress - 49 + RuleERC2980Base.addWhitelistAddress + 55 - RuleERC2980Base.addWhitelistAddresses - 6 + RuleERC2980Base.addWhitelistAddresses + 9 - RuleERC2980Base.areFrozen + RuleERC2980Base.areFrozen 1 - RuleERC2980Base.areWhitelisted + RuleERC2980Base.areWhitelisted 1 - RuleERC2980Base.canReturnTransferRestrictionCode + RuleERC2980Base.canReturnTransferRestrictionCode 5 RuleERC2980Base.constructor - 75 + 87 - RuleERC2980Base.frozenlist - 7 + RuleERC2980Base.frozenlist + 10 - RuleERC2980Base.frozenlistAddressCount + RuleERC2980Base.frozenlistAddressCount 4 - RuleERC2980Base.isFrozen + RuleERC2980Base.isFrozen 12 - RuleERC2980Base.isVerified + RuleERC2980Base.isVerified 5 - RuleERC2980Base.isWhitelisted - 15 + RuleERC2980Base.isWhitelisted + 16 - RuleERC2980Base.messageForTransferRestriction + RuleERC2980Base.messageForTransferRestriction 7 RuleERC2980Base.onlyFrozenlistAdd - 6 + 8 RuleERC2980Base.onlyFrozenlistRemove - 2 + 3 RuleERC2980Base.onlyMintBurnManager @@ -186,54 +186,54 @@ RuleERC2980Base.onlyWhitelistAdd - 6 + 9 RuleERC2980Base.onlyWhitelistRemove 7 - RuleERC2980Base.removeFrozenlistAddress + RuleERC2980Base.removeFrozenlistAddress 7 RuleERC2980Base.removeFrozenlistAddresses - 2 + 3 RuleERC2980Base.removeWhitelistAddress 7 - RuleERC2980Base.removeWhitelistAddresses - 4 + RuleERC2980Base.removeWhitelistAddresses + 5 - RuleERC2980Base.setAllowBurn + RuleERC2980Base.setAllowBurn 3 - RuleERC2980Base.setAllowMint + RuleERC2980Base.setAllowMint 5 - RuleERC2980Base.supportsInterface + RuleERC2980Base.supportsInterface 3 - RuleERC2980Base.transferred.0 - 6 + RuleERC2980Base.transferred.0 + 8 - RuleERC2980Base.transferred.1 + RuleERC2980Base.transferred.1 4 - RuleERC2980Base.whitelist - 11 + RuleERC2980Base.whitelist + 17 - RuleERC2980Base.whitelistAddressCount + RuleERC2980Base.whitelistAddressCount 5 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.gcov.html index fca9ad03..71cdd259 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleERC2980Base.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleERC2980Base.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleERC2980Base.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 127 - 132 - 96.2 % + 123 + 128 + 96.1 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 37 @@ -132,11 +132,11 @@ 61 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address, set permanently at deployment. 62 : : * @param allowMintBurn When true, permits both minting and burning. 63 : : */ - 64 : 75 : constructor(address forwarderIrrevocable, bool allowMintBurn) MetaTxModuleStandalone(forwarderIrrevocable) { - 65 : 75 : allowMint = allowMintBurn; - 66 : 75 : allowBurn = allowMintBurn; - 67 : 75 : emit AllowMintUpdated(allowMintBurn); - 68 : 75 : emit AllowBurnUpdated(allowMintBurn); + 64 : 87 : constructor(address forwarderIrrevocable, bool allowMintBurn) MetaTxModuleStandalone(forwarderIrrevocable) { + 65 : 87 : allowMint = allowMintBurn; + 66 : 87 : allowBurn = allowMintBurn; + 67 : 87 : emit AllowMintUpdated(allowMintBurn); + 68 : 87 : emit AllowBurnUpdated(allowMintBurn); 69 : : } 70 : : 71 : : /*////////////////////////////////////////////////////////////// @@ -148,8 +148,8 @@ 77 : : _; 78 : : } 79 : : - 80 : 6 : modifier onlyWhitelistAdd() { - 81 : 6 : _authorizeWhitelistAdd(); + 80 : 9 : modifier onlyWhitelistAdd() { + 81 : 9 : _authorizeWhitelistAdd(); 82 : : _; 83 : : } 84 : : @@ -158,13 +158,13 @@ 87 : : _; 88 : : } 89 : : - 90 : 6 : modifier onlyFrozenlistAdd() { - 91 : 6 : _authorizeFrozenlistAdd(); + 90 : 8 : modifier onlyFrozenlistAdd() { + 91 : 8 : _authorizeFrozenlistAdd(); 92 : : _; 93 : : } 94 : : - 95 : 2 : modifier onlyFrozenlistRemove() { - 96 : 2 : _authorizeFrozenlistRemove(); + 95 : 3 : modifier onlyFrozenlistRemove() { + 96 : 3 : _authorizeFrozenlistRemove(); 97 : : _; 98 : : } 99 : : @@ -174,38 +174,38 @@ 103 : : 104 : : /** 105 : : * @notice Adds multiple addresses to the whitelist. - 106 : : * @dev Does not revert if an address is already listed. - 107 : : * @param targetAddresses Addresses to add to the whitelist. - 108 : : */ - 109 : 6 : function addWhitelistAddresses(address[] calldata targetAddresses) public onlyWhitelistAdd { - 110 : 4 : _addWhitelistAddresses(targetAddresses); - 111 : 4 : emit AddWhitelistAddresses(targetAddresses); - 112 : : } - 113 : : - 114 : : /** - 115 : : * @notice Removes multiple addresses from the whitelist. - 116 : : * @dev Does not revert if an address is not listed. - 117 : : * @param targetAddresses Addresses to remove from the whitelist. - 118 : : */ - 119 : 4 : function removeWhitelistAddresses(address[] calldata targetAddresses) public onlyWhitelistRemove { - 120 : 3 : _removeWhitelistAddresses(targetAddresses); - 121 : 3 : emit RemoveWhitelistAddresses(targetAddresses); - 122 : : } - 123 : : - 124 : : /** - 125 : : * @notice Adds a single address to the whitelist. - 126 : : * @dev - 127 : : * Reverts if the address is already listed. - 128 : : * Deviation from ERC-2980 `Whitelistable` example interface: the spec's `addAddressToWhitelist` - 129 : : * returns `false` on duplicates instead of reverting. This implementation follows the codebase - 130 : : * convention of reverting on invalid single-item operations. - 131 : : * @param targetAddress Address to add to the whitelist. - 132 : : */ - 133 : 49 : function addWhitelistAddress(address targetAddress) public onlyWhitelistAdd { - 134 [ + + ]: 46 : require(targetAddress != address(0), RuleERC2980_ZeroAddressNotAllowed()); - 135 [ + + ]: 45 : require(!_isWhitelisted(targetAddress), RuleERC2980_AddressAlreadyWhitelisted()); - 136 : 44 : _addWhitelistAddress(targetAddress); - 137 : 44 : emit AddWhitelistAddress(targetAddress); + 106 : : * @dev Does not revert if an address is already listed; duplicates are skipped. REVERTS on + 107 : : * `address(0)`, rejecting the whole batch -- see {addWhitelistAddress}. + 108 : : * @param targetAddresses Addresses to add to the whitelist. + 109 : : */ + 110 : 9 : function addWhitelistAddresses(address[] calldata targetAddresses) public virtual onlyWhitelistAdd { + 111 : 7 : (uint256 added, uint256 skipped) = _addWhitelistAddresses(targetAddresses); + 112 : 6 : emit AddWhitelistAddresses(targetAddresses, added, skipped); + 113 : : } + 114 : : + 115 : : /** + 116 : : * @notice Removes multiple addresses from the whitelist. + 117 : : * @dev Does not revert if an address is not listed. + 118 : : * @param targetAddresses Addresses to remove from the whitelist. + 119 : : */ + 120 : 5 : function removeWhitelistAddresses(address[] calldata targetAddresses) public virtual onlyWhitelistRemove { + 121 : 4 : (uint256 removed, uint256 skipped) = _removeWhitelistAddresses(targetAddresses); + 122 : 4 : emit RemoveWhitelistAddresses(targetAddresses, removed, skipped); + 123 : : } + 124 : : + 125 : : /** + 126 : : * @notice Adds a single address to the whitelist. + 127 : : * @dev + 128 : : * Reverts if the address is already listed. + 129 : : * Deviation from ERC-2980 `Whitelistable` example interface: the spec's `addAddressToWhitelist` + 130 : : * returns `false` on duplicates instead of reverting. This implementation follows the codebase + 131 : : * convention of reverting on invalid single-item operations. + 132 : : * @param targetAddress Address to add to the whitelist. + 133 : : */ + 134 : 55 : function addWhitelistAddress(address targetAddress) public virtual onlyWhitelistAdd { + 135 [ + + ]: 52 : require(targetAddress != address(0), RuleERC2980_ZeroAddressNotAllowed()); + 136 [ + + ]: 51 : require(_addWhitelistAddress(targetAddress), RuleERC2980_AddressAlreadyWhitelisted()); + 137 : 50 : emit AddWhitelistAddress(targetAddress); 138 : : } 139 : : 140 : : /** @@ -217,24 +217,24 @@ 146 : : * convention of reverting on invalid single-item operations. 147 : : * @param targetAddress Address to remove from the whitelist. 148 : : */ - 149 : 7 : function removeWhitelistAddress(address targetAddress) public onlyWhitelistRemove { - 150 [ + + ]: 5 : require(_isWhitelisted(targetAddress), RuleERC2980_AddressNotWhitelisted()); - 151 : 4 : _removeWhitelistAddress(targetAddress); - 152 : 4 : emit RemoveWhitelistAddress(targetAddress); - 153 : : } - 154 : : - 155 : : /*////////////////////////////////////////////////////////////// - 156 : : FROZENLIST MANAGEMENT - 157 : : //////////////////////////////////////////////////////////////*/ - 158 : : - 159 : : /** - 160 : : * @notice Adds multiple addresses to the frozenlist. - 161 : : * @dev Does not revert if an address is already listed. + 149 : 7 : function removeWhitelistAddress(address targetAddress) public virtual onlyWhitelistRemove { + 150 [ + + ]: 5 : require(_removeWhitelistAddress(targetAddress), RuleERC2980_AddressNotWhitelisted()); + 151 : 4 : emit RemoveWhitelistAddress(targetAddress); + 152 : : } + 153 : : + 154 : : /*////////////////////////////////////////////////////////////// + 155 : : FROZENLIST MANAGEMENT + 156 : : //////////////////////////////////////////////////////////////*/ + 157 : : + 158 : : /** + 159 : : * @notice Adds multiple addresses to the frozenlist. + 160 : : * @dev Does not revert if an address is already listed; duplicates are skipped. REVERTS on + 161 : : * `address(0)`, rejecting the whole batch -- see {addFrozenlistAddress}. 162 : : * @param targetAddresses Addresses to add to the frozenlist. 163 : : */ - 164 : 6 : function addFrozenlistAddresses(address[] calldata targetAddresses) public onlyFrozenlistAdd { - 165 : 4 : _addFrozenlistAddresses(targetAddresses); - 166 : 4 : emit AddFrozenlistAddresses(targetAddresses); + 164 : 8 : function addFrozenlistAddresses(address[] calldata targetAddresses) public virtual onlyFrozenlistAdd { + 165 : 6 : (uint256 added, uint256 skipped) = _addFrozenlistAddresses(targetAddresses); + 166 : 5 : emit AddFrozenlistAddresses(targetAddresses, added, skipped); 167 : : } 168 : : 169 : : /** @@ -242,9 +242,9 @@ 171 : : * @dev Does not revert if an address is not listed. 172 : : * @param targetAddresses Addresses to remove from the frozenlist. 173 : : */ - 174 : 2 : function removeFrozenlistAddresses(address[] calldata targetAddresses) public onlyFrozenlistRemove { - 175 : 2 : _removeFrozenlistAddresses(targetAddresses); - 176 : 2 : emit RemoveFrozenlistAddresses(targetAddresses); + 174 : 3 : function removeFrozenlistAddresses(address[] calldata targetAddresses) public virtual onlyFrozenlistRemove { + 175 : 3 : (uint256 removed, uint256 skipped) = _removeFrozenlistAddresses(targetAddresses); + 176 : 3 : emit RemoveFrozenlistAddresses(targetAddresses, removed, skipped); 177 : : } 178 : : 179 : : /** @@ -256,334 +256,332 @@ 185 : : * convention of reverting on invalid single-item operations. 186 : : * @param targetAddress Address to add to the frozenlist. 187 : : */ - 188 : 24 : function addFrozenlistAddress(address targetAddress) public onlyFrozenlistAdd { - 189 [ + + ]: 21 : require(targetAddress != address(0), RuleERC2980_ZeroAddressNotAllowed()); - 190 [ + + ]: 20 : require(!_isFrozen(targetAddress), RuleERC2980_AddressAlreadyFrozen()); - 191 : 19 : _addFrozenlistAddress(targetAddress); - 192 : 19 : emit AddFrozenlistAddress(targetAddress); - 193 : : } - 194 : : - 195 : : /** - 196 : : * @notice Removes a single address from the frozenlist. - 197 : : * @dev - 198 : : * Reverts if the address is not listed. - 199 : : * Deviation from ERC-2980 `Freezable` example interface: the spec's `removeAddressFromFrozenlist` - 200 : : * returns `false` when not found instead of reverting. This implementation follows the codebase - 201 : : * convention of reverting on invalid single-item operations. - 202 : : * @param targetAddress Address to remove from the frozenlist. - 203 : : */ - 204 : 7 : function removeFrozenlistAddress(address targetAddress) public onlyFrozenlistRemove { - 205 [ + + ]: 5 : require(_isFrozen(targetAddress), RuleERC2980_AddressNotFrozen()); - 206 : 4 : _removeFrozenlistAddress(targetAddress); - 207 : 4 : emit RemoveFrozenlistAddress(targetAddress); - 208 : : } - 209 : : - 210 : : /*////////////////////////////////////////////////////////////// - 211 : : PUBLIC FUNCTIONS - 212 : : //////////////////////////////////////////////////////////////*/ - 213 : : - 214 : : /** - 215 : : * @notice Enables or disables minting through this rule. - 216 : : * @param value The new value of the `allowMint` flag. - 217 : : */ - 218 : 5 : function setAllowMint(bool value) public virtual onlyMintBurnManager { - 219 : 3 : allowMint = value; - 220 : 3 : emit AllowMintUpdated(value); - 221 : : } - 222 : : - 223 : : /** - 224 : : * @notice Enables or disables burning through this rule. - 225 : : * @param value The new value of the `allowBurn` flag. - 226 : : */ - 227 : 3 : function setAllowBurn(bool value) public virtual onlyMintBurnManager { - 228 : 2 : allowBurn = value; - 229 : 2 : emit AllowBurnUpdated(value); - 230 : : } - 231 : : - 232 : : /** - 233 : : * @inheritdoc IERC3643IComplianceContract - 234 : : */ - 235 : 6 : function transferred(address from, address to, uint256 value) - 236 : : public - 237 : : view - 238 : : virtual - 239 : : override(IERC3643IComplianceContract) - 240 : : { - 241 : 6 : _transferred(from, to, value); - 242 : : } - 243 : : - 244 : : /** - 245 : : * @inheritdoc IRuleEngine - 246 : : */ - 247 : 4 : function transferred(address spender, address from, address to, uint256 value) - 248 : : public - 249 : : view - 250 : : virtual - 251 : : override(IRuleEngine) - 252 : : { - 253 : 4 : _transferredFrom(spender, from, to, value); - 254 : : } - 255 : : - 256 : : /** - 257 : : * @inheritdoc IRule - 258 : : */ - 259 : 5 : function canReturnTransferRestrictionCode(uint8 restrictionCode) - 260 : : public - 261 : : pure - 262 : : virtual - 263 : : override(IRule) - 264 : : returns (bool) - 265 : : { - 266 : 5 : return restrictionCode == CODE_ADDRESS_FROM_IS_FROZEN || restrictionCode == CODE_ADDRESS_TO_IS_FROZEN - 267 : 3 : || restrictionCode == CODE_ADDRESS_SPENDER_IS_FROZEN || restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED - 268 : 1 : || restrictionCode == CODE_MINT_NOT_ALLOWED || restrictionCode == CODE_BURN_NOT_ALLOWED; - 269 : : } - 270 : : - 271 : : /** - 272 : : * @inheritdoc IERC1404 - 273 : : */ - 274 : 7 : function messageForTransferRestriction(uint8 restrictionCode) - 275 : : public - 276 : : pure - 277 : : virtual - 278 : : override(IERC1404) - 279 : : returns (string memory) - 280 : : { - 281 [ + + ]: 7 : if (restrictionCode == CODE_ADDRESS_FROM_IS_FROZEN) { - 282 : 1 : return TEXT_ADDRESS_FROM_IS_FROZEN; - 283 [ + + ]: 6 : } else if (restrictionCode == CODE_ADDRESS_TO_IS_FROZEN) { - 284 : 1 : return TEXT_ADDRESS_TO_IS_FROZEN; - 285 [ + + ]: 5 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_IS_FROZEN) { - 286 : 1 : return TEXT_ADDRESS_SPENDER_IS_FROZEN; - 287 [ + + ]: 4 : } else if (restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED) { - 288 : 1 : return TEXT_ADDRESS_TO_NOT_WHITELISTED; - 289 [ + + ]: 3 : } else if (restrictionCode == CODE_MINT_NOT_ALLOWED) { - 290 : 1 : return TEXT_MINT_NOT_ALLOWED; - 291 [ + + ]: 2 : } else if (restrictionCode == CODE_BURN_NOT_ALLOWED) { - 292 : 1 : return TEXT_BURN_NOT_ALLOWED; - 293 : : } else { - 294 : 1 : return TEXT_CODE_NOT_FOUND; - 295 : : } - 296 : : } - 297 : : - 298 : : /** - 299 : : * @inheritdoc RuleTransferValidation - 300 : : */ - 301 : 3 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { - 302 : 3 : return RuleTransferValidation.supportsInterface(interfaceId); - 303 : : } - 304 : : - 305 : : /** - 306 : : * @notice Returns the number of whitelisted addresses. - 307 : : * @return The count of addresses currently in the whitelist. - 308 : : */ - 309 : 5 : function whitelistAddressCount() public view returns (uint256) { - 310 : 5 : return _whitelistCount(); - 311 : : } - 312 : : - 313 : : /** - 314 : : * @notice Returns true if the address is in the whitelist. - 315 : : * @param targetAddress Address to check. - 316 : : * @return True if the address is whitelisted. - 317 : : */ - 318 : 15 : function isWhitelisted(address targetAddress) public view returns (bool) { - 319 : 15 : return _isWhitelisted(targetAddress); - 320 : : } - 321 : : - 322 : : /** - 323 : : * @notice ERC-2980 getter: returns true if the address is whitelisted. - 324 : : * @param _operator Address to check. - 325 : : * @return True if the address is whitelisted. - 326 : : */ - 327 : 11 : function whitelist(address _operator) public view virtual override(IERC2980) returns (bool) { - 328 : 11 : return _isWhitelisted(_operator); - 329 : : } - 330 : : - 331 : : /** - 332 : : * @notice Returns true if the address is whitelisted (identity-verified). - 333 : : * @dev Reflects whitelist membership only. Frozen status is intentionally excluded: - 334 : : * freezing is a temporary enforcement action and does not revoke identity verification. - 335 : : * @param targetAddress Address to check. - 336 : : * @return True if the address is whitelisted. - 337 : : */ - 338 : 5 : function isVerified(address targetAddress) public view virtual override(IIdentityRegistryVerified) returns (bool) { - 339 : 5 : return _isWhitelisted(targetAddress); - 340 : : } - 341 : : - 342 : : /** - 343 : : * @notice Checks multiple addresses for whitelist membership. - 344 : : * @param targetAddresses Addresses to check. - 345 : : * @return results Array of booleans, true where the corresponding address is whitelisted. - 346 : : */ - 347 : 1 : function areWhitelisted(address[] memory targetAddresses) public view returns (bool[] memory results) { - 348 : 1 : results = new bool[](targetAddresses.length); - 349 : 1 : for (uint256 i = 0; i < targetAddresses.length; ++i) { - 350 : 2 : results[i] = _isWhitelisted(targetAddresses[i]); - 351 : : } - 352 : : } - 353 : : - 354 : : /** - 355 : : * @notice Returns the number of frozen addresses. - 356 : : * @return The count of addresses currently in the frozenlist. - 357 : : */ - 358 : 4 : function frozenlistAddressCount() public view returns (uint256) { - 359 : 4 : return _frozenlistCount(); - 360 : : } - 361 : : - 362 : : /** - 363 : : * @notice Returns true if the address is in the frozenlist. - 364 : : * @param targetAddress Address to check. - 365 : : * @return True if the address is frozen. - 366 : : */ - 367 : 12 : function isFrozen(address targetAddress) public view returns (bool) { - 368 : 12 : return _isFrozen(targetAddress); - 369 : : } - 370 : : - 371 : : /** - 372 : : * @notice ERC-2980 getter: returns true if the address is frozen. - 373 : : * @param _operator Address to check. - 374 : : * @return True if the address is frozen. - 375 : : */ - 376 : 7 : function frozenlist(address _operator) public view virtual override(IERC2980) returns (bool) { - 377 : 7 : return _isFrozen(_operator); - 378 : : } - 379 : : - 380 : : /** - 381 : : * @notice Checks multiple addresses for frozenlist membership. - 382 : : * @param targetAddresses Addresses to check. - 383 : : * @return results Array of booleans, true where the corresponding address is frozen. - 384 : : */ - 385 : 1 : function areFrozen(address[] memory targetAddresses) public view returns (bool[] memory results) { - 386 : 1 : results = new bool[](targetAddresses.length); - 387 : 1 : for (uint256 i = 0; i < targetAddresses.length; ++i) { - 388 : 2 : results[i] = _isFrozen(targetAddresses[i]); - 389 : : } - 390 : : } - 391 : : - 392 : : /*////////////////////////////////////////////////////////////// - 393 : : INTERNAL FUNCTIONS - 394 : : //////////////////////////////////////////////////////////////*/ - 395 : : - 396 : : /** - 397 : : * @notice Authorization hook invoked before toggling `allowMint` / `allowBurn`. - 398 : : */ - 399 : 0 : function _authorizeMintBurnManager() internal view virtual; - 400 : : - 401 : : /** - 402 : : * @notice Authorization hook invoked before adding addresses to the whitelist. - 403 : : */ - 404 : 0 : function _authorizeWhitelistAdd() internal view virtual; - 405 : : /** - 406 : : * @notice Authorization hook invoked before removing addresses from the whitelist. - 407 : : */ - 408 : 0 : function _authorizeWhitelistRemove() internal view virtual; - 409 : : /** - 410 : : * @notice Authorization hook invoked before adding addresses to the frozenlist. - 411 : : */ - 412 : 0 : function _authorizeFrozenlistAdd() internal view virtual; - 413 : : /** - 414 : : * @notice Authorization hook invoked before removing addresses from the frozenlist. - 415 : : */ - 416 : 0 : function _authorizeFrozenlistRemove() internal view virtual; - 417 : : - 418 : : /** - 419 : : * @inheritdoc RuleTransferValidation - 420 : : */ - 421 : 62 : function _detectTransferRestriction( - 422 : : address from, - 423 : : address to, - 424 : : uint256 /* value */ - 425 : : ) - 426 : : internal - 427 : : view - 428 : : virtual - 429 : : override - 430 : : returns (uint8) - 431 : : { - 432 : 62 : bool isMint = from == address(0); - 433 : 62 : bool isBurn = to == address(0); - 434 : : - 435 : : // Gate the mint/burn OPERATION explicitly, rather than by whitelisting the zero address. - 436 [ + ]: 62 : if (isMint && !allowMint) { - 437 : 1 : return CODE_MINT_NOT_ALLOWED; - 438 : : } - 439 [ + ]: 61 : if (isBurn && !allowBurn) { - 440 : 2 : return CODE_BURN_NOT_ALLOWED; - 441 : : } - 442 : : - 443 : : // Frozenlist check has priority — but only for REAL participants. - 444 [ + ]: 59 : if (!isMint && _isFrozen(from)) { - 445 : 20 : return CODE_ADDRESS_FROM_IS_FROZEN; - 446 : : } - 447 [ + ]: 39 : if (!isBurn && _isFrozen(to)) { - 448 : 4 : return CODE_ADDRESS_TO_IS_FROZEN; - 449 : : } - 450 : : // Whitelist check: only the recipient must be whitelisted (ERC-2980); no recipient on a burn. - 451 [ + ]: 35 : if (!isBurn && !_isWhitelisted(to)) { - 452 : 5 : return CODE_ADDRESS_TO_NOT_WHITELISTED; - 453 : : } - 454 : 30 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 455 : : } - 456 : : - 457 : : /** - 458 : : * @inheritdoc RuleTransferValidation - 459 : : */ - 460 : 24 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 461 : : internal - 462 : : view - 463 : : virtual - 464 : : override - 465 : : returns (uint8) - 466 : : { - 467 [ + ]: 24 : if (_isFrozen(spender)) { - 468 : 4 : return CODE_ADDRESS_SPENDER_IS_FROZEN; - 469 : : } - 470 : 20 : return _detectTransferRestriction(from, to, value); - 471 : : } - 472 : : - 473 : : /** - 474 : : * @inheritdoc RuleNFTAdapter - 475 : : */ - 476 : 13 : function _transferred(address from, address to, uint256 value) internal view virtual override { - 477 : 13 : uint8 code = _detectTransferRestriction(from, to, value); - 478 [ + + ]: 13 : require( - 479 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 480 : : RuleERC2980_InvalidTransfer(address(this), from, to, value, code) - 481 : : ); - 482 : : } - 483 : : - 484 : : /** - 485 : : * @inheritdoc RuleNFTAdapter - 486 : : */ - 487 : 11 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { - 488 : 11 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 489 [ + + ]: 11 : require( - 490 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 491 : : RuleERC2980_InvalidTransferFrom(address(this), spender, from, to, value, code) - 492 : : ); - 493 : : } - 494 : : - 495 : : /** - 496 : : * @inheritdoc ERC2771Context - 497 : : */ - 498 : 293 : function _msgSender() internal view virtual override(ERC2771Context) returns (address sender) { - 499 : 293 : return ERC2771Context._msgSender(); - 500 : : } - 501 : : - 502 : : /** - 503 : : * @inheritdoc ERC2771Context - 504 : : */ - 505 : 2 : function _msgData() internal view virtual override(ERC2771Context) returns (bytes calldata) { - 506 : 2 : return ERC2771Context._msgData(); - 507 : : } - 508 : : - 509 : : /** - 510 : : * @inheritdoc ERC2771Context - 511 : : */ - 512 : 295 : function _contextSuffixLength() internal view virtual override(ERC2771Context) returns (uint256) { - 513 : 295 : return ERC2771Context._contextSuffixLength(); - 514 : : } - 515 : : } + 188 : 26 : function addFrozenlistAddress(address targetAddress) public virtual onlyFrozenlistAdd { + 189 [ + + ]: 23 : require(targetAddress != address(0), RuleERC2980_ZeroAddressNotAllowed()); + 190 [ + + ]: 22 : require(_addFrozenlistAddress(targetAddress), RuleERC2980_AddressAlreadyFrozen()); + 191 : 21 : emit AddFrozenlistAddress(targetAddress); + 192 : : } + 193 : : + 194 : : /** + 195 : : * @notice Removes a single address from the frozenlist. + 196 : : * @dev + 197 : : * Reverts if the address is not listed. + 198 : : * Deviation from ERC-2980 `Freezable` example interface: the spec's `removeAddressFromFrozenlist` + 199 : : * returns `false` when not found instead of reverting. This implementation follows the codebase + 200 : : * convention of reverting on invalid single-item operations. + 201 : : * @param targetAddress Address to remove from the frozenlist. + 202 : : */ + 203 : 7 : function removeFrozenlistAddress(address targetAddress) public virtual onlyFrozenlistRemove { + 204 [ + + ]: 5 : require(_removeFrozenlistAddress(targetAddress), RuleERC2980_AddressNotFrozen()); + 205 : 4 : emit RemoveFrozenlistAddress(targetAddress); + 206 : : } + 207 : : + 208 : : /*////////////////////////////////////////////////////////////// + 209 : : PUBLIC FUNCTIONS + 210 : : //////////////////////////////////////////////////////////////*/ + 211 : : + 212 : : /** + 213 : : * @notice Enables or disables minting through this rule. + 214 : : * @param value The new value of the `allowMint` flag. + 215 : : */ + 216 : 5 : function setAllowMint(bool value) public virtual onlyMintBurnManager { + 217 : 3 : allowMint = value; + 218 : 3 : emit AllowMintUpdated(value); + 219 : : } + 220 : : + 221 : : /** + 222 : : * @notice Enables or disables burning through this rule. + 223 : : * @param value The new value of the `allowBurn` flag. + 224 : : */ + 225 : 3 : function setAllowBurn(bool value) public virtual onlyMintBurnManager { + 226 : 2 : allowBurn = value; + 227 : 2 : emit AllowBurnUpdated(value); + 228 : : } + 229 : : + 230 : : /** + 231 : : * @inheritdoc IERC3643IComplianceContract + 232 : : */ + 233 : 8 : function transferred(address from, address to, uint256 value) + 234 : : public + 235 : : view + 236 : : virtual + 237 : : override(IERC3643IComplianceContract) + 238 : : { + 239 : 8 : _transferred(from, to, value); + 240 : : } + 241 : : + 242 : : /** + 243 : : * @inheritdoc IRuleEngine + 244 : : */ + 245 : 4 : function transferred(address spender, address from, address to, uint256 value) + 246 : : public + 247 : : view + 248 : : virtual + 249 : : override(IRuleEngine) + 250 : : { + 251 : 4 : _transferredFrom(spender, from, to, value); + 252 : : } + 253 : : + 254 : : /** + 255 : : * @inheritdoc IRule + 256 : : */ + 257 : 5 : function canReturnTransferRestrictionCode(uint8 restrictionCode) + 258 : : public + 259 : : pure + 260 : : virtual + 261 : : override(IRule) + 262 : : returns (bool) + 263 : : { + 264 : 5 : return restrictionCode == CODE_ADDRESS_FROM_IS_FROZEN || restrictionCode == CODE_ADDRESS_TO_IS_FROZEN + 265 : 3 : || restrictionCode == CODE_ADDRESS_SPENDER_IS_FROZEN || restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED + 266 : 1 : || restrictionCode == CODE_MINT_NOT_ALLOWED || restrictionCode == CODE_BURN_NOT_ALLOWED; + 267 : : } + 268 : : + 269 : : /** + 270 : : * @inheritdoc IERC1404 + 271 : : */ + 272 : 7 : function messageForTransferRestriction(uint8 restrictionCode) + 273 : : public + 274 : : pure + 275 : : virtual + 276 : : override(IERC1404) + 277 : : returns (string memory) + 278 : : { + 279 [ + + ]: 7 : if (restrictionCode == CODE_ADDRESS_FROM_IS_FROZEN) { + 280 : 1 : return TEXT_ADDRESS_FROM_IS_FROZEN; + 281 [ + + ]: 6 : } else if (restrictionCode == CODE_ADDRESS_TO_IS_FROZEN) { + 282 : 1 : return TEXT_ADDRESS_TO_IS_FROZEN; + 283 [ + + ]: 5 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_IS_FROZEN) { + 284 : 1 : return TEXT_ADDRESS_SPENDER_IS_FROZEN; + 285 [ + + ]: 4 : } else if (restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED) { + 286 : 1 : return TEXT_ADDRESS_TO_NOT_WHITELISTED; + 287 [ + + ]: 3 : } else if (restrictionCode == CODE_MINT_NOT_ALLOWED) { + 288 : 1 : return TEXT_MINT_NOT_ALLOWED; + 289 [ + + ]: 2 : } else if (restrictionCode == CODE_BURN_NOT_ALLOWED) { + 290 : 1 : return TEXT_BURN_NOT_ALLOWED; + 291 : : } else { + 292 : 1 : return TEXT_CODE_NOT_FOUND; + 293 : : } + 294 : : } + 295 : : + 296 : : /** + 297 : : * @inheritdoc RuleTransferValidation + 298 : : */ + 299 : 3 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { + 300 : 3 : return RuleTransferValidation.supportsInterface(interfaceId); + 301 : : } + 302 : : + 303 : : /** + 304 : : * @notice Returns the number of whitelisted addresses. + 305 : : * @return The count of addresses currently in the whitelist. + 306 : : */ + 307 : 5 : function whitelistAddressCount() public view returns (uint256) { + 308 : 5 : return _whitelistCount(); + 309 : : } + 310 : : + 311 : : /** + 312 : : * @notice Returns true if the address is in the whitelist. + 313 : : * @param targetAddress Address to check. + 314 : : * @return True if the address is whitelisted. + 315 : : */ + 316 : 16 : function isWhitelisted(address targetAddress) public view returns (bool) { + 317 : 16 : return _isWhitelisted(targetAddress); + 318 : : } + 319 : : + 320 : : /** + 321 : : * @notice ERC-2980 getter: returns true if the address is whitelisted. + 322 : : * @param _operator Address to check. + 323 : : * @return True if the address is whitelisted. + 324 : : */ + 325 : 17 : function whitelist(address _operator) public view virtual override(IERC2980) returns (bool) { + 326 : 17 : return _isWhitelisted(_operator); + 327 : : } + 328 : : + 329 : : /** + 330 : : * @notice Returns true if the address is whitelisted (identity-verified). + 331 : : * @dev Reflects whitelist membership only. Frozen status is intentionally excluded: + 332 : : * freezing is a temporary enforcement action and does not revoke identity verification. + 333 : : * @param targetAddress Address to check. + 334 : : * @return True if the address is whitelisted. + 335 : : */ + 336 : 5 : function isVerified(address targetAddress) public view virtual override(IIdentityRegistryVerified) returns (bool) { + 337 : 5 : return _isWhitelisted(targetAddress); + 338 : : } + 339 : : + 340 : : /** + 341 : : * @notice Checks multiple addresses for whitelist membership. + 342 : : * @param targetAddresses Addresses to check. + 343 : : * @return results Array of booleans, true where the corresponding address is whitelisted. + 344 : : */ + 345 : 1 : function areWhitelisted(address[] memory targetAddresses) public view returns (bool[] memory results) { + 346 : 1 : results = new bool[](targetAddresses.length); + 347 : 1 : for (uint256 i = 0; i < targetAddresses.length; ++i) { + 348 : 2 : results[i] = _isWhitelisted(targetAddresses[i]); + 349 : : } + 350 : : } + 351 : : + 352 : : /** + 353 : : * @notice Returns the number of frozen addresses. + 354 : : * @return The count of addresses currently in the frozenlist. + 355 : : */ + 356 : 4 : function frozenlistAddressCount() public view returns (uint256) { + 357 : 4 : return _frozenlistCount(); + 358 : : } + 359 : : + 360 : : /** + 361 : : * @notice Returns true if the address is in the frozenlist. + 362 : : * @param targetAddress Address to check. + 363 : : * @return True if the address is frozen. + 364 : : */ + 365 : 12 : function isFrozen(address targetAddress) public view returns (bool) { + 366 : 12 : return _isFrozen(targetAddress); + 367 : : } + 368 : : + 369 : : /** + 370 : : * @notice ERC-2980 getter: returns true if the address is frozen. + 371 : : * @param _operator Address to check. + 372 : : * @return True if the address is frozen. + 373 : : */ + 374 : 10 : function frozenlist(address _operator) public view virtual override(IERC2980) returns (bool) { + 375 : 10 : return _isFrozen(_operator); + 376 : : } + 377 : : + 378 : : /** + 379 : : * @notice Checks multiple addresses for frozenlist membership. + 380 : : * @param targetAddresses Addresses to check. + 381 : : * @return results Array of booleans, true where the corresponding address is frozen. + 382 : : */ + 383 : 1 : function areFrozen(address[] memory targetAddresses) public view returns (bool[] memory results) { + 384 : 1 : results = new bool[](targetAddresses.length); + 385 : 1 : for (uint256 i = 0; i < targetAddresses.length; ++i) { + 386 : 2 : results[i] = _isFrozen(targetAddresses[i]); + 387 : : } + 388 : : } + 389 : : + 390 : : /*////////////////////////////////////////////////////////////// + 391 : : INTERNAL FUNCTIONS + 392 : : //////////////////////////////////////////////////////////////*/ + 393 : : + 394 : : /** + 395 : : * @notice Authorization hook invoked before toggling `allowMint` / `allowBurn`. + 396 : : */ + 397 : 0 : function _authorizeMintBurnManager() internal view virtual; + 398 : : + 399 : : /** + 400 : : * @notice Authorization hook invoked before adding addresses to the whitelist. + 401 : : */ + 402 : 0 : function _authorizeWhitelistAdd() internal view virtual; + 403 : : /** + 404 : : * @notice Authorization hook invoked before removing addresses from the whitelist. + 405 : : */ + 406 : 0 : function _authorizeWhitelistRemove() internal view virtual; + 407 : : /** + 408 : : * @notice Authorization hook invoked before adding addresses to the frozenlist. + 409 : : */ + 410 : 0 : function _authorizeFrozenlistAdd() internal view virtual; + 411 : : /** + 412 : : * @notice Authorization hook invoked before removing addresses from the frozenlist. + 413 : : */ + 414 : 0 : function _authorizeFrozenlistRemove() internal view virtual; + 415 : : + 416 : : /** + 417 : : * @inheritdoc RuleTransferValidation + 418 : : */ + 419 : 78 : function _detectTransferRestriction( + 420 : : address from, + 421 : : address to, + 422 : : uint256 /* value */ + 423 : : ) + 424 : : internal + 425 : : view + 426 : : virtual + 427 : : override + 428 : : returns (uint8) + 429 : : { + 430 : 78 : bool isMint = from == address(0); + 431 : 78 : bool isBurn = to == address(0); + 432 : : + 433 : : // Gate the mint/burn OPERATION explicitly, rather than by whitelisting the zero address. + 434 [ + ]: 78 : if (isMint && !allowMint) { + 435 : 1 : return CODE_MINT_NOT_ALLOWED; + 436 : : } + 437 [ + ]: 77 : if (isBurn && !allowBurn) { + 438 : 2 : return CODE_BURN_NOT_ALLOWED; + 439 : : } + 440 : : + 441 : : // Frozenlist check has priority — but only for REAL participants. + 442 [ + ]: 75 : if (!isMint && _isFrozen(from)) { + 443 : 28 : return CODE_ADDRESS_FROM_IS_FROZEN; + 444 : : } + 445 [ + ]: 47 : if (!isBurn && _isFrozen(to)) { + 446 : 4 : return CODE_ADDRESS_TO_IS_FROZEN; + 447 : : } + 448 : : // Whitelist check: only the recipient must be whitelisted (ERC-2980); no recipient on a burn. + 449 [ + ]: 43 : if (!isBurn && !_isWhitelisted(to)) { + 450 : 5 : return CODE_ADDRESS_TO_NOT_WHITELISTED; + 451 : : } + 452 : 38 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 453 : : } + 454 : : + 455 : : /** + 456 : : * @inheritdoc RuleTransferValidation + 457 : : */ + 458 : 24 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 459 : : internal + 460 : : view + 461 : : virtual + 462 : : override + 463 : : returns (uint8) + 464 : : { + 465 [ + ]: 24 : if (_isFrozen(spender)) { + 466 : 4 : return CODE_ADDRESS_SPENDER_IS_FROZEN; + 467 : : } + 468 : 20 : return _detectTransferRestriction(from, to, value); + 469 : : } + 470 : : + 471 : : /** + 472 : : * @inheritdoc RuleNFTAdapter + 473 : : */ + 474 : 21 : function _transferred(address from, address to, uint256 value) internal view virtual override { + 475 : 21 : uint8 code = _detectTransferRestriction(from, to, value); + 476 [ + + ]: 21 : require( + 477 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 478 : : RuleERC2980_InvalidTransfer(address(this), from, to, value, code) + 479 : : ); + 480 : : } + 481 : : + 482 : : /** + 483 : : * @inheritdoc RuleNFTAdapter + 484 : : */ + 485 : 11 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { + 486 : 11 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + 487 [ + + ]: 11 : require( + 488 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 489 : : RuleERC2980_InvalidTransferFrom(address(this), spender, from, to, value, code) + 490 : : ); + 491 : : } + 492 : : + 493 : : /** + 494 : : * @inheritdoc ERC2771Context + 495 : : */ + 496 : 320 : function _msgSender() internal view virtual override(ERC2771Context) returns (address sender) { + 497 : 320 : return ERC2771Context._msgSender(); + 498 : : } + 499 : : + 500 : : /** + 501 : : * @inheritdoc ERC2771Context + 502 : : */ + 503 : 2 : function _msgData() internal view virtual override(ERC2771Context) returns (bytes calldata) { + 504 : 2 : return ERC2771Context._msgData(); + 505 : : } + 506 : : + 507 : : /** + 508 : : * @inheritdoc ERC2771Context + 509 : : */ + 510 : 322 : function _contextSuffixLength() internal view virtual override(ERC2771Context) returns (uint256) { + 511 : 322 : return ERC2771Context._contextSuffixLength(); + 512 : : } + 513 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func-sort-c.html index 1cee4d70..90914198 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: + 62 63 - 64 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 14 @@ -49,8 +49,8 @@ Branches: - 19 - 19 + 18 + 18 100.0 % @@ -69,64 +69,64 @@ Hit count Sort by hit count - RuleIdentityRegistryBase._authorizeIdentityRegistryManager + RuleIdentityRegistryBase._authorizeIdentityRegistryManager 0 - RuleIdentityRegistryBase.setCheckSender - 2 + RuleIdentityRegistryBase.setCheckSender + 3 - RuleIdentityRegistryBase.canReturnTransferRestrictionCode + RuleIdentityRegistryBase.canReturnTransferRestrictionCode 4 - RuleIdentityRegistryBase.messageForTransferRestriction + RuleIdentityRegistryBase.messageForTransferRestriction 4 - RuleIdentityRegistryBase.setIdentityRegistry + RuleIdentityRegistryBase.setIdentityRegistry 4 - RuleIdentityRegistryBase.clearIdentityRegistry + RuleIdentityRegistryBase.clearIdentityRegistry 5 - RuleIdentityRegistryBase.onlyIdentityRegistryManager + RuleIdentityRegistryBase.onlyIdentityRegistryManager 5 - RuleIdentityRegistryBase.setCheckSpender - 5 + RuleIdentityRegistryBase.setCheckSpender + 6 - RuleIdentityRegistryBase.transferred.0 - 5 + RuleIdentityRegistryBase.transferred.0 + 14 - RuleIdentityRegistryBase.transferred.1 - 7 + RuleIdentityRegistryBase.transferred.1 + 23 - RuleIdentityRegistryBase._transferred - 11 + RuleIdentityRegistryBase._transferred + 26 - RuleIdentityRegistryBase._transferredFrom - 13 + RuleIdentityRegistryBase._transferredFrom + 29 - RuleIdentityRegistryBase._detectTransferRestrictionFrom - 31 + RuleIdentityRegistryBase._detectTransferRestrictionFrom + 57 - RuleIdentityRegistryBase.constructor - 39 + RuleIdentityRegistryBase.constructor + 64 - RuleIdentityRegistryBase._detectTransferRestriction - 62 + RuleIdentityRegistryBase._detectTransferRestriction + 121
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func.html index c8835b63..b1d93707 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: + 62 63 - 64 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 14 @@ -49,8 +49,8 @@ Branches: - 19 - 19 + 18 + 18 100.0 % @@ -69,64 +69,64 @@ Hit count Sort by hit count - RuleIdentityRegistryBase._authorizeIdentityRegistryManager + RuleIdentityRegistryBase._authorizeIdentityRegistryManager 0 - RuleIdentityRegistryBase._detectTransferRestriction - 62 + RuleIdentityRegistryBase._detectTransferRestriction + 121 - RuleIdentityRegistryBase._detectTransferRestrictionFrom - 31 + RuleIdentityRegistryBase._detectTransferRestrictionFrom + 57 - RuleIdentityRegistryBase._transferred - 11 + RuleIdentityRegistryBase._transferred + 26 - RuleIdentityRegistryBase._transferredFrom - 13 + RuleIdentityRegistryBase._transferredFrom + 29 - RuleIdentityRegistryBase.canReturnTransferRestrictionCode + RuleIdentityRegistryBase.canReturnTransferRestrictionCode 4 - RuleIdentityRegistryBase.clearIdentityRegistry + RuleIdentityRegistryBase.clearIdentityRegistry 5 - RuleIdentityRegistryBase.constructor - 39 + RuleIdentityRegistryBase.constructor + 64 - RuleIdentityRegistryBase.messageForTransferRestriction + RuleIdentityRegistryBase.messageForTransferRestriction 4 - RuleIdentityRegistryBase.onlyIdentityRegistryManager + RuleIdentityRegistryBase.onlyIdentityRegistryManager 5 - RuleIdentityRegistryBase.setCheckSender - 2 + RuleIdentityRegistryBase.setCheckSender + 3 - RuleIdentityRegistryBase.setCheckSpender - 5 + RuleIdentityRegistryBase.setCheckSpender + 6 - RuleIdentityRegistryBase.setIdentityRegistry + RuleIdentityRegistryBase.setIdentityRegistry 4 - RuleIdentityRegistryBase.transferred.0 - 5 + RuleIdentityRegistryBase.transferred.0 + 14 - RuleIdentityRegistryBase.transferred.1 - 7 + RuleIdentityRegistryBase.transferred.1 + 23
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.gcov.html index 99639cd1..24d105d6 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: + 62 63 - 64 98.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 14 @@ -49,8 +49,8 @@ Branches: - 19 - 19 + 18 + 18 100.0 % @@ -82,267 +82,269 @@ 11 : : /** 12 : : * @title RuleIdentityRegistryBase 13 : : * @notice Checks the ERC-3643 Identity Registry for transfer participants when configured. - 14 : : * @dev **ERC-3643 conformant by default.** The specification mandates that ONLY THE RECEIVER be - 15 : : * identity-verified: - 16 : : * - 17 : : * - "The receiver MUST be whitelisted on the Identity Registry and verified" (§ Transfer) - 18 : : * - "`transferFrom` works the same way" (§ Transfer) - 19 : : * - "`mint` and `forcedTransfer` only require the receiver to be whitelisted - 20 : : * and verified on the Identity Registry" (§ Transfer) - 21 : : * - "The `burn` function bypasses all checks on eligibility" (§ Transfer) - 22 : : * - 23 : : * The sender, the spender and the minter are NOT required to be verified. Checking the sender - 24 : : * in particular would TRAP DE-LISTED HOLDERS: ERC-3643 screens only the receiver precisely so - 25 : : * that an investor whose identity lapses (expired claim, revoked identity) can still exit their - 26 : : * position by sending to a verified counterparty. - 27 : : * - 28 : : * Stricter screening remains available, but as an EXPLICIT OPT-IN ({checkSender}, - 29 : : * {checkSpender}) rather than an undocumented default. - 30 : : */ - 31 : : abstract contract RuleIdentityRegistryBase is RuleNFTAdapter, RuleIdentityRegistryInvariantStorage { - 32 : : /** - 33 : : * @notice The ERC-3643 Identity Registry consulted to verify transfer participants; the zero address disables checks. - 34 : : */ - 35 : : IIdentityRegistryVerified public identityRegistry; - 36 : : - 37 : : /** - 38 : : * @notice When true, ALSO require the sender to be identity-verified. - 39 : : * @dev Defaults to FALSE: ERC-3643 does not require it. Enabling it is stricter than the - 40 : : * specification and prevents a de-listed holder from exiting their position. + 14 : : * @dev **ERC-3643 conformant by default: only the RECEIVER is verified.** The spec states the + 15 : : * receiver must be whitelisted and verified, that `transferFrom` works the same way, that + 16 : : * `mint` and `forcedTransfer` require only the receiver, and that `burn` bypasses eligibility. + 17 : : * + 18 : : * The sender, spender and minter are NOT required to be verified. Checking the sender would + 19 : : * TRAP DE-LISTED HOLDERS: the spec screens only the receiver precisely so an investor whose + 20 : : * identity lapses can still exit to a verified counterparty. Stricter screening is available as + 21 : : * an explicit opt-in ({checkSender}, {checkSpender}), not an undocumented default. + 22 : : */ + 23 : : abstract contract RuleIdentityRegistryBase is RuleNFTAdapter, RuleIdentityRegistryInvariantStorage { + 24 : : /** + 25 : : * @notice The ERC-3643 Identity Registry consulted to verify transfer participants; the zero address disables checks. + 26 : : */ + 27 : : IIdentityRegistryVerified public identityRegistry; + 28 : : + 29 : : /** + 30 : : * @notice When true, ALSO require the sender to be identity-verified. + 31 : : * @dev Defaults to FALSE: ERC-3643 does not require it. Enabling it is stricter than the + 32 : : * specification and prevents a de-listed holder from exiting their position. + 33 : : */ + 34 : : bool public checkSender; + 35 : : + 36 : : /** + 37 : : * @notice When true, ALSO require the spender to be identity-verified on `transferFrom`. + 38 : : * @dev Defaults to FALSE: ERC-3643 does not require it ("`transferFrom` works the same way"). + 39 : : * Mint and burn are exempt from this check regardless — the minter/burner acts on its own + 40 : : * authority, not as a delegated ERC-20 spender. 41 : : */ - 42 : : bool public checkSender; + 42 : : bool public checkSpender; 43 : : - 44 : : /** - 45 : : * @notice When true, ALSO require the spender to be identity-verified on `transferFrom`. - 46 : : * @dev Defaults to FALSE: ERC-3643 does not require it ("`transferFrom` works the same way"). - 47 : : * Mint and burn are exempt from this check regardless — the minter/burner acts on its own - 48 : : * authority, not as a delegated ERC-20 spender. - 49 : : */ - 50 : : bool public checkSpender; - 51 : : - 52 : : /*////////////////////////////////////////////////////////////// - 53 : : CONSTRUCTOR - 54 : : //////////////////////////////////////////////////////////////*/ - 55 : : - 56 : : /** - 57 : : * @notice Initializes the rule with an optional identity registry. - 58 : : * @dev Pass `false, false` for the ERC-3643-conformant default (only the receiver is verified). - 59 : : * @param identityRegistry_ Identity registry address; when the zero address, the registry is left unset (checks disabled). - 60 : : * @param checkSender_ When true, also verify the sender (STRICTER than ERC-3643). - 61 : : * @param checkSpender_ When true, also verify the spender on `transferFrom` (STRICTER than ERC-3643). - 62 : : */ - 63 : 39 : constructor(address identityRegistry_, bool checkSender_, bool checkSpender_) { - 64 [ + ]: 39 : if (identityRegistry_ != address(0)) { - 65 : 37 : identityRegistry = IIdentityRegistryVerified(identityRegistry_); - 66 : : } - 67 : 39 : checkSender = checkSender_; - 68 : 39 : checkSpender = checkSpender_; - 69 : 39 : emit IdentityCheckSenderUpdated(checkSender_); - 70 : 39 : emit IdentityCheckSpenderUpdated(checkSpender_); - 71 : : } - 72 : : - 73 : : /*////////////////////////////////////////////////////////////// - 74 : : ACCESS CONTROL - 75 : : //////////////////////////////////////////////////////////////*/ - 76 : : - 77 : 5 : modifier onlyIdentityRegistryManager() { - 78 : 5 : _authorizeIdentityRegistryManager(); - 79 : : _; - 80 : : } - 81 : : - 82 : : /*////////////////////////////////////////////////////////////// - 83 : : EXTERNAL FUNCTIONS - 84 : : //////////////////////////////////////////////////////////////*/ - 85 : : - 86 : : /** - 87 : : * @notice Returns whether this rule can produce the given restriction code. - 88 : : * @param restrictionCode Restriction code to test. - 89 : : * @return True if `restrictionCode` is one of this rule's identity-verification codes. - 90 : : */ - 91 : 4 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) { - 92 : 4 : return restrictionCode == CODE_ADDRESS_FROM_NOT_VERIFIED || restrictionCode == CODE_ADDRESS_TO_NOT_VERIFIED - 93 : 2 : || restrictionCode == CODE_ADDRESS_SPENDER_NOT_VERIFIED; - 94 : : } - 95 : : - 96 : : /*////////////////////////////////////////////////////////////// - 97 : : PUBLIC FUNCTIONS - 98 : : //////////////////////////////////////////////////////////////*/ - 99 : : - 100 : : /** - 101 : : * @notice Sets the identity registry consulted during transfer checks. - 102 : : * @param newRegistry New identity registry address; must not be the zero address. - 103 : : */ - 104 : 4 : function setIdentityRegistry(address newRegistry) public onlyIdentityRegistryManager { - 105 [ + + ]: 2 : require(newRegistry != address(0), RuleIdentityRegistry_RegistryAddressZeroNotAllowed()); - 106 : 1 : identityRegistry = IIdentityRegistryVerified(newRegistry); - 107 : 1 : emit IdentityRegistryUpdated(newRegistry); - 108 : : } - 109 : : - 110 : : /** - 111 : : * @notice Enables or disables the (non-ERC-3643) sender verification check. - 112 : : * @dev STRICTER than ERC-3643, which verifies only the receiver. Enabling this prevents a - 113 : : * de-listed holder from exiting their position. - 114 : : * @param value The new value of the `checkSender` flag. - 115 : : */ - 116 : 2 : function setCheckSender(bool value) public virtual onlyIdentityRegistryManager { - 117 : 2 : checkSender = value; - 118 : 2 : emit IdentityCheckSenderUpdated(value); - 119 : : } - 120 : : - 121 : : /** - 122 : : * @notice Enables or disables the (non-ERC-3643) spender verification check on `transferFrom`. - 123 : : * @dev STRICTER than ERC-3643. Mint and burn remain exempt regardless. - 124 : : * @param value The new value of the `checkSpender` flag. - 125 : : */ - 126 : 5 : function setCheckSpender(bool value) public virtual onlyIdentityRegistryManager { - 127 : 5 : checkSpender = value; - 128 : 5 : emit IdentityCheckSpenderUpdated(value); - 129 : : } - 130 : : - 131 : : /** - 132 : : * @notice Clears the identity registry, disabling identity checks (all transfers pass this rule). - 133 : : */ - 134 : 5 : function clearIdentityRegistry() public onlyIdentityRegistryManager { - 135 : 3 : identityRegistry = IIdentityRegistryVerified(address(0)); - 136 : 3 : emit IdentityRegistryUpdated(address(0)); - 137 : : } - 138 : : - 139 : : /** - 140 : : * @inheritdoc IERC3643IComplianceContract - 141 : : */ - 142 : 5 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { - 143 : 5 : _transferred(from, to, value); - 144 : : } - 145 : : - 146 : : /** - 147 : : * @inheritdoc IRuleEngine - 148 : : */ - 149 : 7 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { - 150 : 7 : _transferredFrom(spender, from, to, value); - 151 : : } - 152 : : - 153 : : /** - 154 : : * @inheritdoc IERC1404 - 155 : : */ - 156 : 4 : function messageForTransferRestriction(uint8 restrictionCode) - 157 : : public - 158 : : pure - 159 : : override(IERC1404) - 160 : : returns (string memory) - 161 : : { - 162 [ + + ]: 4 : if (restrictionCode == CODE_ADDRESS_FROM_NOT_VERIFIED) { - 163 : 1 : return TEXT_ADDRESS_FROM_NOT_VERIFIED; - 164 [ + + ]: 3 : } else if (restrictionCode == CODE_ADDRESS_TO_NOT_VERIFIED) { - 165 : 1 : return TEXT_ADDRESS_TO_NOT_VERIFIED; - 166 [ + ]: 2 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_NOT_VERIFIED) { - 167 : 1 : return TEXT_ADDRESS_SPENDER_NOT_VERIFIED; - 168 : : } - 169 : 1 : return TEXT_CODE_NOT_FOUND; - 170 : : } - 171 : : - 172 : : /*////////////////////////////////////////////////////////////// - 173 : : INTERNAL FUNCTIONS - 174 : : //////////////////////////////////////////////////////////////*/ - 175 : : - 176 : : /** - 177 : : * @notice Authorization hook invoked before updating or clearing the identity registry. - 178 : : */ - 179 : 0 : function _authorizeIdentityRegistryManager() internal view virtual; - 180 : : - 181 : : /** - 182 : : * @notice Detects the restriction code for a direct transfer, verifying `from` and `to` against the registry. - 183 : : * @param from Sender address; must be verified unless it is the zero address (mint). - 184 : : * @param to Recipient address; must be verified unless it is the zero address (burn). - 185 : : * @return The applicable restriction code, or TRANSFER_OK when no restriction applies. - 186 : : */ - 187 : 62 : function _detectTransferRestriction( - 188 : : address from, - 189 : : address to, - 190 : : uint256 /* value */ - 191 : : ) - 192 : : internal - 193 : : view - 194 : : override - 195 : : returns (uint8) - 196 : : { - 197 [ + ]: 62 : if (address(identityRegistry) == address(0)) { - 198 : 3 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 199 : : } - 200 : : // ERC-3643: "The `burn` function bypasses all checks on eligibility." - 201 [ + ]: 59 : if (to == address(0)) { - 202 : 3 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 203 : : } - 204 : : - 205 : : // OPT-IN, stricter than ERC-3643. Mints carry no sender, so they are exempt. - 206 [ + ]: 56 : if (checkSender && from != address(0) && !identityRegistry.isVerified(from)) { - 207 : 1 : return CODE_ADDRESS_FROM_NOT_VERIFIED; - 208 : : } - 209 : : - 210 : : // MANDATED by ERC-3643: the receiver must be verified. This is the only required check, - 211 : : // and it applies identically to `transfer`, `transferFrom` and `mint`. - 212 [ + ]: 55 : if (!identityRegistry.isVerified(to)) { - 213 : 6 : return CODE_ADDRESS_TO_NOT_VERIFIED; - 214 : : } - 215 : 49 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 216 : : } - 217 : : - 218 : : /** - 219 : : * @notice Detects the restriction code for a `transferFrom`, verifying `spender` and delegating to the direct check. - 220 : : * @param spender Approved spender initiating the transfer; must be verified unless it is the zero address. - 221 : : * @param from Sender address, forwarded to the direct transfer check. - 222 : : * @param to Recipient address, forwarded to the direct transfer check. - 223 : : * @param value Transfer amount, forwarded to the direct transfer check. - 224 : : * @return The applicable restriction code, or TRANSFER_OK when no restriction applies. - 225 : : */ - 226 : 31 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 227 : : internal - 228 : : view - 229 : : override - 230 : : returns (uint8) - 231 : : { - 232 [ + ]: 31 : if (address(identityRegistry) == address(0)) { - 233 : 1 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 234 : : } - 235 : : // ERC-3643: burn bypasses all eligibility checks. - 236 [ + ]: 30 : if (to == address(0)) { - 237 : 2 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 238 : : } - 239 : : - 240 : : // OPT-IN, stricter than ERC-3643 ("`transferFrom` works the same way" — receiver only). - 241 : : // Mint (from == 0) and burn (to == 0) are exempt: the minter/burner acts on its own - 242 : : // authority, not as a delegated ERC-20 spender. This is what makes an unverified MINTER - 243 : : // able to mint to a verified recipient, exactly as the specification requires. - 244 : : if ( - 245 : 5 : checkSpender && spender != address(0) && from != address(0) && to != address(0) - 246 : 4 : && !identityRegistry.isVerified(spender) - 247 [ + ]: 3 : ) { - 248 : 3 : return CODE_ADDRESS_SPENDER_NOT_VERIFIED; - 249 : : } - 250 : 25 : return _detectTransferRestriction(from, to, value); - 251 : : } - 252 : : - 253 : : /** - 254 : : * @inheritdoc RuleNFTAdapter - 255 : : */ - 256 : 11 : function _transferred(address from, address to, uint256 value) internal view virtual override { - 257 : 11 : uint8 code = _detectTransferRestriction(from, to, value); - 258 [ + + ]: 11 : require( - 259 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 260 : : RuleIdentityRegistry_InvalidTransfer(address(this), from, to, value, code) - 261 : : ); - 262 : : } - 263 : : - 264 : : /** - 265 : : * @inheritdoc RuleNFTAdapter - 266 : : */ - 267 : 13 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { - 268 : 13 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 269 [ + + ]: 13 : require( - 270 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 271 : : RuleIdentityRegistry_InvalidTransferFrom(address(this), spender, from, to, value, code) - 272 : : ); - 273 : : } - 274 : : } + 44 : : /*////////////////////////////////////////////////////////////// + 45 : : CONSTRUCTOR + 46 : : //////////////////////////////////////////////////////////////*/ + 47 : : + 48 : : /** + 49 : : * @notice Initializes the rule with an optional identity registry. + 50 : : * @dev Pass `false, false` for the ERC-3643-conformant default (only the receiver is verified). + 51 : : * @param identityRegistry_ Identity registry address; when the zero address, the registry is left unset (checks disabled). + 52 : : * @param checkSender_ When true, also verify the sender (STRICTER than ERC-3643). + 53 : : * @param checkSpender_ When true, also verify the spender on `transferFrom` (STRICTER than ERC-3643). + 54 : : */ + 55 : 64 : constructor(address identityRegistry_, bool checkSender_, bool checkSpender_) { + 56 : : // Every value actually assigned here is announced, so the deployed configuration can be + 57 : : // reconstructed from events alone. The registry is only assigned when non-zero -- a zero + 58 : : // argument leaves the default untouched, so there is nothing to report, matching + 59 : : // {RuleSanctionsListBase}'s constructor. + 60 [ + ]: 64 : if (identityRegistry_ != address(0)) { + 61 : 57 : identityRegistry = IIdentityRegistryVerified(identityRegistry_); + 62 : 57 : emit IdentityRegistryUpdated(identityRegistry_); + 63 : : } + 64 : 64 : checkSender = checkSender_; + 65 : 64 : checkSpender = checkSpender_; + 66 : 64 : emit IdentityCheckSenderUpdated(checkSender_); + 67 : 64 : emit IdentityCheckSpenderUpdated(checkSpender_); + 68 : : } + 69 : : + 70 : : /*////////////////////////////////////////////////////////////// + 71 : : ACCESS CONTROL + 72 : : //////////////////////////////////////////////////////////////*/ + 73 : : + 74 : 5 : modifier onlyIdentityRegistryManager() { + 75 : 5 : _authorizeIdentityRegistryManager(); + 76 : : _; + 77 : : } + 78 : : + 79 : : /*////////////////////////////////////////////////////////////// + 80 : : EXTERNAL FUNCTIONS + 81 : : //////////////////////////////////////////////////////////////*/ + 82 : : + 83 : : /** + 84 : : * @notice Returns whether this rule can produce the given restriction code. + 85 : : * @param restrictionCode Restriction code to test. + 86 : : * @return True if `restrictionCode` is one of this rule's identity-verification codes. + 87 : : */ + 88 : 4 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) { + 89 : 4 : return restrictionCode == CODE_ADDRESS_FROM_NOT_VERIFIED || restrictionCode == CODE_ADDRESS_TO_NOT_VERIFIED + 90 : 2 : || restrictionCode == CODE_ADDRESS_SPENDER_NOT_VERIFIED; + 91 : : } + 92 : : + 93 : : /*////////////////////////////////////////////////////////////// + 94 : : PUBLIC FUNCTIONS + 95 : : //////////////////////////////////////////////////////////////*/ + 96 : : + 97 : : /** + 98 : : * @notice Sets the identity registry consulted during transfer checks. + 99 : : * @param newRegistry New identity registry address; must not be the zero address. + 100 : : */ + 101 : 4 : function setIdentityRegistry(address newRegistry) public virtual onlyIdentityRegistryManager { + 102 [ + + ]: 2 : require(newRegistry != address(0), RuleIdentityRegistry_RegistryAddressZeroNotAllowed()); + 103 : 1 : identityRegistry = IIdentityRegistryVerified(newRegistry); + 104 : 1 : emit IdentityRegistryUpdated(newRegistry); + 105 : : } + 106 : : + 107 : : /** + 108 : : * @notice Enables or disables the (non-ERC-3643) sender verification check. + 109 : : * @dev STRICTER than ERC-3643, which verifies only the receiver. Enabling this prevents a + 110 : : * de-listed holder from exiting their position. + 111 : : * @param value The new value of the `checkSender` flag. + 112 : : */ + 113 : 3 : function setCheckSender(bool value) public virtual onlyIdentityRegistryManager { + 114 : 3 : checkSender = value; + 115 : 3 : emit IdentityCheckSenderUpdated(value); + 116 : : } + 117 : : + 118 : : /** + 119 : : * @notice Enables or disables the (non-ERC-3643) spender verification check on `transferFrom`. + 120 : : * @dev STRICTER than ERC-3643. Mint and burn remain exempt regardless. + 121 : : * @param value The new value of the `checkSpender` flag. + 122 : : */ + 123 : 6 : function setCheckSpender(bool value) public virtual onlyIdentityRegistryManager { + 124 : 6 : checkSpender = value; + 125 : 6 : emit IdentityCheckSpenderUpdated(value); + 126 : : } + 127 : : + 128 : : /** + 129 : : * @notice Clears the identity registry, disabling identity checks (all transfers pass this rule). + 130 : : */ + 131 : 5 : function clearIdentityRegistry() public virtual onlyIdentityRegistryManager { + 132 : 3 : identityRegistry = IIdentityRegistryVerified(address(0)); + 133 : 3 : emit IdentityRegistryUpdated(address(0)); + 134 : : } + 135 : : + 136 : : /** + 137 : : * @inheritdoc IERC3643IComplianceContract + 138 : : */ + 139 : 14 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { + 140 : 14 : _transferred(from, to, value); + 141 : : } + 142 : : + 143 : : /** + 144 : : * @inheritdoc IRuleEngine + 145 : : */ + 146 : 23 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { + 147 : 23 : _transferredFrom(spender, from, to, value); + 148 : : } + 149 : : + 150 : : /** + 151 : : * @inheritdoc IERC1404 + 152 : : */ + 153 : 4 : function messageForTransferRestriction(uint8 restrictionCode) + 154 : : public + 155 : : pure + 156 : : override(IERC1404) + 157 : : returns (string memory) + 158 : : { + 159 [ + + ]: 4 : if (restrictionCode == CODE_ADDRESS_FROM_NOT_VERIFIED) { + 160 : 1 : return TEXT_ADDRESS_FROM_NOT_VERIFIED; + 161 [ + + ]: 3 : } else if (restrictionCode == CODE_ADDRESS_TO_NOT_VERIFIED) { + 162 : 1 : return TEXT_ADDRESS_TO_NOT_VERIFIED; + 163 [ + ]: 2 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_NOT_VERIFIED) { + 164 : 1 : return TEXT_ADDRESS_SPENDER_NOT_VERIFIED; + 165 : : } + 166 : 1 : return TEXT_CODE_NOT_FOUND; + 167 : : } + 168 : : + 169 : : /*////////////////////////////////////////////////////////////// + 170 : : INTERNAL FUNCTIONS + 171 : : //////////////////////////////////////////////////////////////*/ + 172 : : + 173 : : /** + 174 : : * @notice Authorization hook invoked before updating or clearing the identity registry. + 175 : : */ + 176 : 0 : function _authorizeIdentityRegistryManager() internal view virtual; + 177 : : + 178 : : /** + 179 : : * @notice Detects the restriction code for a direct transfer, verifying `from` and `to` against the registry. + 180 : : * @param from Sender address; must be verified unless it is the zero address (mint). + 181 : : * @param to Recipient address; must be verified unless it is the zero address (burn). + 182 : : * @return The applicable restriction code, or TRANSFER_OK when no restriction applies. + 183 : : */ + 184 : 121 : function _detectTransferRestriction( + 185 : : address from, + 186 : : address to, + 187 : : uint256 /* value */ + 188 : : ) + 189 : : internal + 190 : : view + 191 : : virtual + 192 : : override + 193 : : returns (uint8) + 194 : : { + 195 : : // Read the registry address once. Safe to cache across the calls below: this function is + 196 : : // `view`, so those are STATICCALLs and cannot write `identityRegistry`. + 197 : 121 : IIdentityRegistryVerified registry = identityRegistry; + 198 [ + ]: 121 : if (address(registry) == address(0)) { + 199 : 10 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 200 : : } + 201 : : // ERC-3643: "The `burn` function bypasses all checks on eligibility." + 202 [ + ]: 111 : if (to == address(0)) { + 203 : 10 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 204 : : } + 205 : : + 206 : : // OPT-IN, stricter than ERC-3643. Mints carry no sender, so they are exempt. + 207 [ + ]: 101 : if (checkSender && from != address(0) && !registry.isVerified(from)) { + 208 : 2 : return CODE_ADDRESS_FROM_NOT_VERIFIED; + 209 : : } + 210 : : + 211 : : // MANDATED by ERC-3643: the receiver must be verified. This is the only required check, + 212 : : // and it applies identically to `transfer`, `transferFrom` and `mint`. + 213 [ + ]: 99 : if (!registry.isVerified(to)) { + 214 : 13 : return CODE_ADDRESS_TO_NOT_VERIFIED; + 215 : : } + 216 : 86 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 217 : : } + 218 : : + 219 : : /** + 220 : : * @notice Detects the restriction code for a `transferFrom`, verifying `spender` and delegating to the direct check. + 221 : : * @param spender Approved spender initiating the transfer; must be verified unless it is the zero address. + 222 : : * @param from Sender address, forwarded to the direct transfer check. + 223 : : * @param to Recipient address, forwarded to the direct transfer check. + 224 : : * @param value Transfer amount, forwarded to the direct transfer check. + 225 : : * @return The applicable restriction code, or TRANSFER_OK when no restriction applies. + 226 : : */ + 227 : 57 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 228 : : internal + 229 : : view + 230 : : virtual + 231 : : override + 232 : : returns (uint8) + 233 : : { + 234 : 57 : IIdentityRegistryVerified registry = identityRegistry; + 235 : : // The guard scopes ONLY the spender check; the delegation is unconditional, as in + 236 : : // {RuleSanctionsListBase}. Returning TRANSFER_OK here instead would silently drop any check a + 237 : : // subclass adds by overriding {_detectTransferRestriction} alone. An unset registry and a burn + 238 : : // (to == 0) both resolve to TRANSFER_OK inside the delegate, so no answer changes. + 239 [ + ]: 57 : if (address(registry) == address(0) || to == address(0)) { + 240 : 11 : return _detectTransferRestriction(from, to, value); + 241 : : } + 242 : : + 243 : : // OPT-IN, stricter than ERC-3643 ("`transferFrom` works the same way" — receiver only). + 244 : : // Mint (from == 0) is exempt: the minter acts on its own authority, not as a delegated + 245 : : // ERC-20 spender. This is what makes an unverified MINTER able to mint to a verified + 246 : : // recipient, exactly as the specification requires. + 247 : : // Burn (to == 0) never reaches this line -- the guard above delegates it -- so do NOT + 248 : : // re-test `to` here; the condition would be dead. + 249 [ + ]: 46 : if (checkSpender && spender != address(0) && from != address(0) && !registry.isVerified(spender)) { + 250 : 5 : return CODE_ADDRESS_SPENDER_NOT_VERIFIED; + 251 : : } + 252 : 41 : return _detectTransferRestriction(from, to, value); + 253 : : } + 254 : : + 255 : : /** + 256 : : * @inheritdoc RuleNFTAdapter + 257 : : */ + 258 : 26 : function _transferred(address from, address to, uint256 value) internal view virtual override { + 259 : 26 : uint8 code = _detectTransferRestriction(from, to, value); + 260 [ + + ]: 26 : require( + 261 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 262 : : RuleIdentityRegistry_InvalidTransfer(address(this), from, to, value, code) + 263 : : ); + 264 : : } + 265 : : + 266 : : /** + 267 : : * @inheritdoc RuleNFTAdapter + 268 : : */ + 269 : 29 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { + 270 : 29 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + 271 [ + + ]: 29 : require( + 272 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 273 : : RuleIdentityRegistry_InvalidTransferFrom(address(this), spender, from, to, value, code) + 274 : : ); + 275 : : } + 276 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.func-sort-c.html new file mode 100644 index 00000000..71d2d747 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.func-sort-c.html @@ -0,0 +1,125 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleMaxBalanceBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleMaxBalanceBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:3737100.0 %
Date:2026-08-19 15:38:25Functions:1111100.0 %
Branches:1010100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxBalanceBase._detectTransferRestrictionFrom2
RuleMaxBalanceBase.canReturnTransferRestrictionCode3
RuleMaxBalanceBase.messageForTransferRestriction3
RuleMaxBalanceBase._transferred5
RuleMaxBalanceBase.remainingCapacity5
RuleMaxBalanceBase.transferred.05
RuleMaxBalanceBase._transferredFrom15
RuleMaxBalanceBase.transferred.115
RuleMaxBalanceBase._detectTransferRestrictionOnNotify18
RuleMaxBalanceBase._detectTransferRestriction46
RuleMaxBalanceBase.constructor63
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.func.html new file mode 100644 index 00000000..e63f644a --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.func.html @@ -0,0 +1,125 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleMaxBalanceBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleMaxBalanceBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:3737100.0 %
Date:2026-08-19 15:38:25Functions:1111100.0 %
Branches:1010100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxBalanceBase._detectTransferRestriction46
RuleMaxBalanceBase._detectTransferRestrictionFrom2
RuleMaxBalanceBase._detectTransferRestrictionOnNotify18
RuleMaxBalanceBase._transferred5
RuleMaxBalanceBase._transferredFrom15
RuleMaxBalanceBase.canReturnTransferRestrictionCode3
RuleMaxBalanceBase.constructor63
RuleMaxBalanceBase.messageForTransferRestriction3
RuleMaxBalanceBase.remainingCapacity5
RuleMaxBalanceBase.transferred.05
RuleMaxBalanceBase.transferred.115
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.gcov.html new file mode 100644 index 00000000..73e1dc0c --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol.gcov.html @@ -0,0 +1,295 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleMaxBalanceBase.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleMaxBalanceBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:3737100.0 %
Date:2026-08-19 15:38:25Functions:1111100.0 %
Branches:1010100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol";
+       5                 :            : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol";
+       6                 :            : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol";
+       7                 :            : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol";
+       8                 :            : import {BalanceCapManager} from "../core/BalanceCapManager.sol";
+       9                 :            : 
+      10                 :            : /**
+      11                 :            :  * @title RuleMaxBalanceBase
+      12                 :            :  * @notice Caps how many tokens a single address may hold, with an operator-managed exemption list.
+      13                 :            :  * @dev The rule half: constructor, ERC-1404 / ERC-3643 surface, and the mapping from a breached cap
+      14                 :            :  * to a restriction code; the cap itself lives in {BalanceCapManager}. Screens the **receiver** --
+      15                 :            :  * rejected when `balanceOf(to) + value > maxBalance`, mints included. Burns and the sender are not.
+      16                 :            :  *
+      17                 :            :  * WARNING: **the cap counts tokens per address, so splitting a position across wallets defeats it.**
+      18                 :            :  * Pair it with a rule tying addresses to identities (`RuleWhitelist`, `RuleReceiverWhitelist`,
+      19                 :            :  * `RuleIdentityRegistry`) *and* admit one address per investor.
+      20                 :            :  *
+      21                 :            :  * @dev **Assumes the token calls this BEFORE moving the value**, so `balanceOf(to)` still excludes
+      22                 :            :  * `value`. CMTAT does; a token notifying afterwards would halve the effective cap. Pinned by
+      23                 :            :  * `testMintExactlyToTheCapProvesPreUpdateAccounting`.
+      24                 :            :  *
+      25                 :            :  * @dev `maxBalance = 0` forbids holding entirely; it does not disable the rule. The read path must
+      26                 :            :  * never revert: an unreadable balance yields {CODE_BALANCE_UNAVAILABLE}
+      27                 :            :  * (fail-closed). Burns and exempt receivers resolve before any balance is read.
+      28                 :            :  */
+      29                 :            : abstract contract RuleMaxBalanceBase is RuleTransferValidation, BalanceCapManager {
+      30                 :            :     /*//////////////////////////////////////////////////////////////
+      31                 :            :                              CONSTRUCTOR
+      32                 :            :     //////////////////////////////////////////////////////////////*/
+      33                 :            : 
+      34                 :            :     /**
+      35                 :            :      * @notice Initializes the rule with the observed token and the per-holder cap.
+      36                 :            :      * @dev Routes through {BalanceCapManager}'s internals, which are constructor-agnostic, so the
+      37                 :            :      * initial configuration is announced by {MaxBalanceTokenUpdated} and {MaxBalanceUpdated} exactly
+      38                 :            :      * like every later change. An upgradeable variant would call the same two from an initializer.
+      39                 :            :      * @param balanceToken_ Token whose `balanceOf` is checked; must be a contract.
+      40                 :            :      * @param maxBalance_ Maximum balance per non-exempt address. `0` forbids holding entirely.
+      41                 :            :      */
+      42                 :         63 :     constructor(address balanceToken_, uint256 maxBalance_) {
+      43                 :         63 :         _setBalanceToken(balanceToken_);
+      44                 :         60 :         _setMaxBalance(maxBalance_);
+      45                 :            :     }
+      46                 :            : 
+      47                 :            :     /*//////////////////////////////////////////////////////////////
+      48                 :            :                         EXTERNAL FUNCTIONS
+      49                 :            :     //////////////////////////////////////////////////////////////*/
+      50                 :            : 
+      51                 :            :     /**
+      52                 :            :      * @notice Returns whether this rule can produce the given restriction code.
+      53                 :            :      * @param restrictionCode Restriction code to test.
+      54                 :            :      * @return True if `restrictionCode` is one of this rule's codes.
+      55                 :            :      */
+      56                 :          3 :     function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) {
+      57                 :          3 :         return restrictionCode == CODE_MAX_BALANCE_EXCEEDED || restrictionCode == CODE_BALANCE_UNAVAILABLE;
+      58                 :            :     }
+      59                 :            : 
+      60                 :            :     /*//////////////////////////////////////////////////////////////
+      61                 :            :                         PUBLIC FUNCTIONS
+      62                 :            :     //////////////////////////////////////////////////////////////*/
+      63                 :            : 
+      64                 :            :     /**
+      65                 :            :      * @notice Returns the balance `to` may still receive before reaching the cap.
+      66                 :            :      * @dev Mirrors what {_detectTransferRestriction} computes, so an integrator can size a transfer
+      67                 :            :      * without simulating it. Never reverts. This is the ERC-1404-flavoured wrapper over
+      68                 :            :      * {BalanceCapManager._remainingCapacity}: the manager answers in booleans, the rule maps that to
+      69                 :            :      * a restriction code.
+      70                 :            :      * @param to The prospective receiver.
+      71                 :            :      * @return restrictionCode `0` when the headroom is meaningful, otherwise the code a transfer
+      72                 :            :      * would return.
+      73                 :            :      * @return headroom Remaining capacity in token units. `type(uint256).max` for an exempt address
+      74                 :            :      * or the burn sentinel; meaningless when `restrictionCode` is non-zero.
+      75                 :            :      */
+      76                 :          5 :     function remainingCapacity(address to) public view virtual returns (uint8 restrictionCode, uint256 headroom) {
+      77                 :          5 :         (bool balanceAvailable, uint256 headroom_) = _remainingCapacity(to);
+      78            [ + ]:          5 :         if (!balanceAvailable) {
+      79                 :          1 :             return (CODE_BALANCE_UNAVAILABLE, 0);
+      80                 :            :         }
+      81                 :          4 :         return (uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), headroom_);
+      82                 :            :     }
+      83                 :            : 
+      84                 :            :     /**
+      85                 :            :      * @inheritdoc IERC3643IComplianceContract
+      86                 :            :      */
+      87                 :          5 :     function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) {
+      88                 :          5 :         _transferred(from, to, value);
+      89                 :            :     }
+      90                 :            : 
+      91                 :            :     /**
+      92                 :            :      * @inheritdoc IRuleEngine
+      93                 :            :      */
+      94                 :         15 :     function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) {
+      95                 :         15 :         _transferredFrom(spender, from, to, value);
+      96                 :            :     }
+      97                 :            : 
+      98                 :            :     /**
+      99                 :            :      * @inheritdoc IERC1404
+     100                 :            :      */
+     101                 :          3 :     function messageForTransferRestriction(uint8 restrictionCode)
+     102                 :            :         public
+     103                 :            :         pure
+     104                 :            :         override(IERC1404)
+     105                 :            :         returns (string memory)
+     106                 :            :     {
+     107         [ +  + ]:          3 :         if (restrictionCode == CODE_MAX_BALANCE_EXCEEDED) {
+     108                 :          1 :             return TEXT_MAX_BALANCE_EXCEEDED;
+     109            [ + ]:          2 :         } else if (restrictionCode == CODE_BALANCE_UNAVAILABLE) {
+     110                 :          1 :             return TEXT_BALANCE_UNAVAILABLE;
+     111                 :            :         }
+     112                 :          1 :         return TEXT_CODE_NOT_FOUND;
+     113                 :            :     }
+     114                 :            : 
+     115                 :            :     /*//////////////////////////////////////////////////////////////
+     116                 :            :                         INTERNAL FUNCTIONS
+     117                 :            :     //////////////////////////////////////////////////////////////*/
+     118                 :            : 
+     119                 :            :     /**
+     120                 :            :      * @inheritdoc RuleTransferValidation
+     121                 :            :      */
+     122                 :         46 :     function _detectTransferRestriction(
+     123                 :            :         address,
+     124                 :            :         /* from */
+     125                 :            :         address to,
+     126                 :            :         uint256 value
+     127                 :            :     )
+     128                 :            :         internal
+     129                 :            :         view
+     130                 :            :         virtual
+     131                 :            :         override
+     132                 :            :         returns (uint8)
+     133                 :            :     {
+     134                 :         46 :         (bool balanceAvailable, bool exceeded) = _capExceeded(to, value);
+     135            [ + ]:         46 :         if (!balanceAvailable) {
+     136                 :          2 :             return CODE_BALANCE_UNAVAILABLE;
+     137                 :            :         }
+     138            [ + ]:         17 :         if (exceeded) {
+     139                 :         17 :             return CODE_MAX_BALANCE_EXCEEDED;
+     140                 :            :         }
+     141                 :         27 :         return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK);
+     142                 :            :     }
+     143                 :            : 
+     144                 :            :     /**
+     145                 :            :      * @inheritdoc RuleTransferValidation
+     146                 :            :      * @dev The spender is irrelevant: the cap constrains who ends up holding the tokens, not who
+     147                 :            :      * moved them.
+     148                 :            :      */
+     149                 :          2 :     function _detectTransferRestrictionFrom(address, address from, address to, uint256 value)
+     150                 :            :         internal
+     151                 :            :         view
+     152                 :            :         virtual
+     153                 :            :         override
+     154                 :            :         returns (uint8)
+     155                 :            :     {
+     156                 :          2 :         return _detectTransferRestriction(from, to, value);
+     157                 :            :     }
+     158                 :            : 
+     159                 :            :     /**
+     160                 :            :      * @notice Restriction code for the NOTIFICATION phase, i.e. what the write hook enforces.
+     161                 :            :      * @dev **The seam an ERC-3643 variant overrides.** Defaults to the pre-flight check, correct for a token
+     162                 :            :      * that notifies BEFORE moving the value (CMTAT). A token that notifies AFTERWARDS reports an observation
+     163                 :            :      * that already includes `value`, and counting it again halves the effective cap; such a variant overrides
+     164                 :            :      * this with `_detectTransferRestriction(from, to, 0)`.
+     165                 :            :      *
+     166                 :            :      * The read path is deliberately NOT routed through here: a pre-flight view always runs before the movement
+     167                 :            :      * on either kind of token, so it must always count `value`.
+     168                 :            :      * @param from Sender address.
+     169                 :            :      * @param to Recipient address.
+     170                 :            :      * @param value Amount moved.
+     171                 :            :      * @return The restriction code the write hook will enforce.
+     172                 :            :      */
+     173                 :         18 :     function _detectTransferRestrictionOnNotify(address from, address to, uint256 value)
+     174                 :            :         internal
+     175                 :            :         view
+     176                 :            :         virtual
+     177                 :            :         returns (uint8)
+     178                 :            :     {
+     179                 :         18 :         return _detectTransferRestriction(from, to, value);
+     180                 :            :     }
+     181                 :            : 
+     182                 :            :     /**
+     183                 :            :      * @notice Enforces the cap for a direct transfer, reverting on violation.
+     184                 :            :      * @param from Sender address.
+     185                 :            :      * @param to Recipient address whose resulting balance is checked.
+     186                 :            :      * @param value Transfer amount.
+     187                 :            :      */
+     188                 :          5 :     function _transferred(address from, address to, uint256 value) internal view virtual {
+     189                 :          5 :         uint8 code = _detectTransferRestrictionOnNotify(from, to, value);
+     190         [ +  + ]:          5 :         require(
+     191                 :            :             code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK),
+     192                 :            :             RuleMaxBalance_InvalidTransfer(address(this), from, to, value, code)
+     193                 :            :         );
+     194                 :            :     }
+     195                 :            : 
+     196                 :            :     /**
+     197                 :            :      * @notice Enforces the cap for a `transferFrom`, reverting on violation.
+     198                 :            :      * @param spender Approved spender initiating the transfer; the minter on the mint path.
+     199                 :            :      * @param from Sender address.
+     200                 :            :      * @param to Recipient address whose resulting balance is checked.
+     201                 :            :      * @param value Transfer amount.
+     202                 :            :      */
+     203                 :         15 :     function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual {
+     204                 :         15 :         uint8 code = _detectTransferRestrictionOnNotify(from, to, value);
+     205         [ +  + ]:         15 :         require(
+     206                 :            :             code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK),
+     207                 :            :             RuleMaxBalance_InvalidTransferFrom(address(this), spender, from, to, value, code)
+     208                 :            :         );
+     209                 :            :     }
+     210                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func-sort-c.html index 432a05aa..737c890a 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol - functions @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 37 - 38 - 97.4 % + 33 + 33 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 13 - 92.3 % + 10 + 10 + 100.0 % Branches: - 11 - 11 + 10 + 10 100.0 % @@ -69,56 +69,44 @@ Hit count Sort by hit count - RuleMaxTotalSupplyBase._authorizeMaxTotalSupplyManager - 0 - - - RuleMaxTotalSupplyBase._transferred - 2 - - - RuleMaxTotalSupplyBase._transferredFrom - 2 - - - RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode - 2 + RuleMaxTotalSupplyBase._detectTransferRestrictionFrom + 4 - RuleMaxTotalSupplyBase.messageForTransferRestriction - 2 + RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode + 4 - RuleMaxTotalSupplyBase.transferred.0 - 2 + RuleMaxTotalSupplyBase.messageForTransferRestriction + 4 - RuleMaxTotalSupplyBase.transferred.1 - 2 + RuleMaxTotalSupplyBase._transferred + 18 - RuleMaxTotalSupplyBase._detectTransferRestrictionFrom - 3 + RuleMaxTotalSupplyBase.transferred.0 + 18 - RuleMaxTotalSupplyBase.setTokenContract - 4 + RuleMaxTotalSupplyBase._transferredFrom + 50 - RuleMaxTotalSupplyBase.onlyMaxTotalSupplyManager - 260 + RuleMaxTotalSupplyBase.transferred.1 + 50 - RuleMaxTotalSupplyBase.setMaxTotalSupply - 260 + RuleMaxTotalSupplyBase._detectTransferRestrictionOnNotify + 54 - RuleMaxTotalSupplyBase.constructor - 542 + RuleMaxTotalSupplyBase.constructor + 588 - RuleMaxTotalSupplyBase._detectTransferRestriction - 787 + RuleMaxTotalSupplyBase._detectTransferRestriction + 868
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func.html index 2a835c43..bc24867a 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol - functions @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 37 - 38 - 97.4 % + 33 + 33 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 13 - 92.3 % + 10 + 10 + 100.0 % Branches: - 11 - 11 + 10 + 10 100.0 % @@ -69,56 +69,44 @@ Hit count Sort by hit count - RuleMaxTotalSupplyBase._authorizeMaxTotalSupplyManager - 0 - - - RuleMaxTotalSupplyBase._detectTransferRestriction - 787 - - - RuleMaxTotalSupplyBase._detectTransferRestrictionFrom - 3 - - - RuleMaxTotalSupplyBase._transferred - 2 + RuleMaxTotalSupplyBase._detectTransferRestriction + 868 - RuleMaxTotalSupplyBase._transferredFrom - 2 + RuleMaxTotalSupplyBase._detectTransferRestrictionFrom + 4 - RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode - 2 + RuleMaxTotalSupplyBase._detectTransferRestrictionOnNotify + 54 - RuleMaxTotalSupplyBase.constructor - 542 + RuleMaxTotalSupplyBase._transferred + 18 - RuleMaxTotalSupplyBase.messageForTransferRestriction - 2 + RuleMaxTotalSupplyBase._transferredFrom + 50 - RuleMaxTotalSupplyBase.onlyMaxTotalSupplyManager - 260 + RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode + 4 - RuleMaxTotalSupplyBase.setMaxTotalSupply - 260 + RuleMaxTotalSupplyBase.constructor + 588 - RuleMaxTotalSupplyBase.setTokenContract + RuleMaxTotalSupplyBase.messageForTransferRestriction 4 - RuleMaxTotalSupplyBase.transferred.0 - 2 + RuleMaxTotalSupplyBase.transferred.0 + 18 - RuleMaxTotalSupplyBase.transferred.1 - 2 + RuleMaxTotalSupplyBase.transferred.1 + 50
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.gcov.html index 8a1df7be..082df7d4 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 37 - 38 - 97.4 % + 33 + 33 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 12 - 13 - 92.3 % + 10 + 10 + 100.0 % Branches: - 11 - 11 + 10 + 10 100.0 % @@ -72,191 +72,179 @@ 1 : : // SPDX-License-Identifier: MPL-2.0 2 : : pragma solidity ^0.8.20; 3 : : - 4 : : import {RuleMaxTotalSupplyInvariantStorage} from "../invariant/RuleMaxTotalSupplyInvariantStorage.sol"; - 5 : : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; - 6 : : import {ITotalSupply} from "../../../interfaces/ITotalSupply.sol"; - 7 : : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; - 8 : : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; - 9 : : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; - 10 : : - 11 : : /** - 12 : : * @title RuleMaxTotalSupplyBase - 13 : : * @notice Restricts minting so that total supply never exceeds a maximum value. - 14 : : */ - 15 : : abstract contract RuleMaxTotalSupplyBase is RuleTransferValidation, RuleMaxTotalSupplyInvariantStorage { - 16 : : /** - 17 : : * @dev tokenContract is trusted to return a correct totalSupply. - 18 : : */ - 19 : : ITotalSupply public tokenContract; - 20 : : /** - 21 : : * @notice Maximum total supply; minting that would exceed this value is rejected. - 22 : : */ - 23 : : uint256 public maxTotalSupply; - 24 : : - 25 : : /*////////////////////////////////////////////////////////////// - 26 : : CONSTRUCTOR - 27 : : //////////////////////////////////////////////////////////////*/ - 28 : : - 29 : : /** - 30 : : * @notice Initializes the rule with the token to observe and the supply cap. - 31 : : * @param tokenContract_ Address of the token whose `totalSupply` is checked; must not be the zero address. - 32 : : * @param maxTotalSupply_ Maximum total supply allowed. - 33 : : */ - 34 : 542 : constructor(address tokenContract_, uint256 maxTotalSupply_) { - 35 [ + + ]: 542 : require(tokenContract_ != address(0), RuleMaxTotalSupply_TokenAddressZeroNotAllowed()); - 36 : 541 : tokenContract = ITotalSupply(tokenContract_); - 37 : 541 : maxTotalSupply = maxTotalSupply_; - 38 : : } - 39 : : - 40 : : /*////////////////////////////////////////////////////////////// - 41 : : EXTERNAL FUNCTIONS - 42 : : //////////////////////////////////////////////////////////////*/ - 43 : : - 44 : : /** - 45 : : * @notice Returns whether this rule can produce the given restriction code. - 46 : : * @param restrictionCode Restriction code to test. - 47 : : * @return True if `restrictionCode` is the max-total-supply-exceeded code. - 48 : : */ - 49 : 2 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) { - 50 : 2 : return restrictionCode == CODE_MAX_TOTAL_SUPPLY_EXCEEDED; - 51 : : } - 52 : : - 53 : : /*////////////////////////////////////////////////////////////// - 54 : : PUBLIC FUNCTIONS - 55 : : //////////////////////////////////////////////////////////////*/ + 4 : : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; + 5 : : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; + 6 : : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; + 7 : : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; + 8 : : import {TotalSupplyCapManager} from "../core/TotalSupplyCapManager.sol"; + 9 : : + 10 : : /** + 11 : : * @title RuleMaxTotalSupplyBase + 12 : : * @notice Restricts minting so that total supply never exceeds a maximum value. + 13 : : */ + 14 : : abstract contract RuleMaxTotalSupplyBase is RuleTransferValidation, TotalSupplyCapManager { + 15 : : /*////////////////////////////////////////////////////////////// + 16 : : CONSTRUCTOR + 17 : : //////////////////////////////////////////////////////////////*/ + 18 : : + 19 : : /** + 20 : : * @notice Initializes the rule with the token to observe and the supply cap. + 21 : : * @dev Routes through the same internal setters the public API uses, so the initial + 22 : : * configuration is announced by {TokenContractUpdated} and {MaxTotalSupplyUpdated} exactly like + 23 : : * every later change. A cap that is set once at deployment and never touched would otherwise + 24 : : * have no on-chain event trail at all. + 25 : : * @param tokenContract_ Address of the token whose `totalSupply` is checked; must not be the zero address. + 26 : : * @param maxTotalSupply_ Maximum total supply allowed. + 27 : : */ + 28 : 588 : constructor(address tokenContract_, uint256 maxTotalSupply_) { + 29 : 588 : _setTokenContract(tokenContract_); + 30 : 585 : _setMaxTotalSupply(maxTotalSupply_); + 31 : : } + 32 : : + 33 : : /*////////////////////////////////////////////////////////////// + 34 : : EXTERNAL FUNCTIONS + 35 : : //////////////////////////////////////////////////////////////*/ + 36 : : + 37 : : /** + 38 : : * @notice Returns whether this rule can produce the given restriction code. + 39 : : * @param restrictionCode Restriction code to test. + 40 : : * @return True if `restrictionCode` is the max-total-supply-exceeded code. + 41 : : */ + 42 : 4 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) { + 43 : 4 : return restrictionCode == CODE_MAX_TOTAL_SUPPLY_EXCEEDED || restrictionCode == CODE_SUPPLY_ORACLE_UNAVAILABLE; + 44 : : } + 45 : : + 46 : : /*////////////////////////////////////////////////////////////// + 47 : : PUBLIC FUNCTIONS + 48 : : //////////////////////////////////////////////////////////////*/ + 49 : : + 50 : : /** + 51 : : * @inheritdoc IERC3643IComplianceContract + 52 : : */ + 53 : 18 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { + 54 : 18 : _transferred(from, to, value); + 55 : : } 56 : : 57 : : /** - 58 : : * @notice Updates the maximum total supply. - 59 : : * @param newMaxTotalSupply New maximum total supply value. - 60 : : */ - 61 : 260 : function setMaxTotalSupply(uint256 newMaxTotalSupply) public onlyMaxTotalSupplyManager { - 62 : 258 : maxTotalSupply = newMaxTotalSupply; - 63 : 258 : emit MaxTotalSupplyUpdated(newMaxTotalSupply); - 64 : : } - 65 : : - 66 : : /** - 67 : : * @notice Updates the token contract whose total supply is checked. - 68 : : * @param newTokenContract New token contract address; must not be the zero address. - 69 : : */ - 70 : 4 : function setTokenContract(address newTokenContract) public onlyMaxTotalSupplyManager { - 71 [ + + ]: 2 : require(newTokenContract != address(0), RuleMaxTotalSupply_TokenAddressZeroNotAllowed()); - 72 : 1 : tokenContract = ITotalSupply(newTokenContract); - 73 : 1 : emit TokenContractUpdated(newTokenContract); - 74 : : } - 75 : : - 76 : : /** - 77 : : * @inheritdoc IERC3643IComplianceContract - 78 : : */ - 79 : 2 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { - 80 : 2 : _transferred(from, to, value); - 81 : : } - 82 : : - 83 : : /** - 84 : : * @inheritdoc IRuleEngine - 85 : : */ - 86 : 2 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { - 87 : 2 : _transferredFrom(spender, from, to, value); - 88 : : } - 89 : : - 90 : : /** - 91 : : * @inheritdoc IERC1404 - 92 : : */ - 93 : 2 : function messageForTransferRestriction(uint8 restrictionCode) - 94 : : public - 95 : : pure - 96 : : override(IERC1404) - 97 : : returns (string memory) - 98 : : { - 99 [ + ]: 2 : if (restrictionCode == CODE_MAX_TOTAL_SUPPLY_EXCEEDED) { - 100 : 1 : return TEXT_MAX_TOTAL_SUPPLY_EXCEEDED; - 101 : : } - 102 : 1 : return TEXT_CODE_NOT_FOUND; - 103 : : } - 104 : : - 105 : : /*////////////////////////////////////////////////////////////// - 106 : : ACCESS CONTROL - 107 : : //////////////////////////////////////////////////////////////*/ - 108 : : - 109 : 260 : modifier onlyMaxTotalSupplyManager() { - 110 : 260 : _authorizeMaxTotalSupplyManager(); - 111 : : _; - 112 : : } - 113 : : - 114 : : /** - 115 : : * @notice Authorization hook invoked before updating the max total supply or token contract. - 116 : : */ - 117 : 0 : function _authorizeMaxTotalSupplyManager() internal view virtual; - 118 : : - 119 : : /*////////////////////////////////////////////////////////////// - 120 : : INTERNAL FUNCTIONS - 121 : : //////////////////////////////////////////////////////////////*/ - 122 : : - 123 : : /** - 124 : : * @inheritdoc RuleTransferValidation - 125 : : */ - 126 : 787 : function _detectTransferRestriction( - 127 : : address from, - 128 : : address, - 129 : : /* to */ - 130 : : uint256 value - 131 : : ) - 132 : : internal - 133 : : view - 134 : : override - 135 : : returns (uint8) - 136 : : { - 137 [ + ]: 787 : if (from == address(0)) { - 138 : 784 : uint256 currentSupply = tokenContract.totalSupply(); - 139 : : // Overflow-safe: `currentSupply + value` could exceed uint256 and this is a - 140 : : // MUST-NOT-revert ERC-1404/ERC-3643 view, so compare against the remaining headroom. - 141 [ + ]: 784 : if (currentSupply > maxTotalSupply || value > maxTotalSupply - currentSupply) { - 142 : 458 : return CODE_MAX_TOTAL_SUPPLY_EXCEEDED; - 143 : : } - 144 : : } - 145 : 329 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 58 : : * @inheritdoc IRuleEngine + 59 : : */ + 60 : 50 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { + 61 : 50 : _transferredFrom(spender, from, to, value); + 62 : : } + 63 : : + 64 : : /** + 65 : : * @inheritdoc IERC1404 + 66 : : */ + 67 : 4 : function messageForTransferRestriction(uint8 restrictionCode) + 68 : : public + 69 : : pure + 70 : : override(IERC1404) + 71 : : returns (string memory) + 72 : : { + 73 [ + + ]: 4 : if (restrictionCode == CODE_MAX_TOTAL_SUPPLY_EXCEEDED) { + 74 : 2 : return TEXT_MAX_TOTAL_SUPPLY_EXCEEDED; + 75 [ + ]: 2 : } else if (restrictionCode == CODE_SUPPLY_ORACLE_UNAVAILABLE) { + 76 : 1 : return TEXT_SUPPLY_ORACLE_UNAVAILABLE; + 77 : : } + 78 : 1 : return TEXT_CODE_NOT_FOUND; + 79 : : } + 80 : : + 81 : : /*////////////////////////////////////////////////////////////// + 82 : : INTERNAL FUNCTIONS + 83 : : //////////////////////////////////////////////////////////////*/ + 84 : : + 85 : : /** + 86 : : * @inheritdoc RuleTransferValidation + 87 : : */ + 88 : 868 : function _detectTransferRestriction( + 89 : : address from, + 90 : : address, + 91 : : /* to */ + 92 : : uint256 value + 93 : : ) + 94 : : internal + 95 : : view + 96 : : virtual + 97 : : override + 98 : : returns (uint8) + 99 : : { + 100 [ + ]: 868 : if (from == address(0)) { + 101 : 856 : (bool supplyAvailable, bool exceeded) = _capExceeded(value); + 102 [ + ]: 856 : if (!supplyAvailable) { + 103 : 4 : return CODE_SUPPLY_ORACLE_UNAVAILABLE; + 104 : : } + 105 [ + ]: 475 : if (exceeded) { + 106 : 475 : return CODE_MAX_TOTAL_SUPPLY_EXCEEDED; + 107 : : } + 108 : : } + 109 : 389 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 110 : : } + 111 : : + 112 : : /** + 113 : : * @inheritdoc RuleTransferValidation + 114 : : */ + 115 : 4 : function _detectTransferRestrictionFrom(address, address from, address to, uint256 value) + 116 : : internal + 117 : : view + 118 : : virtual + 119 : : override + 120 : : returns (uint8) + 121 : : { + 122 : 4 : return _detectTransferRestriction(from, to, value); + 123 : : } + 124 : : + 125 : : /** + 126 : : * @notice Restriction code for the NOTIFICATION phase, i.e. what the write hook enforces. + 127 : : * @dev **The seam an ERC-3643 variant overrides.** Defaults to the pre-flight check, correct for a token + 128 : : * that notifies BEFORE moving the value (CMTAT). A token that notifies AFTERWARDS reports an observation + 129 : : * that already includes `value`, and counting it again halves the effective cap; such a variant overrides + 130 : : * this with `_detectTransferRestriction(from, to, 0)`. + 131 : : * + 132 : : * The read path is deliberately NOT routed through here: a pre-flight view always runs before the movement + 133 : : * on either kind of token, so it must always count `value`. + 134 : : * @param from Sender address. + 135 : : * @param to Recipient address. + 136 : : * @param value Amount moved. + 137 : : * @return The restriction code the write hook will enforce. + 138 : : */ + 139 : 54 : function _detectTransferRestrictionOnNotify(address from, address to, uint256 value) + 140 : : internal + 141 : : view + 142 : : virtual + 143 : : returns (uint8) + 144 : : { + 145 : 54 : return _detectTransferRestriction(from, to, value); 146 : : } 147 : : 148 : : /** - 149 : : * @inheritdoc RuleTransferValidation - 150 : : */ - 151 : 3 : function _detectTransferRestrictionFrom(address, address from, address to, uint256 value) - 152 : : internal - 153 : : view - 154 : : override - 155 : : returns (uint8) - 156 : : { - 157 : 3 : return _detectTransferRestriction(from, to, value); - 158 : : } - 159 : : - 160 : : /** - 161 : : * @notice Enforces the max-total-supply restriction for a direct transfer, reverting on violation. - 162 : : * @param from Sender address; the zero address denotes a mint whose supply is checked. - 163 : : * @param to Recipient address. - 164 : : * @param value Transfer amount. - 165 : : */ - 166 : 2 : function _transferred(address from, address to, uint256 value) internal view virtual { - 167 : 2 : uint8 code = _detectTransferRestriction(from, to, value); - 168 [ + + ]: 2 : require( - 169 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 170 : : RuleMaxTotalSupply_InvalidTransfer(address(this), from, to, value, code) - 171 : : ); - 172 : : } - 173 : : - 174 : : /** - 175 : : * @notice Enforces the max-total-supply restriction for a `transferFrom`, reverting on violation. - 176 : : * @param spender Approved spender initiating the transfer. - 177 : : * @param from Sender address; the zero address denotes a mint whose supply is checked. - 178 : : * @param to Recipient address. - 179 : : * @param value Transfer amount. - 180 : : */ - 181 : 2 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual { - 182 : 2 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 183 [ + + ]: 2 : require( - 184 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 185 : : RuleMaxTotalSupply_InvalidTransferFrom(address(this), spender, from, to, value, code) - 186 : : ); - 187 : : } - 188 : : } + 149 : : * @notice Enforces the max-total-supply restriction for a direct transfer, reverting on violation. + 150 : : * @param from Sender address; the zero address denotes a mint whose supply is checked. + 151 : : * @param to Recipient address. + 152 : : * @param value Transfer amount. + 153 : : */ + 154 : 18 : function _transferred(address from, address to, uint256 value) internal view virtual { + 155 : 18 : uint8 code = _detectTransferRestrictionOnNotify(from, to, value); + 156 [ + + ]: 18 : require( + 157 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 158 : : RuleMaxTotalSupply_InvalidTransfer(address(this), from, to, value, code) + 159 : : ); + 160 : : } + 161 : : + 162 : : /** + 163 : : * @notice Enforces the max-total-supply restriction for a `transferFrom`, reverting on violation. + 164 : : * @param spender Approved spender initiating the transfer. + 165 : : * @param from Sender address; the zero address denotes a mint whose supply is checked. + 166 : : * @param to Recipient address. + 167 : : * @param value Transfer amount. + 168 : : */ + 169 : 50 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual { + 170 : 50 : uint8 code = _detectTransferRestrictionOnNotify(from, to, value); + 171 [ + + ]: 50 : require( + 172 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 173 : : RuleMaxTotalSupply_InvalidTransferFrom(address(this), spender, from, to, value, code) + 174 : : ); + 175 : : } + 176 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.func-sort-c.html new file mode 100644 index 00000000..2fa4cd58 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.func-sort-c.html @@ -0,0 +1,121 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleReceiverWhitelistBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:2929100.0 %
Date:2026-08-19 15:38:25Functions:1010100.0 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleReceiverWhitelistBase.isAllowList1
RuleReceiverWhitelistBase._transferredFrom2
RuleReceiverWhitelistBase.canReturnTransferRestrictionCode2
RuleReceiverWhitelistBase.messageForTransferRestriction2
RuleReceiverWhitelistBase.transferred.12
RuleReceiverWhitelistBase._transferred4
RuleReceiverWhitelistBase.transferred.04
RuleReceiverWhitelistBase._detectTransferRestrictionFrom7
RuleReceiverWhitelistBase.supportsInterface8
RuleReceiverWhitelistBase._detectTransferRestriction21
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.func.html new file mode 100644 index 00000000..727175f4 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.func.html @@ -0,0 +1,121 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleReceiverWhitelistBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:2929100.0 %
Date:2026-08-19 15:38:25Functions:1010100.0 %
Branches:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleReceiverWhitelistBase._detectTransferRestriction21
RuleReceiverWhitelistBase._detectTransferRestrictionFrom7
RuleReceiverWhitelistBase._transferred4
RuleReceiverWhitelistBase._transferredFrom2
RuleReceiverWhitelistBase.canReturnTransferRestrictionCode2
RuleReceiverWhitelistBase.isAllowList1
RuleReceiverWhitelistBase.messageForTransferRestriction2
RuleReceiverWhitelistBase.supportsInterface8
RuleReceiverWhitelistBase.transferred.04
RuleReceiverWhitelistBase.transferred.12
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.gcov.html new file mode 100644 index 00000000..2b657927 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol.gcov.html @@ -0,0 +1,266 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/base - RuleReceiverWhitelistBase.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:2929100.0 %
Date:2026-08-19 15:38:25Functions:1010100.0 %
Branches:66100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleAddressSet} from "../RuleAddressSet/RuleAddressSet.sol";
+       5                 :            : import {RuleNFTAdapter} from "../core/RuleNFTAdapter.sol";
+       6                 :            : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol";
+       7                 :            : import {RuleReceiverWhitelistInvariantStorage} from "../invariant/RuleReceiverWhitelistInvariantStorage.sol";
+       8                 :            : import {IAddressListPolarity} from "../../../interfaces/IAddressList.sol";
+       9                 :            : import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol";
+      10                 :            : import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol";
+      11                 :            : import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol";
+      12                 :            : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol";
+      13                 :            : 
+      14                 :            : /**
+      15                 :            :  * @title RuleReceiverWhitelistBase
+      16                 :            :  * @notice A whitelist that screens **only the receiver**, reproducing ERC-3643's eligibility rule.
+      17                 :            :  *
+      18                 :            :  * @dev ERC-3643 mandates one identity check -- the receiver -- and states that `transferFrom` works
+      19                 :            :  * the same way, `mint` requires only the receiver, and `burn` bypasses eligibility. Implemented
+      20                 :            :  * literally: `to` is screened on transfer, `transferFrom` and mint; the spender and sender never
+      21                 :            :  * are; burn is always allowed.
+      22                 :            :  *
+      23                 :            :  * @dev **Do not add a sender check.** It would trap de-listed holders, whose position would be
+      24                 :            :  * stranded. The spec screens only the receiver precisely so a lapsed investor can still exit. Use
+      25                 :            :  * {RuleWhitelist} if screening both parties is the policy you want.
+      26                 :            :  *
+      27                 :            :  * @dev Burn is exempt rather than checked because `address(0)` can never be listed, so without the
+      28                 :            :  * exemption every burn would be rejected. That matches the spec, it is not a convenience.
+      29                 :            :  *
+      30                 :            :  * @dev There is no `allowMint` flag, unlike {RuleWhitelist}: ERC-3643 gates minting on receiver
+      31                 :            :  * eligibility alone. Compose with `RuleMaxTotalSupply` or `RuleChainlinkPoR` to cap issuance.
+      32                 :            :  */
+      33                 :            : abstract contract RuleReceiverWhitelistBase is
+      34                 :            :     RuleAddressSet,
+      35                 :            :     RuleNFTAdapter,
+      36                 :            :     RuleReceiverWhitelistInvariantStorage,
+      37                 :            :     IAddressListPolarity
+      38                 :            : {
+      39                 :            :     /*//////////////////////////////////////////////////////////////
+      40                 :            :                              CONSTRUCTOR
+      41                 :            :     //////////////////////////////////////////////////////////////*/
+      42                 :            : 
+      43                 :            :     /**
+      44                 :            :      * @notice Deploys the receiver-whitelist rule base.
+      45                 :            :      * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions.
+      46                 :            :      */
+      47                 :            :     constructor(address forwarderIrrevocable) RuleAddressSet(forwarderIrrevocable) {}
+      48                 :            : 
+      49                 :            :     /*//////////////////////////////////////////////////////////////
+      50                 :            :                         EXTERNAL FUNCTIONS
+      51                 :            :     //////////////////////////////////////////////////////////////*/
+      52                 :            : 
+      53                 :            :     /**
+      54                 :            :      * @notice Returns whether this rule can emit the given restriction code.
+      55                 :            :      * @param restrictionCode The restriction code to check.
+      56                 :            :      * @return True if the code is produced by this rule.
+      57                 :            :      */
+      58                 :          2 :     function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) {
+      59                 :          2 :         return restrictionCode == CODE_ADDRESS_RECEIVER_NOT_WHITELISTED;
+      60                 :            :     }
+      61                 :            : 
+      62                 :            :     /*//////////////////////////////////////////////////////////////
+      63                 :            :                         PUBLIC FUNCTIONS
+      64                 :            :     //////////////////////////////////////////////////////////////*/
+      65                 :            : 
+      66                 :            :     /**
+      67                 :            :      * @inheritdoc IERC3643IComplianceContract
+      68                 :            :      */
+      69                 :          4 :     function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) {
+      70                 :          4 :         _transferred(from, to, value);
+      71                 :            :     }
+      72                 :            : 
+      73                 :            :     /**
+      74                 :            :      * @inheritdoc IRuleEngine
+      75                 :            :      */
+      76                 :          2 :     function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) {
+      77                 :          2 :         _transferredFrom(spender, from, to, value);
+      78                 :            :     }
+      79                 :            : 
+      80                 :            :     /**
+      81                 :            :      * @inheritdoc IERC1404
+      82                 :            :      */
+      83                 :          2 :     function messageForTransferRestriction(uint8 restrictionCode)
+      84                 :            :         public
+      85                 :            :         pure
+      86                 :            :         override(IERC1404)
+      87                 :            :         returns (string memory)
+      88                 :            :     {
+      89            [ + ]:          2 :         if (restrictionCode == CODE_ADDRESS_RECEIVER_NOT_WHITELISTED) {
+      90                 :          1 :             return TEXT_ADDRESS_RECEIVER_NOT_WHITELISTED;
+      91                 :            :         }
+      92                 :          1 :         return TEXT_CODE_NOT_FOUND;
+      93                 :            :     }
+      94                 :            : 
+      95                 :            :     /**
+      96                 :            :      * @inheritdoc RuleTransferValidation
+      97                 :            :      */
+      98                 :          8 :     function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) {
+      99                 :            :         // Advertise IAddressList: this rule manages an address set and is callable through
+     100                 :            :         // the IAddressList interface.
+     101                 :          8 :         return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID
+     102                 :          7 :             || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID
+     103                 :          7 :             || interfaceId == AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID
+     104                 :          6 :             || RuleTransferValidation.supportsInterface(interfaceId);
+     105                 :            :     }
+     106                 :            : 
+     107                 :            :     /**
+     108                 :            :      * @inheritdoc IAddressListPolarity
+     109                 :            :      * @dev Listed addresses are the permitted receivers.
+     110                 :            :      */
+     111                 :          1 :     function isAllowList() public pure virtual override returns (bool) {
+     112                 :          1 :         return true;
+     113                 :            :     }
+     114                 :            : 
+     115                 :            :     /*//////////////////////////////////////////////////////////////
+     116                 :            :                         INTERNAL FUNCTIONS
+     117                 :            :     //////////////////////////////////////////////////////////////*/
+     118                 :            : 
+     119                 :            :     /**
+     120                 :            :      * @notice Detects whether a transfer is blocked because the receiver is not whitelisted.
+     121                 :            :      * @dev The sender is deliberately ignored; see the contract-level notes.
+     122                 :            :      * @param to The recipient address; `address(0)` denotes a burn and is exempt.
+     123                 :            :      * @return The restriction code, or TRANSFER_OK when allowed.
+     124                 :            :      */
+     125                 :         21 :     function _detectTransferRestriction(address, address to, uint256) internal view virtual override returns (uint8) {
+     126                 :            :         // Burn (to == address(0)) bypasses eligibility per ERC-3643. It must be exempted
+     127                 :            :         // explicitly: address(0) can never be listed, so it would otherwise always be rejected.
+     128            [ + ]:         21 :         if (to != address(0) && !_isAddressListed(to)) {
+     129                 :          6 :             return CODE_ADDRESS_RECEIVER_NOT_WHITELISTED;
+     130                 :            :         }
+     131                 :         15 :         return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK);
+     132                 :            :     }
+     133                 :            : 
+     134                 :            :     /**
+     135                 :            :      * @notice Detects whether a delegated transfer is blocked because the receiver is not whitelisted.
+     136                 :            :      * @dev ERC-3643: `transferFrom` "works the same way" as `transfer`, so the spender is not
+     137                 :            :      * screened and this delegates to {_detectTransferRestriction}.
+     138                 :            :      * @param from The sender address.
+     139                 :            :      * @param to The recipient address.
+     140                 :            :      * @param value The amount transferred.
+     141                 :            :      * @return The restriction code, or TRANSFER_OK when allowed.
+     142                 :            :      */
+     143                 :          7 :     function _detectTransferRestrictionFrom(address, address from, address to, uint256 value)
+     144                 :            :         internal
+     145                 :            :         view
+     146                 :            :         virtual
+     147                 :            :         override
+     148                 :            :         returns (uint8)
+     149                 :            :     {
+     150                 :          7 :         return _detectTransferRestriction(from, to, value);
+     151                 :            :     }
+     152                 :            : 
+     153                 :            :     /**
+     154                 :            :      * @notice Reverts if a direct transfer is blocked because the receiver is not whitelisted.
+     155                 :            :      * @param from The sender address.
+     156                 :            :      * @param to The recipient address.
+     157                 :            :      * @param value The amount transferred.
+     158                 :            :      */
+     159                 :          4 :     function _transferred(address from, address to, uint256 value) internal view virtual override {
+     160                 :          4 :         uint8 code = _detectTransferRestriction(from, to, value);
+     161         [ +  + ]:          4 :         require(
+     162                 :            :             code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK),
+     163                 :            :             RuleReceiverWhitelist_InvalidTransfer(address(this), from, to, value, code)
+     164                 :            :         );
+     165                 :            :     }
+     166                 :            : 
+     167                 :            :     /**
+     168                 :            :      * @notice Reverts if a delegated transfer is blocked because the receiver is not whitelisted.
+     169                 :            :      * @param spender The delegated spender address; recorded in the error only, never screened.
+     170                 :            :      * @param from The sender address.
+     171                 :            :      * @param to The recipient address.
+     172                 :            :      * @param value The amount transferred.
+     173                 :            :      */
+     174                 :          2 :     function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override {
+     175                 :          2 :         uint8 code = _detectTransferRestrictionFrom(spender, from, to, value);
+     176         [ +  + ]:          2 :         require(
+     177                 :            :             code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK),
+     178                 :            :             RuleReceiverWhitelist_InvalidTransferFrom(address(this), spender, from, to, value, code)
+     179                 :            :         );
+     180                 :            :     }
+     181                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func-sort-c.html index 91a2f974..8bc1a999 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleSanctionsListBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleSanctionsListBase.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 47 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 13 @@ -49,8 +49,8 @@ Branches: - 18 - 18 + 17 + 17 100.0 % @@ -72,10 +72,6 @@ RuleSanctionsListBase._authorizeSanctionListManager 0 - - RuleSanctionsListBase.canReturnTransferRestrictionCode - 3 - RuleSanctionsListBase.clearSanctionListOracle 3 @@ -85,44 +81,48 @@ 3 - RuleSanctionsListBase.messageForTransferRestriction - 4 + RuleSanctionsListBase.canReturnTransferRestrictionCode + 5 - RuleSanctionsListBase.transferred.0 - 9 + RuleSanctionsListBase.messageForTransferRestriction + 5 RuleSanctionsListBase.setSanctionListOracle 18 - RuleSanctionsListBase._transferred - 19 + RuleSanctionsListBase.transferred.0 + 18 + + + RuleSanctionsListBase._transferred + 34 RuleSanctionsListBase._setSanctionListOracle - 39 + 69 - RuleSanctionsListBase.transferred.1 - 41 + RuleSanctionsListBase.constructor + 82 - RuleSanctionsListBase._transferredFrom - 48 + RuleSanctionsListBase.transferred.1 + 86 - RuleSanctionsListBase.constructor - 49 + RuleSanctionsListBase._transferredFrom + 93 - RuleSanctionsListBase._detectTransferRestrictionFrom - 69 + RuleSanctionsListBase._detectTransferRestrictionFrom + 124 - RuleSanctionsListBase._detectTransferRestriction - 119 + RuleSanctionsListBase._detectTransferRestriction + 212
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func.html index 6e74be9d..4424b99a 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleSanctionsListBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleSanctionsListBase.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 47 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 13 @@ -49,8 +49,8 @@ Branches: - 18 - 18 + 17 + 17 100.0 % @@ -73,28 +73,28 @@ 0 - RuleSanctionsListBase._detectTransferRestriction - 119 + RuleSanctionsListBase._detectTransferRestriction + 212 - RuleSanctionsListBase._detectTransferRestrictionFrom - 69 + RuleSanctionsListBase._detectTransferRestrictionFrom + 124 RuleSanctionsListBase._setSanctionListOracle - 39 + 69 - RuleSanctionsListBase._transferred - 19 + RuleSanctionsListBase._transferred + 34 - RuleSanctionsListBase._transferredFrom - 48 + RuleSanctionsListBase._transferredFrom + 93 RuleSanctionsListBase.canReturnTransferRestrictionCode - 3 + 5 RuleSanctionsListBase.clearSanctionListOracle @@ -102,11 +102,11 @@ RuleSanctionsListBase.constructor - 49 + 82 RuleSanctionsListBase.messageForTransferRestriction - 4 + 5 RuleSanctionsListBase.onlySanctionListManager @@ -118,11 +118,11 @@ RuleSanctionsListBase.transferred.0 - 9 + 18 RuleSanctionsListBase.transferred.1 - 41 + 86
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.gcov.html index ad6b1b2f..79ce334d 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSanctionsListBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleSanctionsListBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleSanctionsListBase.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 47 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 13 @@ -49,8 +49,8 @@ Branches: - 18 - 18 + 17 + 17 100.0 % @@ -100,11 +100,11 @@ 29 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. 30 : : * @param sanctionContractOracle_ Initial sanctions oracle; skipped when the zero address. 31 : : */ - 32 : 49 : constructor(address forwarderIrrevocable, ISanctionsList sanctionContractOracle_) + 32 : 82 : constructor(address forwarderIrrevocable, ISanctionsList sanctionContractOracle_) 33 : : MetaTxModuleStandalone(forwarderIrrevocable) 34 : : { - 35 [ + ]: 48 : if (address(sanctionContractOracle_) != address(0)) { - 36 : 21 : _setSanctionListOracle(sanctionContractOracle_); + 35 [ + ]: 81 : if (address(sanctionContractOracle_) != address(0)) { + 36 : 51 : _setSanctionListOracle(sanctionContractOracle_); 37 : : } 38 : : } 39 : : @@ -115,9 +115,9 @@ 44 : : /** 45 : : * @inheritdoc IRule 46 : : */ - 47 : 3 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override(IRule) returns (bool) { - 48 : 3 : return restrictionCode == CODE_ADDRESS_FROM_IS_SANCTIONED || restrictionCode == CODE_ADDRESS_TO_IS_SANCTIONED - 49 : 1 : || restrictionCode == CODE_ADDRESS_SPENDER_IS_SANCTIONED; + 47 : 5 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override(IRule) returns (bool) { + 48 : 5 : return restrictionCode == CODE_ADDRESS_FROM_IS_SANCTIONED || restrictionCode == CODE_ADDRESS_TO_IS_SANCTIONED + 49 : 2 : || restrictionCode == CODE_ADDRESS_SPENDER_IS_SANCTIONED; 50 : : } 51 : : 52 : : /*////////////////////////////////////////////////////////////// @@ -145,28 +145,28 @@ 74 : : /** 75 : : * @inheritdoc IERC3643IComplianceContract 76 : : */ - 77 : 9 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { - 78 : 9 : _transferred(from, to, value); + 77 : 18 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { + 78 : 18 : _transferred(from, to, value); 79 : : } 80 : : 81 : : /** 82 : : * @inheritdoc IRuleEngine 83 : : */ - 84 : 41 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { - 85 : 41 : _transferredFrom(spender, from, to, value); + 84 : 86 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { + 85 : 86 : _transferredFrom(spender, from, to, value); 86 : : } 87 : : 88 : : /** 89 : : * @inheritdoc IERC1404 90 : : */ - 91 : 4 : function messageForTransferRestriction(uint8 restrictionCode) + 91 : 5 : function messageForTransferRestriction(uint8 restrictionCode) 92 : : public 93 : : pure 94 : : override(IERC1404) 95 : : returns (string memory) 96 : : { - 97 [ + + ]: 4 : if (restrictionCode == CODE_ADDRESS_FROM_IS_SANCTIONED) { - 98 : 1 : return TEXT_ADDRESS_FROM_IS_SANCTIONED; + 97 [ + + ]: 5 : if (restrictionCode == CODE_ADDRESS_FROM_IS_SANCTIONED) { + 98 : 2 : return TEXT_ADDRESS_FROM_IS_SANCTIONED; 99 [ + + ]: 3 : } else if (restrictionCode == CODE_ADDRESS_TO_IS_SANCTIONED) { 100 : 1 : return TEXT_ADDRESS_TO_IS_SANCTIONED; 101 [ + ]: 2 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_IS_SANCTIONED) { @@ -192,9 +192,9 @@ 121 : : * @notice Updates the stored sanctions oracle and emits {SetSanctionListOracle}. 122 : : * @param sanctionContractOracle_ The new sanctions oracle address (may be zero to disable). 123 : : */ - 124 : 39 : function _setSanctionListOracle(ISanctionsList sanctionContractOracle_) internal virtual { - 125 : 39 : sanctionsList = sanctionContractOracle_; - 126 : 39 : emit SetSanctionListOracle(sanctionContractOracle_); + 124 : 69 : function _setSanctionListOracle(ISanctionsList sanctionContractOracle_) internal virtual { + 125 : 69 : sanctionsList = sanctionContractOracle_; + 126 : 69 : emit SetSanctionListOracle(sanctionContractOracle_); 127 : : } 128 : : 129 : : /** @@ -205,83 +205,95 @@ 134 : : 135 : : /** 136 : : * @notice Detects whether a direct transfer is restricted by the sanctions oracle. - 137 : : * @param from The sender address. - 138 : : * @param to The recipient address. - 139 : : * @return The restriction code, or TRANSFER_OK when no party is sanctioned. - 140 : : */ - 141 : 119 : function _detectTransferRestriction( - 142 : : address from, - 143 : : address to, - 144 : : uint256 /* value */ - 145 : : ) - 146 : : internal - 147 : : view - 148 : : override - 149 : : returns (uint8) - 150 : : { - 151 [ + ]: 119 : if (address(sanctionsList) != address(0)) { - 152 [ + + ]: 112 : if (sanctionsList.isSanctioned(from)) { - 153 : 27 : return CODE_ADDRESS_FROM_IS_SANCTIONED; - 154 [ + ]: 85 : } else if (sanctionsList.isSanctioned(to)) { - 155 : 12 : return CODE_ADDRESS_TO_IS_SANCTIONED; - 156 : : } - 157 : : } - 158 : 80 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 159 : : } - 160 : : - 161 : : /** - 162 : : * @notice Detects whether a delegated transfer is restricted by the sanctions oracle. - 163 : : * @param spender The delegated spender address. - 164 : : * @param from The sender address. - 165 : : * @param to The recipient address. - 166 : : * @param value The amount transferred. - 167 : : * @return The restriction code, or TRANSFER_OK when no party is sanctioned. - 168 : : */ - 169 : 69 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 170 : : internal - 171 : : view - 172 : : virtual - 173 : : override - 174 : : returns (uint8) - 175 : : { - 176 [ + ]: 69 : if (address(sanctionsList) != address(0)) { - 177 [ + ]: 68 : if (sanctionsList.isSanctioned(spender)) { - 178 : 6 : return CODE_ADDRESS_SPENDER_IS_SANCTIONED; - 179 : : } - 180 : 62 : return _detectTransferRestriction(from, to, value); - 181 : : } - 182 : 1 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 183 : : } - 184 : : - 185 : : /** - 186 : : * @notice Reverts if a direct transfer is blocked by the sanctions oracle. - 187 : : * @param from The sender address. - 188 : : * @param to The recipient address. - 189 : : * @param value The amount transferred. - 190 : : */ - 191 : 19 : function _transferred(address from, address to, uint256 value) internal view virtual override { - 192 : 19 : uint8 code = _detectTransferRestriction(from, to, value); - 193 [ + + ]: 19 : require( - 194 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 195 : : RuleSanctionsList_InvalidTransfer(address(this), from, to, value, code) - 196 : : ); - 197 : : } - 198 : : - 199 : : /** - 200 : : * @notice Reverts if a delegated transfer is blocked by the sanctions oracle. - 201 : : * @param spender The delegated spender address. - 202 : : * @param from The sender address. - 203 : : * @param to The recipient address. - 204 : : * @param value The amount transferred. - 205 : : */ - 206 : 48 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { - 207 : 48 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 208 [ + + ]: 48 : require( - 209 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 210 : : RuleSanctionsList_InvalidTransferFrom(address(this), spender, from, to, value, code) - 211 : : ); - 212 : : } - 213 : : } + 137 : : * @dev The zero address is the ERC-20 mint/burn sentinel, not a participant, so it is never sent + 138 : : * to the oracle: on a mint `from` is skipped, on a burn `to` is skipped. Asking a + 139 : : * third-party contract whether `address(0)` is sanctioned would delegate this rule's + 140 : : * mint/burn behaviour to that contract's handling of a degenerate input -- an oracle + 141 : : * answering `true` would block ALL issuance and ALL redemption, trapping holders. Every + 142 : : * other rule in this library screens only the real participants for the same reason. + 143 : : * @param from The sender address; the zero address denotes a mint and is not screened. + 144 : : * @param to The recipient address; the zero address denotes a burn and is not screened. + 145 : : * @return The restriction code, or TRANSFER_OK when no party is sanctioned. + 146 : : */ + 147 : 212 : function _detectTransferRestriction( + 148 : : address from, + 149 : : address to, + 150 : : uint256 /* value */ + 151 : : ) + 152 : : internal + 153 : : view + 154 : : virtual + 155 : : override + 156 : : returns (uint8) + 157 : : { + 158 : : // Read the oracle address once. Safe to cache across the calls below: this function is + 159 : : // `view`, so those are STATICCALLs and cannot write `sanctionsList`. + 160 : 212 : ISanctionsList oracle = sanctionsList; + 161 [ + ]: 212 : if (address(oracle) != address(0)) { + 162 [ + + ]: 198 : if (from != address(0) && oracle.isSanctioned(from)) { + 163 : 40 : return CODE_ADDRESS_FROM_IS_SANCTIONED; + 164 [ + ]: 158 : } else if (to != address(0) && oracle.isSanctioned(to)) { + 165 : 17 : return CODE_ADDRESS_TO_IS_SANCTIONED; + 166 : : } + 167 : : } + 168 : 155 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 169 : : } + 170 : : + 171 : : /** + 172 : : * @notice Detects whether a delegated transfer is restricted by the sanctions oracle. + 173 : : * @param spender The delegated spender address. + 174 : : * @param from The sender address. + 175 : : * @param to The recipient address. + 176 : : * @param value The amount transferred. + 177 : : * @return The restriction code, or TRANSFER_OK when no party is sanctioned. + 178 : : */ + 179 : 124 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 180 : : internal + 181 : : view + 182 : : virtual + 183 : : override + 184 : : returns (uint8) + 185 : : { + 186 : 124 : ISanctionsList oracle = sanctionsList; + 187 : : // The oracle guard scopes ONLY the spender check; the delegation below is unconditional, as + 188 : : // in every sibling rule. Nesting the delegation inside the guard -- as this function used to + 189 : : // -- silently drops any check in {_detectTransferRestriction} that does not depend on the + 190 : : // oracle, including one added by a subclass overriding that hook. + 191 [ + ]: 124 : if (address(oracle) != address(0) && oracle.isSanctioned(spender)) { + 192 : 8 : return CODE_ADDRESS_SPENDER_IS_SANCTIONED; + 193 : : } + 194 : 116 : return _detectTransferRestriction(from, to, value); + 195 : : } + 196 : : + 197 : : /** + 198 : : * @notice Reverts if a direct transfer is blocked by the sanctions oracle. + 199 : : * @param from The sender address. + 200 : : * @param to The recipient address. + 201 : : * @param value The amount transferred. + 202 : : */ + 203 : 34 : function _transferred(address from, address to, uint256 value) internal view virtual override { + 204 : 34 : uint8 code = _detectTransferRestriction(from, to, value); + 205 [ + + ]: 34 : require( + 206 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 207 : : RuleSanctionsList_InvalidTransfer(address(this), from, to, value, code) + 208 : : ); + 209 : : } + 210 : : + 211 : : /** + 212 : : * @notice Reverts if a delegated transfer is blocked by the sanctions oracle. + 213 : : * @param spender The delegated spender address. + 214 : : * @param from The sender address. + 215 : : * @param to The recipient address. + 216 : : * @param value The amount transferred. + 217 : : */ + 218 : 93 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { + 219 : 93 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + 220 [ + + ]: 93 : require( + 221 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 222 : : RuleSanctionsList_InvalidTransferFrom(address(this), spender, from, to, value, code) + 223 : : ); + 224 : : } + 225 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func-sort-c.html index ab131118..85bb1eab 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 22 - 22 + 23 + 23 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -69,40 +69,40 @@ Hit count Sort by hit count - RuleSpenderWhitelistBase.canReturnTransferRestrictionCode + RuleSpenderWhitelistBase.canReturnTransferRestrictionCode 2 - RuleSpenderWhitelistBase.messageForTransferRestriction + RuleSpenderWhitelistBase.messageForTransferRestriction 2 - RuleSpenderWhitelistBase.transferred.0 - 3 + RuleSpenderWhitelistBase.transferred.0 + 4 - RuleSpenderWhitelistBase.transferred.1 + RuleSpenderWhitelistBase.transferred.1 6 - RuleSpenderWhitelistBase.supportsInterface - 8 + RuleSpenderWhitelistBase._transferred + 14 - RuleSpenderWhitelistBase._transferred - 9 + RuleSpenderWhitelistBase.supportsInterface + 14 - RuleSpenderWhitelistBase._detectTransferRestriction - 12 + RuleSpenderWhitelistBase._detectTransferRestriction + 18 - RuleSpenderWhitelistBase._transferredFrom - 17 + RuleSpenderWhitelistBase._transferredFrom + 18 - RuleSpenderWhitelistBase._detectTransferRestrictionFrom - 35 + RuleSpenderWhitelistBase._detectTransferRestrictionFrom + 38
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func.html index 16f8dcf3..d6cfc5bc 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol - functions @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 22 - 22 + 23 + 23 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -69,39 +69,39 @@ Hit count Sort by hit count - RuleSpenderWhitelistBase._detectTransferRestriction - 12 + RuleSpenderWhitelistBase._detectTransferRestriction + 18 - RuleSpenderWhitelistBase._detectTransferRestrictionFrom - 35 + RuleSpenderWhitelistBase._detectTransferRestrictionFrom + 38 - RuleSpenderWhitelistBase._transferred - 9 + RuleSpenderWhitelistBase._transferred + 14 - RuleSpenderWhitelistBase._transferredFrom - 17 + RuleSpenderWhitelistBase._transferredFrom + 18 - RuleSpenderWhitelistBase.canReturnTransferRestrictionCode + RuleSpenderWhitelistBase.canReturnTransferRestrictionCode 2 - RuleSpenderWhitelistBase.messageForTransferRestriction + RuleSpenderWhitelistBase.messageForTransferRestriction 2 - RuleSpenderWhitelistBase.supportsInterface - 8 + RuleSpenderWhitelistBase.supportsInterface + 14 - RuleSpenderWhitelistBase.transferred.0 - 3 + RuleSpenderWhitelistBase.transferred.0 + 4 - RuleSpenderWhitelistBase.transferred.1 + RuleSpenderWhitelistBase.transferred.1 6 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.gcov.html index 21b9b3e4..6665bcaa 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol @@ -28,16 +28,16 @@ Test: - lcov.info + lcov2.info Lines: - 22 - 22 + 23 + 23 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -85,128 +85,136 @@ 14 : : * @title RuleSpenderWhitelistBase 15 : : * @notice Restricts `transferFrom`-style flows to whitelisted spenders only. 16 : : * @dev Direct transfers (`transferred(from,to,value)`) are intentionally no-op. - 17 : : */ - 18 : : abstract contract RuleSpenderWhitelistBase is RuleAddressSet, RuleNFTAdapter, RuleSpenderWhitelistInvariantStorage { - 19 : : /*////////////////////////////////////////////////////////////// - 20 : : CONSTRUCTOR - 21 : : //////////////////////////////////////////////////////////////*/ - 22 : : - 23 : : /** - 24 : : * @notice Deploys the spender-whitelist rule base. - 25 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. - 26 : : */ - 27 : : constructor(address forwarderIrrevocable) RuleAddressSet(forwarderIrrevocable) {} - 28 : : - 29 : : /*////////////////////////////////////////////////////////////// - 30 : : EXTERNAL FUNCTIONS - 31 : : //////////////////////////////////////////////////////////////*/ - 32 : : - 33 : : /** - 34 : : * @notice Returns whether this rule can emit the given restriction code. - 35 : : * @param restrictionCode The restriction code to check. - 36 : : * @return True if the code is produced by this rule. - 37 : : */ - 38 : 2 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) { - 39 : 2 : return restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED; - 40 : : } - 41 : : - 42 : : /*////////////////////////////////////////////////////////////// - 43 : : PUBLIC FUNCTIONS - 44 : : //////////////////////////////////////////////////////////////*/ - 45 : : - 46 : : /** - 47 : : * @dev Regular transfers are always accepted by this rule. - 48 : : */ - 49 : 3 : function transferred(address, address, uint256) public view override(IERC3643IComplianceContract) {} - 50 : : - 51 : : /** - 52 : : * @inheritdoc IRuleEngine - 53 : : */ - 54 : 6 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { - 55 : 6 : _transferredFrom(spender, from, to, value); - 56 : : } + 17 : : * + 18 : : * @dev **Deliberately does NOT implement {IAddressListPolarity}, and must not be made to.** Its set is + 19 : : * an allow-list, so declaring `isAllowList() == true` would be honest about polarity and still wrong: + 20 : : * the listed addresses are permitted **spenders**, not permitted **holders**. Declaring polarity would + 21 : : * let `RuleWhitelistWrapper` accept this rule and then read whitelisted spenders as eligible transfer + 22 : : * participants. Withholding the declaration is what makes the wrapper's fail-closed check refuse it. + 23 : : * Polarity is only half the question; the other half is what the addresses are. + 24 : : */ + 25 : : abstract contract RuleSpenderWhitelistBase is RuleAddressSet, RuleNFTAdapter, RuleSpenderWhitelistInvariantStorage { + 26 : : /*////////////////////////////////////////////////////////////// + 27 : : CONSTRUCTOR + 28 : : //////////////////////////////////////////////////////////////*/ + 29 : : + 30 : : /** + 31 : : * @notice Deploys the spender-whitelist rule base. + 32 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. + 33 : : */ + 34 : : constructor(address forwarderIrrevocable) RuleAddressSet(forwarderIrrevocable) {} + 35 : : + 36 : : /*////////////////////////////////////////////////////////////// + 37 : : EXTERNAL FUNCTIONS + 38 : : //////////////////////////////////////////////////////////////*/ + 39 : : + 40 : : /** + 41 : : * @notice Returns whether this rule can emit the given restriction code. + 42 : : * @param restrictionCode The restriction code to check. + 43 : : * @return True if the code is produced by this rule. + 44 : : */ + 45 : 2 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool) { + 46 : 2 : return restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED; + 47 : : } + 48 : : + 49 : : /*////////////////////////////////////////////////////////////// + 50 : : PUBLIC FUNCTIONS + 51 : : //////////////////////////////////////////////////////////////*/ + 52 : : + 53 : : /** + 54 : : * @dev Regular transfers are always accepted by this rule. + 55 : : */ + 56 : 4 : function transferred(address, address, uint256) public view override(IERC3643IComplianceContract) {} 57 : : 58 : : /** - 59 : : * @inheritdoc IERC1404 + 59 : : * @inheritdoc IRuleEngine 60 : : */ - 61 : 2 : function messageForTransferRestriction(uint8 restrictionCode) - 62 : : public - 63 : : pure - 64 : : override(IERC1404) - 65 : : returns (string memory) - 66 : : { - 67 [ + ]: 2 : if (restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED) { - 68 : 1 : return TEXT_ADDRESS_SPENDER_NOT_WHITELISTED; - 69 : : } - 70 : 1 : return TEXT_CODE_NOT_FOUND; - 71 : : } - 72 : : - 73 : : /** - 74 : : * @inheritdoc RuleTransferValidation - 75 : : */ - 76 : 8 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { - 77 : : // Advertise IAddressList: this rule manages an address set and is callable through - 78 : : // the IAddressList interface. - 79 : 8 : return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID - 80 : 6 : || RuleTransferValidation.supportsInterface(interfaceId); - 81 : : } - 82 : : - 83 : : /*////////////////////////////////////////////////////////////// - 84 : : INTERNAL FUNCTIONS - 85 : : //////////////////////////////////////////////////////////////*/ - 86 : : - 87 : : /** - 88 : : * @notice Direct transfers are always accepted by this rule. - 89 : : * @return Always TRANSFER_OK. - 90 : : */ - 91 : 12 : function _detectTransferRestriction(address, address, uint256) internal pure virtual override returns (uint8) { - 92 : 12 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 93 : : } + 61 : 6 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { + 62 : 6 : _transferredFrom(spender, from, to, value); + 63 : : } + 64 : : + 65 : : /** + 66 : : * @inheritdoc IERC1404 + 67 : : */ + 68 : 2 : function messageForTransferRestriction(uint8 restrictionCode) + 69 : : public + 70 : : pure + 71 : : override(IERC1404) + 72 : : returns (string memory) + 73 : : { + 74 [ + ]: 2 : if (restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED) { + 75 : 1 : return TEXT_ADDRESS_SPENDER_NOT_WHITELISTED; + 76 : : } + 77 : 1 : return TEXT_CODE_NOT_FOUND; + 78 : : } + 79 : : + 80 : : /** + 81 : : * @inheritdoc RuleTransferValidation + 82 : : */ + 83 : 14 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { + 84 : : // Advertise IAddressList: this rule manages an address set and is callable through + 85 : : // the IAddressList interface. + 86 : 14 : return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + 87 : 12 : || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + 88 : 10 : || RuleTransferValidation.supportsInterface(interfaceId); + 89 : : } + 90 : : + 91 : : /*////////////////////////////////////////////////////////////// + 92 : : INTERNAL FUNCTIONS + 93 : : //////////////////////////////////////////////////////////////*/ 94 : : 95 : : /** - 96 : : * @notice Detects whether a delegated transfer is blocked because the spender is not whitelisted. - 97 : : * @param spender The delegated spender address. - 98 : : * @param from The sender address. - 99 : : * @param to The recipient address. - 100 : : * @return The restriction code, or TRANSFER_OK when allowed. - 101 : : */ - 102 : 35 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256) - 103 : : internal - 104 : : view - 105 : : virtual - 106 : : override - 107 : : returns (uint8) - 108 : : { - 109 : : // Mint (from == address(0)) and burn (to == address(0)) are exempt from the spender check: - 110 : : // the minter/burner acts on its own authority, not as a delegated ERC-20 spender. - 111 [ + ]: 35 : if (from != address(0) && to != address(0) && !_isAddressListed(spender)) { - 112 : 13 : return CODE_ADDRESS_SPENDER_NOT_WHITELISTED; - 113 : : } - 114 : 22 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 115 : : } - 116 : : - 117 : : /** - 118 : : * @notice No-op: regular transfers are intentionally ignored by this rule. - 119 : : */ - 120 : 9 : function _transferred(address, address, uint256) internal view virtual override { - 121 : : // no-op: regular transfers are intentionally ignored by this rule - 122 : : } - 123 : : - 124 : : /** - 125 : : * @notice Reverts if a delegated transfer is blocked because the spender is not whitelisted. - 126 : : * @param spender The delegated spender address. - 127 : : * @param from The sender address. - 128 : : * @param to The recipient address. - 129 : : * @param value The amount transferred. - 130 : : */ - 131 : 17 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { - 132 : 17 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 133 [ + + ]: 17 : require( - 134 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), - 135 : : RuleSpenderWhitelist_InvalidTransferFrom(address(this), spender, from, to, value, code) - 136 : : ); - 137 : : } - 138 : : } + 96 : : * @notice Direct transfers are always accepted by this rule. + 97 : : * @return Always TRANSFER_OK. + 98 : : */ + 99 : 18 : function _detectTransferRestriction(address, address, uint256) internal pure virtual override returns (uint8) { + 100 : 18 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 101 : : } + 102 : : + 103 : : /** + 104 : : * @notice Detects whether a delegated transfer is blocked because the spender is not whitelisted. + 105 : : * @param spender The delegated spender address. + 106 : : * @param from The sender address. + 107 : : * @param to The recipient address. + 108 : : * @return The restriction code, or TRANSFER_OK when allowed. + 109 : : */ + 110 : 38 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256) + 111 : : internal + 112 : : view + 113 : : virtual + 114 : : override + 115 : : returns (uint8) + 116 : : { + 117 : : // Mint (from == address(0)) and burn (to == address(0)) are exempt from the spender check: + 118 : : // the minter/burner acts on its own authority, not as a delegated ERC-20 spender. + 119 [ + ]: 38 : if (from != address(0) && to != address(0) && !_isAddressListed(spender)) { + 120 : 16 : return CODE_ADDRESS_SPENDER_NOT_WHITELISTED; + 121 : : } + 122 : 22 : return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 123 : : } + 124 : : + 125 : : /** + 126 : : * @notice No-op: regular transfers are intentionally ignored by this rule. + 127 : : */ + 128 : 14 : function _transferred(address, address, uint256) internal view virtual override { + 129 : : // no-op: regular transfers are intentionally ignored by this rule + 130 : : } + 131 : : + 132 : : /** + 133 : : * @notice Reverts if a delegated transfer is blocked because the spender is not whitelisted. + 134 : : * @param spender The delegated spender address. + 135 : : * @param from The sender address. + 136 : : * @param to The recipient address. + 137 : : * @param value The amount transferred. + 138 : : */ + 139 : 18 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { + 140 : 18 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + 141 [ + + ]: 18 : require( + 142 : : code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), + 143 : : RuleSpenderWhitelist_InvalidTransferFrom(address(this), spender, from, to, value, code) + 144 : : ); + 145 : : } + 146 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func-sort-c.html index a5012cb1..01269ff8 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleWhitelistBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleWhitelistBase.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 30 - 31 - 96.8 % + 27 + 27 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 8 - 9 - 88.9 % + 6 + 6 + 100.0 % @@ -69,40 +69,28 @@ Hit count Sort by hit count - RuleWhitelistBase._authorizeCheckSpenderManager - 0 - - - RuleWhitelistBase._setCheckSpender - 2 - - - RuleWhitelistBase.onlyCheckSpenderManager - 3 - - - RuleWhitelistBase.setCheckSpender - 3 + RuleWhitelistBase.isVerified + 6 - RuleWhitelistBase.isVerified - 6 + RuleWhitelistBase._detectTransferRestrictionFrom + 43 - RuleWhitelistBase.supportsInterface - 35 + RuleWhitelistBase.isAllowList + 104 - RuleWhitelistBase._detectTransferRestrictionFrom - 38 + RuleWhitelistBase._detectTransferRestriction + 155 - RuleWhitelistBase._detectTransferRestriction - 100 + RuleWhitelistBase.constructor + 226 - RuleWhitelistBase.constructor - 188 + RuleWhitelistBase.supportsInterface + 473
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func.html index 93e28b1a..ad379eab 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleWhitelistBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleWhitelistBase.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 30 - 31 - 96.8 % + 27 + 27 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 8 - 9 - 88.9 % + 6 + 6 + 100.0 % @@ -69,40 +69,28 @@ Hit count Sort by hit count - RuleWhitelistBase._authorizeCheckSpenderManager - 0 - - - RuleWhitelistBase._detectTransferRestriction - 100 + RuleWhitelistBase._detectTransferRestriction + 155 - RuleWhitelistBase._detectTransferRestrictionFrom - 38 + RuleWhitelistBase._detectTransferRestrictionFrom + 43 - RuleWhitelistBase._setCheckSpender - 2 + RuleWhitelistBase.constructor + 226 - RuleWhitelistBase.constructor - 188 + RuleWhitelistBase.isAllowList + 104 - RuleWhitelistBase.isVerified + RuleWhitelistBase.isVerified 6 - RuleWhitelistBase.onlyCheckSpenderManager - 3 - - - RuleWhitelistBase.setCheckSpender - 3 - - - RuleWhitelistBase.supportsInterface - 35 + RuleWhitelistBase.supportsInterface + 473
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.gcov.html index 8dbdb3b5..f1736c0e 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleWhitelistBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleWhitelistBase.sol @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 30 - 31 - 96.8 % + 27 + 27 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 8 - 9 - 88.9 % + 6 + 6 + 100.0 % @@ -76,161 +76,148 @@ 5 : : import {RuleWhitelistShared} from "../core/RuleWhitelistShared.sol"; 6 : : import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; 7 : : import {IIdentityRegistryVerified} from "../../../interfaces/IIdentityRegistry.sol"; - 8 : : import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; - 9 : : - 10 : : /** - 11 : : * @title RuleWhitelistBase - 12 : : * @notice Core whitelist logic without access-control policy. - 13 : : */ - 14 : : abstract contract RuleWhitelistBase is RuleAddressSet, RuleWhitelistShared, IIdentityRegistryVerified { - 15 : : /*////////////////////////////////////////////////////////////// - 16 : : CONSTRUCTOR - 17 : : //////////////////////////////////////////////////////////////*/ - 18 : : - 19 : : /** - 20 : : * @notice Deploys the whitelist rule base. - 21 : : * @dev `allowMintBurn` sets BOTH {allowMint} and {allowBurn} — the common case, since mint and - 22 : : * burn are normally permitted for a whitelist rule. Use {setAllowMint} / {setAllowBurn} - 23 : : * afterwards for independent control (e.g. to permanently close issuance while still - 24 : : * allowing redemptions). - 25 : : * @dev Mint/burn permission is an explicit flag and NO LONGER whitelists `address(0)`: the zero - 26 : : * address is the ERC-20 sentinel, not a participant, and listing it made - 27 : : * `isVerified(address(0))` return `true` in violation of ERC-3643. - 28 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. - 29 : : * @param checkSpender_ Whether to also verify the spender on delegated transfers. - 30 : : * @param allowMintBurn When true, permits both minting and burning. - 31 : : */ - 32 : 188 : constructor(address forwarderIrrevocable, bool checkSpender_, bool allowMintBurn) - 33 : : RuleAddressSet(forwarderIrrevocable) - 34 : : { - 35 : 188 : checkSpender = checkSpender_; - 36 : 188 : _setAllowMintBurn(allowMintBurn, allowMintBurn); - 37 : : } - 38 : : - 39 : : /*////////////////////////////////////////////////////////////// - 40 : : PUBLIC FUNCTIONS - 41 : : //////////////////////////////////////////////////////////////*/ - 42 : : - 43 : : /** - 44 : : * @notice Enables or disables spender verification on delegated transfers. - 45 : : * @dev Restricted to the check-spender manager; emits {CheckSpenderUpdated}. - 46 : : * @param value The new state of the `checkSpender` flag. - 47 : : */ - 48 : 3 : function setCheckSpender(bool value) public virtual onlyCheckSpenderManager { - 49 : 2 : _setCheckSpender(value); - 50 : 2 : emit CheckSpenderUpdated(value); - 51 : : } - 52 : : - 53 : : /** - 54 : : * @inheritdoc IIdentityRegistryVerified - 55 : : */ - 56 : 6 : function isVerified(address targetAddress) - 57 : : public - 58 : : view - 59 : : virtual - 60 : : override(IIdentityRegistryVerified) - 61 : : returns (bool isListed) - 62 : : { - 63 : 6 : isListed = _isAddressListed(targetAddress); - 64 : : } - 65 : : - 66 : : /** - 67 : : * @inheritdoc RuleTransferValidation - 68 : : */ - 69 : 35 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { - 70 : : // Advertise IAddressList: this rule manages an address set and is usable as a - 71 : : // child rule of RuleWhitelistWrapper, which calls it through IAddressList. - 72 : 35 : return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID - 73 : 33 : || RuleTransferValidation.supportsInterface(interfaceId); - 74 : : } - 75 : : - 76 : : /*////////////////////////////////////////////////////////////// - 77 : : ACCESS CONTROL - 78 : : //////////////////////////////////////////////////////////////*/ - 79 : : - 80 : 3 : modifier onlyCheckSpenderManager() { - 81 : 3 : _authorizeCheckSpenderManager(); - 82 : : _; - 83 : : } - 84 : : - 85 : : /*////////////////////////////////////////////////////////////// - 86 : : INTERNAL FUNCTIONS - 87 : : //////////////////////////////////////////////////////////////*/ - 88 : : - 89 : : /** - 90 : : * @notice Internal helper to update the `checkSpender` flag. - 91 : : * @param value New flag value. - 92 : : */ - 93 : 2 : function _setCheckSpender(bool value) internal virtual { - 94 : 2 : checkSpender = value; - 95 : : } - 96 : : - 97 : : /** - 98 : : * @notice Authorizes the caller as check-spender manager; reverts otherwise. - 99 : : * @dev Implemented by concrete subclasses with the desired access-control policy. - 100 : : */ - 101 : 0 : function _authorizeCheckSpenderManager() internal view virtual; - 102 : : - 103 : : /** - 104 : : * @notice Detects whether a direct transfer is restricted by the whitelist. - 105 : : * @param from The sender address. - 106 : : * @param to The recipient address. - 107 : : * @return The restriction code, or TRANSFER_OK when both parties are whitelisted. - 108 : : */ - 109 : 100 : function _detectTransferRestriction( - 110 : : address from, - 111 : : address to, - 112 : : uint256 /* value */ - 113 : : ) - 114 : : internal - 115 : : view - 116 : : virtual - 117 : : override - 118 : : returns (uint8) - 119 : : { - 120 : 100 : bool isMint = from == address(0); - 121 : 100 : bool isBurn = to == address(0); - 122 : : - 123 : : // Gate the mint/burn OPERATION explicitly, rather than by listing the zero address. - 124 : 100 : uint8 mintBurnCode = _detectMintBurnRestriction(from, to); - 125 [ + ]: 100 : if (mintBurnCode != uint8(REJECTED_CODE_BASE.TRANSFER_OK)) { - 126 : 7 : return mintBurnCode; - 127 : : } - 128 : : - 129 : : // Screen only the REAL participants. A permitted mint still requires a whitelisted - 130 : : // recipient; a permitted burn still requires a whitelisted sender. - 131 [ + ]: 93 : if (!isMint && !isAddressListed(from)) { - 132 : 30 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; - 133 : : } - 134 [ + ]: 63 : if (!isBurn && !isAddressListed(to)) { - 135 : 11 : return CODE_ADDRESS_TO_NOT_WHITELISTED; - 136 : : } - 137 : 52 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 138 : : } - 139 : : - 140 : : /** - 141 : : * @notice Detects whether a delegated transfer is restricted by the whitelist. - 142 : : * @param spender The delegated spender address. - 143 : : * @param from The sender address. - 144 : : * @param to The recipient address. - 145 : : * @param value The amount transferred. - 146 : : * @return The restriction code, or TRANSFER_OK when allowed. - 147 : : */ - 148 : 38 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 149 : : internal - 150 : : view - 151 : : virtual - 152 : : override - 153 : : returns (uint8) - 154 : : { - 155 : : // Mint (from == address(0)) and burn (to == address(0)) are exempt from the spender check: - 156 : : // the minter/burner acts on its own authority, not as a delegated ERC-20 spender. - 157 [ + ]: 38 : if (checkSpender && from != address(0) && to != address(0) && !isAddressListed(spender)) { - 158 : 8 : return CODE_ADDRESS_SPENDER_NOT_WHITELISTED; - 159 : : } - 160 : 30 : return _detectTransferRestriction(from, to, value); - 161 : : } - 162 : : } + 8 : : import {IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; + 9 : : import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; + 10 : : + 11 : : /** + 12 : : * @title RuleWhitelistBase + 13 : : * @notice Core whitelist logic without access-control policy. + 14 : : */ + 15 : : abstract contract RuleWhitelistBase is + 16 : : RuleAddressSet, + 17 : : RuleWhitelistShared, + 18 : : IIdentityRegistryVerified, + 19 : : IAddressListPolarity + 20 : : { + 21 : : /*////////////////////////////////////////////////////////////// + 22 : : CONSTRUCTOR + 23 : : //////////////////////////////////////////////////////////////*/ + 24 : : + 25 : : /** + 26 : : * @notice Deploys the whitelist rule base. + 27 : : * @dev `allowMintBurn` sets BOTH {allowMint} and {allowBurn} — the common case, since mint and + 28 : : * burn are normally permitted for a whitelist rule. Use {setAllowMint} / {setAllowBurn} + 29 : : * afterwards for independent control (e.g. to permanently close issuance while still + 30 : : * allowing redemptions). + 31 : : * @dev Mint/burn permission is an explicit flag and NO LONGER whitelists `address(0)`: the zero + 32 : : * address is the ERC-20 sentinel, not a participant, and listing it made + 33 : : * `isVerified(address(0))` return `true` in violation of ERC-3643. + 34 : : * @param forwarderIrrevocable Trusted ERC-2771 forwarder address for meta-transactions. + 35 : : * @param checkSpender_ Whether to also verify the spender on delegated transfers. + 36 : : * @param allowMintBurn When true, permits both minting and burning. + 37 : : */ + 38 : 226 : constructor(address forwarderIrrevocable, bool checkSpender_, bool allowMintBurn) + 39 : : RuleAddressSet(forwarderIrrevocable) + 40 : : { + 41 : 226 : _setCheckSpender(checkSpender_); + 42 : 226 : _setAllowMintBurn(allowMintBurn, allowMintBurn); + 43 : : } + 44 : : + 45 : : /*////////////////////////////////////////////////////////////// + 46 : : PUBLIC FUNCTIONS + 47 : : //////////////////////////////////////////////////////////////*/ + 48 : : + 49 : : /** + 50 : : * @inheritdoc IIdentityRegistryVerified + 51 : : */ + 52 : 6 : function isVerified(address targetAddress) + 53 : : public + 54 : : view + 55 : : virtual + 56 : : override(IIdentityRegistryVerified) + 57 : : returns (bool isListed) + 58 : : { + 59 : 6 : isListed = _isAddressListed(targetAddress); + 60 : : } + 61 : : + 62 : : /** + 63 : : * @inheritdoc RuleTransferValidation + 64 : : */ + 65 : 473 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { + 66 : : // Advertise IAddressList: this rule manages an address set and is usable as a + 67 : : // child rule of RuleWhitelistWrapper, which calls it through IAddressList. + 68 : 473 : return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + 69 : 471 : || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + 70 : 368 : || interfaceId == AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID + 71 : 265 : || RuleTransferValidation.supportsInterface(interfaceId); + 72 : : } + 73 : : + 74 : : /** + 75 : : * @inheritdoc IAddressListPolarity + 76 : : * @dev Listed addresses are the permitted transfer participants. + 77 : : */ + 78 : 104 : function isAllowList() public pure virtual override returns (bool) { + 79 : 104 : return true; + 80 : : } + 81 : : + 82 : : /*////////////////////////////////////////////////////////////// + 83 : : ACCESS CONTROL + 84 : : //////////////////////////////////////////////////////////////*/ + 85 : : + 86 : : /*////////////////////////////////////////////////////////////// + 87 : : INTERNAL FUNCTIONS + 88 : : //////////////////////////////////////////////////////////////*/ + 89 : : + 90 : : /** + 91 : : * @notice Detects whether a direct transfer is restricted by the whitelist. + 92 : : * @param from The sender address. + 93 : : * @param to The recipient address. + 94 : : * @return The restriction code, or TRANSFER_OK when both parties are whitelisted. + 95 : : */ + 96 : 155 : function _detectTransferRestriction( + 97 : : address from, + 98 : : address to, + 99 : : uint256 /* value */ + 100 : : ) + 101 : : internal + 102 : : view + 103 : : virtual + 104 : : override + 105 : : returns (uint8) + 106 : : { + 107 : 155 : bool isMint = from == address(0); + 108 : 155 : bool isBurn = to == address(0); + 109 : : + 110 : : // Gate the mint/burn OPERATION explicitly, rather than by listing the zero address. + 111 : 155 : uint8 mintBurnCode = _detectMintBurnRestriction(from, to); + 112 [ + ]: 155 : if (mintBurnCode != uint8(REJECTED_CODE_BASE.TRANSFER_OK)) { + 113 : 11 : return mintBurnCode; + 114 : : } + 115 : : + 116 : : // Screen only the REAL participants. A permitted mint still requires a whitelisted + 117 : : // recipient; a permitted burn still requires a whitelisted sender. + 118 [ + ]: 144 : if (!isMint && !isAddressListed(from)) { + 119 : 38 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; + 120 : : } + 121 [ + ]: 106 : if (!isBurn && !isAddressListed(to)) { + 122 : 16 : return CODE_ADDRESS_TO_NOT_WHITELISTED; + 123 : : } + 124 : 90 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 125 : : } + 126 : : + 127 : : /** + 128 : : * @notice Detects whether a delegated transfer is restricted by the whitelist. + 129 : : * @param spender The delegated spender address. + 130 : : * @param from The sender address. + 131 : : * @param to The recipient address. + 132 : : * @param value The amount transferred. + 133 : : * @return The restriction code, or TRANSFER_OK when allowed. + 134 : : */ + 135 : 43 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 136 : : internal + 137 : : view + 138 : : virtual + 139 : : override + 140 : : returns (uint8) + 141 : : { + 142 : : // Mint (from == address(0)) and burn (to == address(0)) are exempt from the spender check: + 143 : : // the minter/burner acts on its own authority, not as a delegated ERC-20 spender. + 144 [ + ]: 43 : if (checkSpender && from != address(0) && to != address(0) && !isAddressListed(spender)) { + 145 : 8 : return CODE_ADDRESS_SPENDER_NOT_WHITELISTED; + 146 : : } + 147 : 35 : return _detectTransferRestriction(from, to, value); + 148 : : } + 149 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func-sort-c.html index 9951824f..8e3c2b5f 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol - functions @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 87 - 88 - 98.9 % + 80 + 80 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 15 - 16 - 93.8 % + 13 + 13 + 100.0 % Branches: - 20 - 20 + 25 + 25 100.0 % @@ -69,68 +69,56 @@ Hit count Sort by hit count - RuleWhitelistWrapperBase._authorizeCheckSpenderManager - 0 - - - RuleWhitelistWrapperBase._transferred.1 + RuleWhitelistWrapperBase._transferred.1 1 - RuleWhitelistWrapperBase._msgData + RuleWhitelistWrapperBase._msgData 2 - RuleWhitelistWrapperBase._setCheckSpender - 3 - - - RuleWhitelistWrapperBase.onlyCheckSpenderManager - 4 - - - RuleWhitelistWrapperBase.setCheckSpender - 4 + RuleWhitelistWrapperBase.isVerified + 9 - RuleWhitelistWrapperBase._isListedInAnyChild - 5 + RuleWhitelistWrapperBase._isListedInAnyChild + 14 - RuleWhitelistWrapperBase.isVerified - 7 + RuleWhitelistWrapperBase._transferred.0 + 28 - RuleWhitelistWrapperBase._transferred.0 - 20 + RuleWhitelistWrapperBase._detectTransferRestrictionFrom + 38 - RuleWhitelistWrapperBase._detectTransferRestrictionFrom - 37 + RuleWhitelistWrapperBase.supportsInterface + 52 - RuleWhitelistWrapperBase.supportsInterface - 49 + RuleWhitelistWrapperBase.constructor + 64 - RuleWhitelistWrapperBase.constructor - 57 + RuleWhitelistWrapperBase._detectTransferRestriction + 82 - RuleWhitelistWrapperBase._detectTransferRestriction - 66 + RuleWhitelistWrapperBase._checkRule + 106 - RuleWhitelistWrapperBase._detectTransferRestrictionForTargets - 102 + RuleWhitelistWrapperBase._detectTransferRestrictionForTargets + 121 - RuleWhitelistWrapperBase._msgSender - 175 + RuleWhitelistWrapperBase._msgSender + 189 - RuleWhitelistWrapperBase._contextSuffixLength - 177 + RuleWhitelistWrapperBase._contextSuffixLength + 191
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func.html index cc5b2b98..4a206883 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol - functions @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 87 - 88 - 98.9 % + 80 + 80 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 15 - 16 - 93.8 % + 13 + 13 + 100.0 % Branches: - 20 - 20 + 25 + 25 100.0 % @@ -69,68 +69,56 @@ Hit count Sort by hit count - RuleWhitelistWrapperBase._authorizeCheckSpenderManager - 0 + RuleWhitelistWrapperBase._checkRule + 106 - RuleWhitelistWrapperBase._contextSuffixLength - 177 + RuleWhitelistWrapperBase._contextSuffixLength + 191 - RuleWhitelistWrapperBase._detectTransferRestriction - 66 + RuleWhitelistWrapperBase._detectTransferRestriction + 82 - RuleWhitelistWrapperBase._detectTransferRestrictionForTargets - 102 + RuleWhitelistWrapperBase._detectTransferRestrictionForTargets + 121 - RuleWhitelistWrapperBase._detectTransferRestrictionFrom - 37 + RuleWhitelistWrapperBase._detectTransferRestrictionFrom + 38 - RuleWhitelistWrapperBase._isListedInAnyChild - 5 + RuleWhitelistWrapperBase._isListedInAnyChild + 14 - RuleWhitelistWrapperBase._msgData + RuleWhitelistWrapperBase._msgData 2 - RuleWhitelistWrapperBase._msgSender - 175 - - - RuleWhitelistWrapperBase._setCheckSpender - 3 + RuleWhitelistWrapperBase._msgSender + 189 - RuleWhitelistWrapperBase._transferred.0 - 20 + RuleWhitelistWrapperBase._transferred.0 + 28 - RuleWhitelistWrapperBase._transferred.1 + RuleWhitelistWrapperBase._transferred.1 1 - RuleWhitelistWrapperBase.constructor - 57 - - - RuleWhitelistWrapperBase.isVerified - 7 - - - RuleWhitelistWrapperBase.onlyCheckSpenderManager - 4 + RuleWhitelistWrapperBase.constructor + 64 - RuleWhitelistWrapperBase.setCheckSpender - 4 + RuleWhitelistWrapperBase.isVerified + 9 - RuleWhitelistWrapperBase.supportsInterface - 49 + RuleWhitelistWrapperBase.supportsInterface + 52
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.gcov.html index 487c3823..461dbb19 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol + LCOV - lcov2.info - src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 87 - 88 - 98.9 % + 80 + 80 + 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 15 - 16 - 93.8 % + 13 + 13 + 100.0 % Branches: - 20 - 20 + 25 + 25 100.0 % @@ -80,315 +80,317 @@ 9 : : /* ==== RuleEngine === */ 10 : : import {RulesManagementModule} from "RuleEngine/modules/RulesManagementModule.sol"; 11 : : /* ==== Interfaces === */ - 12 : : import {IAddressList} from "../../../interfaces/IAddressList.sol"; - 13 : : import {IIdentityRegistryVerified} from "../../../interfaces/IIdentityRegistry.sol"; - 14 : : - 15 : : /** - 16 : : * @title Wrapper to call several different whitelist rules (base) - 17 : : * @dev Child rules must implement {IAddressList}. - 18 : : */ - 19 : : abstract contract RuleWhitelistWrapperBase is - 20 : : RulesManagementModule, - 21 : : MetaTxModuleStandalone, - 22 : : RuleWhitelistShared, - 23 : : IIdentityRegistryVerified - 24 : : { - 25 : : /*////////////////////////////////////////////////////////////// - 26 : : CONSTRUCTOR - 27 : : //////////////////////////////////////////////////////////////*/ - 28 : : /** - 29 : : * @notice Deploys the whitelist wrapper base. - 30 : : * @dev The wrapper holds no addresses of its own — it ORs its child rules. It therefore needs its - 31 : : * OWN mint/burn flags: the children no longer list `address(0)`, so without these a mint - 32 : : * would resolve `from` as unlisted and be rejected. - 33 : : * @param forwarderIrrevocable Address of the forwarder, required for the gasless support - 34 : : * @param checkSpender_ Whether to also verify the spender on delegated transfers. - 35 : : * @param allowMintBurn When true, permits both minting and burning. - 36 : : */ - 37 : 57 : constructor(address forwarderIrrevocable, bool checkSpender_, bool allowMintBurn) - 38 : : MetaTxModuleStandalone(forwarderIrrevocable) - 39 : : { - 40 : 57 : checkSpender = checkSpender_; - 41 : 57 : _setAllowMintBurn(allowMintBurn, allowMintBurn); - 42 : : } - 43 : : - 44 : : /*////////////////////////////////////////////////////////////// - 45 : : ACCESS CONTROL - 46 : : //////////////////////////////////////////////////////////////*/ - 47 : : - 48 : 4 : modifier onlyCheckSpenderManager() { - 49 : 4 : _authorizeCheckSpenderManager(); - 50 : : _; - 51 : : } - 52 : : - 53 : : /*////////////////////////////////////////////////////////////// - 54 : : PUBLIC FUNCTIONS - 55 : : //////////////////////////////////////////////////////////////*/ - 56 : : - 57 : : /** - 58 : : * @notice Sets whether the rule should enforce spender-based checks. - 59 : : * @dev - 60 : : * - Restricted to holders of the manager role. - 61 : : * - Updates the internal `checkSpender` flag. - 62 : : * - Emits a {CheckSpenderUpdated} event. - 63 : : * @param value The new state of the `checkSpender` flag. - 64 : : */ - 65 : 4 : function setCheckSpender(bool value) public virtual onlyCheckSpenderManager { - 66 : 3 : _setCheckSpender(value); - 67 : 3 : emit CheckSpenderUpdated(value); - 68 : : } - 69 : : - 70 : : /** - 71 : : * @inheritdoc RuleTransferValidation + 12 : : import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; + 13 : : import {IAddressListBatchQuery, IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; + 14 : : import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; + 15 : : import {IIdentityRegistryVerified} from "../../../interfaces/IIdentityRegistry.sol"; + 16 : : + 17 : : /** + 18 : : * @title Wrapper to call several different whitelist rules (base) + 19 : : * @dev Child rules must implement {IAddressList} and must be ALLOW-lists. + 20 : : * + 21 : : * WARNING: {IAddressList} carries membership, not polarity. This wrapper ORs its children's + 22 : : * `areAddressesListed` answers and reads `true` as ELIGIBLE. A deny-list such as `RuleBlacklist` + 23 : : * satisfies the same interface and passes every check {addRule} performs, yet its set means the + 24 : : * opposite: add one as a child and its blacklisted addresses become whitelisted, and {isVerified} + 25 : : * reports them as verified investors. An ERC-165 guard would not catch this -- a blacklist advertises + 26 : : * the same interface id, because the interface really is the same. Polarity is configuration + 27 : : * discipline enforced by the rules manager, not by this contract. Nethermind AuditAgent NM-20. + 28 : : */ + 29 : : abstract contract RuleWhitelistWrapperBase is + 30 : : RulesManagementModule, + 31 : : MetaTxModuleStandalone, + 32 : : RuleWhitelistShared, + 33 : : IIdentityRegistryVerified + 34 : : { + 35 : : /*////////////////////////////////////////////////////////////// + 36 : : CONSTRUCTOR + 37 : : //////////////////////////////////////////////////////////////*/ + 38 : : /** + 39 : : * @notice Deploys the whitelist wrapper base. + 40 : : * @dev The wrapper holds no addresses of its own — it ORs its child rules. It therefore needs its + 41 : : * OWN mint/burn flags: the children no longer list `address(0)`, so without these a mint + 42 : : * would resolve `from` as unlisted and be rejected. + 43 : : * @param forwarderIrrevocable Address of the forwarder, required for the gasless support + 44 : : * @param checkSpender_ Whether to also verify the spender on delegated transfers. + 45 : : * @param allowMintBurn When true, permits both minting and burning. + 46 : : */ + 47 : 64 : constructor(address forwarderIrrevocable, bool checkSpender_, bool allowMintBurn) + 48 : : MetaTxModuleStandalone(forwarderIrrevocable) + 49 : : { + 50 : 64 : _setCheckSpender(checkSpender_); + 51 : 64 : _setAllowMintBurn(allowMintBurn, allowMintBurn); + 52 : : } + 53 : : + 54 : : /*////////////////////////////////////////////////////////////// + 55 : : PUBLIC FUNCTIONS + 56 : : //////////////////////////////////////////////////////////////*/ + 57 : : + 58 : : /** + 59 : : * @inheritdoc RuleTransferValidation + 60 : : */ + 61 : 52 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { + 62 : 52 : return RuleTransferValidation.supportsInterface(interfaceId); + 63 : : } + 64 : : + 65 : : /** + 66 : : * @notice Returns true if the address is listed in at least one child whitelist rule. + 67 : : * @dev Delegates to {_isListedInAnyChild}, the same single-address resolution the mint and burn + 68 : : * branches of {_detectTransferRestriction} use, so the ERC-3643 eligibility view and the + 69 : : * transfer check can never disagree about an address. + 70 : : * @param targetAddress The address to check across all child whitelist rules. + 71 : : * @return True if the address is listed in at least one child rule. 72 : : */ - 73 : 49 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleTransferValidation) returns (bool) { - 74 : 49 : return RuleTransferValidation.supportsInterface(interfaceId); + 73 : 9 : function isVerified(address targetAddress) public view virtual override(IIdentityRegistryVerified) returns (bool) { + 74 : 9 : return _isListedInAnyChild(targetAddress); 75 : : } 76 : : - 77 : : /** - 78 : : * @notice Returns true if the address is listed in at least one child whitelist rule. - 79 : : * @dev Delegates to the same child-rule scan used by transfer restriction checks. - 80 : : * @param targetAddress The address to check across all child whitelist rules. - 81 : : * @return True if the address is listed in at least one child rule. - 82 : : */ - 83 : 7 : function isVerified(address targetAddress) public view virtual override(IIdentityRegistryVerified) returns (bool) { - 84 : 7 : address[] memory targets = new address[](1); - 85 : 7 : targets[0] = targetAddress; - 86 : 7 : bool[] memory result = _detectTransferRestrictionForTargets(targets); - 87 : 7 : return result[0]; - 88 : : } - 89 : : - 90 : : /*////////////////////////////////////////////////////////////// - 91 : : INTERNAL FUNCTIONS - 92 : : //////////////////////////////////////////////////////////////*/ - 93 : : - 94 : : /** - 95 : : * @notice Authorizes the caller as check-spender manager; reverts otherwise. - 96 : : * @dev Implemented by concrete subclasses with the desired access-control policy. - 97 : : * `view` by convention: an access-control hook checks and reverts, it never mutates state. - 98 : : * Declaring it `view` makes that a compiler-enforced invariant rather than a convention. - 99 : : */ - 100 : 0 : function _authorizeCheckSpenderManager() internal view virtual; - 101 : : - 102 : : /** - 103 : : * @notice Internal helper to update the `checkSpender` flag. - 104 : : * @param value New flag value. - 105 : : */ - 106 : 3 : function _setCheckSpender(bool value) internal virtual { - 107 : 3 : checkSpender = value; - 108 : : } - 109 : : - 110 : : /** - 111 : : * @notice Go through all the whitelist rules to know if a restriction exists on the transfer - 112 : : * @param from the origin address - 113 : : * @param to the destination address - 114 : : * @return The restricion code or REJECTED_CODE_BASE.TRANSFER_OK - 115 : : * - 116 : : */ - 117 : 66 : function _detectTransferRestriction( - 118 : : address from, - 119 : : address to, - 120 : : uint256 /* value */ - 121 : : ) - 122 : : internal - 123 : : view - 124 : : virtual - 125 : : override - 126 : : returns (uint8) - 127 : : { - 128 : : // Gate the mint/burn OPERATION explicitly, before consulting any child rule. - 129 : 66 : uint8 mintBurnCode = _detectMintBurnRestriction(from, to); - 130 [ + ]: 66 : if (mintBurnCode != uint8(REJECTED_CODE_BASE.TRANSFER_OK)) { - 131 : 4 : return mintBurnCode; - 132 : : } - 133 : : - 134 : 62 : bool isMint = from == address(0); - 135 : 62 : bool isBurn = to == address(0); - 136 : : - 137 : : // Resolve only the REAL participants against the children: the zero address is a sentinel, - 138 : : // not a listed member of any child, so asking about it would always fail. - 139 : : // Degenerate (0, 0): neither leg is a real participant, so there is nothing to screen. - 140 : : // Handled explicitly so the wrapper and {RuleWhitelistBase} return the same answer — the two - 141 : : // share `_detectMintBurnRestriction` precisely so they cannot drift. - 142 [ + ]: 62 : if (isMint && isBurn) { - 143 : 2 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 144 : : } - 145 [ + ]: 3 : if (isMint) { - 146 [ + ]: 3 : if (!_isListedInAnyChild(to)) { - 147 : 1 : return CODE_ADDRESS_TO_NOT_WHITELISTED; - 148 : : } - 149 : 2 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 150 : : } - 151 [ + ]: 2 : if (isBurn) { - 152 [ + ]: 2 : if (!_isListedInAnyChild(from)) { - 153 : 1 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; - 154 : : } - 155 : 1 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 156 : : } - 157 : : - 158 : 55 : address[] memory targetAddress = new address[](2); - 159 : 55 : targetAddress[0] = from; - 160 : 55 : targetAddress[1] = to; - 161 : : - 162 : 55 : bool[] memory result = _detectTransferRestrictionForTargets(targetAddress); - 163 [ + + ]: 54 : if (!result[0]) { - 164 : 22 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; - 165 [ + + ]: 32 : } else if (!result[1]) { - 166 : 8 : return CODE_ADDRESS_TO_NOT_WHITELISTED; - 167 : : } else { - 168 : 24 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 169 : : } - 170 : : } - 171 : : - 172 : : /** - 173 : : * @notice Returns true when `targetAddress` is listed in at least one child rule. - 174 : : * @param targetAddress The address to resolve across the children. - 175 : : * @return True if listed in any child. - 176 : : */ - 177 : 5 : function _isListedInAnyChild(address targetAddress) internal view virtual returns (bool) { - 178 : 5 : address[] memory targets = new address[](1); - 179 : 5 : targets[0] = targetAddress; - 180 : 5 : return _detectTransferRestrictionForTargets(targets)[0]; - 181 : : } - 182 : : - 183 : : /** - 184 : : * @notice Go through all the whitelist rules to know if a delegated transfer is restricted. - 185 : : * @param spender The delegated spender address. - 186 : : * @param from The origin address. - 187 : : * @param to The destination address. - 188 : : * @param value The amount transferred. - 189 : : * @return The restriction code or REJECTED_CODE_BASE.TRANSFER_OK. - 190 : : */ - 191 : 37 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 192 : : internal - 193 : : view - 194 : : virtual - 195 : : override - 196 : : returns (uint8) - 197 : : { - 198 : : // Mint (from == address(0)) and burn (to == address(0)) are exempt from the spender check: - 199 : : // the minter/burner acts on its own authority, not as a delegated ERC-20 spender. - 200 [ + ]: 37 : if (!checkSpender || from == address(0) || to == address(0)) { - 201 : 2 : return _detectTransferRestriction(from, to, value); - 202 : : } - 203 : : - 204 : 35 : address[] memory targetAddress = new address[](3); - 205 : 35 : targetAddress[0] = from; - 206 : 35 : targetAddress[1] = to; - 207 : 35 : targetAddress[2] = spender; - 208 : : - 209 : 35 : bool[] memory result = _detectTransferRestrictionForTargets(targetAddress); - 210 : : - 211 [ + + ]: 35 : if (!result[0]) { - 212 : 9 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; - 213 [ + + ]: 26 : } else if (!result[1]) { - 214 : 1 : return CODE_ADDRESS_TO_NOT_WHITELISTED; - 215 [ + + ]: 25 : } else if (!result[2]) { - 216 : 8 : return CODE_ADDRESS_SPENDER_NOT_WHITELISTED; - 217 : : } else { - 218 : 17 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 219 : : } - 220 : : } - 221 : : - 222 : : // ERC-7943 tokenId overloads are provided by {RuleNFTAdapter} via RuleWhitelistShared. - 223 : : - 224 : : /** - 225 : : * @notice Reverts if a direct transfer is blocked by any child whitelist rule. - 226 : : * @param from The sender address. - 227 : : * @param to The recipient address. - 228 : : * @param value The amount transferred. - 229 : : */ - 230 : 20 : function _transferred(address from, address to, uint256 value) - 231 : : internal - 232 : : view - 233 : : virtual - 234 : : override(RulesManagementModule, RuleWhitelistShared) - 235 : : { - 236 : 20 : RuleWhitelistShared._transferred(from, to, value); - 237 : : } - 238 : : - 239 : : /** - 240 : : * @notice Reverts if a delegated transfer is blocked by any child whitelist rule. - 241 : : * @param spender The delegated spender address. - 242 : : * @param from The sender address. - 243 : : * @param to The recipient address. - 244 : : * @param value The amount transferred. - 245 : : */ - 246 : 1 : function _transferred(address spender, address from, address to, uint256 value) - 247 : : internal - 248 : : view - 249 : : virtual - 250 : : override(RulesManagementModule) - 251 : : { - 252 : 1 : RuleWhitelistShared._transferredFrom(spender, from, to, value); - 253 : : } - 254 : : - 255 : : /** - 256 : : * @notice Evaluates target addresses across all child rules. - 257 : : * @param targetAddress Addresses to validate (from/to[/spender]). - 258 : : * @return result Boolean array aligned with targetAddress indicating if each address is listed. - 259 : : */ - 260 : 102 : function _detectTransferRestrictionForTargets(address[] memory targetAddress) - 261 : : internal - 262 : : view - 263 : : virtual - 264 : : returns (bool[] memory) - 265 : : { - 266 : 102 : uint256 rulesLength = rulesCount(); - 267 : 102 : bool[] memory result = new bool[](targetAddress.length); - 268 : 102 : for (uint256 i = 0; i < rulesLength; ++i) { - 269 : : // Call the whitelist rules - 270 : : // Gas cost grows with the number of rules. Keep the wrapper list bounded. - 271 : 153 : bool[] memory isListed = IAddressList(rule(i)).areAddressesListed(targetAddress); - 272 : 152 : for (uint256 j = 0; j < targetAddress.length; ++j) { - 273 [ + ]: 160 : if (isListed[j]) { - 274 : 160 : result[j] = true; - 275 : : } - 276 : : } - 277 : : - 278 : : // Break early if all listed - 279 : 152 : bool allListed = true; - 280 : 152 : for (uint256 k = 0; k < result.length; ++k) { - 281 [ + ]: 278 : if (!result[k]) { - 282 : 105 : allListed = false; - 283 : 105 : break; + 77 : : /*////////////////////////////////////////////////////////////// + 78 : : INTERNAL FUNCTIONS + 79 : : //////////////////////////////////////////////////////////////*/ + 80 : : + 81 : : /** + 82 : : * @notice Go through all the whitelist rules to know if a restriction exists on the transfer + 83 : : * @param from the origin address + 84 : : * @param to the destination address + 85 : : * @return The restricion code or REJECTED_CODE_BASE.TRANSFER_OK + 86 : : * + 87 : : */ + 88 : 82 : function _detectTransferRestriction( + 89 : : address from, + 90 : : address to, + 91 : : uint256 /* value */ + 92 : : ) + 93 : : internal + 94 : : view + 95 : : virtual + 96 : : override + 97 : : returns (uint8) + 98 : : { + 99 : : // Gate the mint/burn OPERATION explicitly, before consulting any child rule. + 100 : 82 : uint8 mintBurnCode = _detectMintBurnRestriction(from, to); + 101 [ + ]: 82 : if (mintBurnCode != uint8(REJECTED_CODE_BASE.TRANSFER_OK)) { + 102 : 4 : return mintBurnCode; + 103 : : } + 104 : : + 105 : 78 : bool isMint = from == address(0); + 106 : 78 : bool isBurn = to == address(0); + 107 : : + 108 : : // Resolve only the REAL participants against the children: the zero address is a sentinel, + 109 : : // not a listed member of any child, so asking about it would always fail. + 110 : : // Degenerate (0, 0): neither leg is a real participant, so there is nothing to screen. + 111 : : // Handled explicitly so the wrapper and {RuleWhitelistBase} return the same answer — the two + 112 : : // share `_detectMintBurnRestriction` precisely so they cannot drift. + 113 [ + ]: 78 : if (isMint && isBurn) { + 114 : 2 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 115 : : } + 116 [ + ]: 3 : if (isMint) { + 117 [ + ]: 3 : if (!_isListedInAnyChild(to)) { + 118 : 1 : return CODE_ADDRESS_TO_NOT_WHITELISTED; + 119 : : } + 120 : 2 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 121 : : } + 122 [ + ]: 2 : if (isBurn) { + 123 [ + ]: 2 : if (!_isListedInAnyChild(from)) { + 124 : 1 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; + 125 : : } + 126 : 1 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 127 : : } + 128 : : + 129 : 71 : address[] memory targetAddress = new address[](2); + 130 : 71 : targetAddress[0] = from; + 131 : 71 : targetAddress[1] = to; + 132 : : + 133 : 71 : bool[] memory result = _detectTransferRestrictionForTargets(targetAddress); + 134 [ + + ]: 71 : if (!result[0]) { + 135 : 30 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; + 136 [ + + ]: 41 : } else if (!result[1]) { + 137 : 9 : return CODE_ADDRESS_TO_NOT_WHITELISTED; + 138 : : } else { + 139 : 32 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 140 : : } + 141 : : } + 142 : : + 143 : : /** + 144 : : * @notice Returns true when `targetAddress` is listed in at least one child rule. + 145 : : * @param targetAddress The address to resolve across the children. + 146 : : * @return True if listed in any child. + 147 : : */ + 148 : 14 : function _isListedInAnyChild(address targetAddress) internal view virtual returns (bool) { + 149 : 14 : address[] memory targets = new address[](1); + 150 : 14 : targets[0] = targetAddress; + 151 : 14 : return _detectTransferRestrictionForTargets(targets)[0]; + 152 : : } + 153 : : + 154 : : /** + 155 : : * @notice Go through all the whitelist rules to know if a delegated transfer is restricted. + 156 : : * @param spender The delegated spender address. + 157 : : * @param from The origin address. + 158 : : * @param to The destination address. + 159 : : * @param value The amount transferred. + 160 : : * @return The restriction code or REJECTED_CODE_BASE.TRANSFER_OK. + 161 : : */ + 162 : 38 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 163 : : internal + 164 : : view + 165 : : virtual + 166 : : override + 167 : : returns (uint8) + 168 : : { + 169 : : // Mint (from == address(0)) and burn (to == address(0)) are exempt from the spender check: + 170 : : // the minter/burner acts on its own authority, not as a delegated ERC-20 spender. + 171 [ + ]: 38 : if (!checkSpender || from == address(0) || to == address(0)) { + 172 : 2 : return _detectTransferRestriction(from, to, value); + 173 : : } + 174 : : + 175 : 36 : address[] memory targetAddress = new address[](3); + 176 : 36 : targetAddress[0] = from; + 177 : 36 : targetAddress[1] = to; + 178 : 36 : targetAddress[2] = spender; + 179 : : + 180 : 36 : bool[] memory result = _detectTransferRestrictionForTargets(targetAddress); + 181 : : + 182 [ + + ]: 36 : if (!result[0]) { + 183 : 9 : return CODE_ADDRESS_FROM_NOT_WHITELISTED; + 184 [ + + ]: 27 : } else if (!result[1]) { + 185 : 1 : return CODE_ADDRESS_TO_NOT_WHITELISTED; + 186 [ + + ]: 26 : } else if (!result[2]) { + 187 : 8 : return CODE_ADDRESS_SPENDER_NOT_WHITELISTED; + 188 : : } else { + 189 : 18 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); + 190 : : } + 191 : : } + 192 : : + 193 : : // ERC-7943 tokenId overloads are provided by {RuleNFTAdapter} via RuleWhitelistShared. + 194 : : + 195 : : /** + 196 : : * @notice Reverts if a direct transfer is blocked by any child whitelist rule. + 197 : : * @param from The sender address. + 198 : : * @param to The recipient address. + 199 : : * @param value The amount transferred. + 200 : : */ + 201 : 28 : function _transferred(address from, address to, uint256 value) + 202 : : internal + 203 : : view + 204 : : virtual + 205 : : override(RulesManagementModule, RuleWhitelistShared) + 206 : : { + 207 : 28 : RuleWhitelistShared._transferred(from, to, value); + 208 : : } + 209 : : + 210 : : /** + 211 : : * @notice Reverts if a delegated transfer is blocked by any child whitelist rule. + 212 : : * @param spender The delegated spender address. + 213 : : * @param from The sender address. + 214 : : * @param to The recipient address. + 215 : : * @param value The amount transferred. + 216 : : */ + 217 : 1 : function _transferred(address spender, address from, address to, uint256 value) + 218 : : internal + 219 : : view + 220 : : virtual + 221 : : override(RulesManagementModule) + 222 : : { + 223 : 1 : RuleWhitelistShared._transferredFrom(spender, from, to, value); + 224 : : } + 225 : : + 226 : : /** + 227 : : * @notice Rejects a child rule that cannot answer the only question this wrapper asks it. + 228 : : * @dev Mirrors `RuleEngineBase._checkRule`, which guards its own children the same way. The + 229 : : * requirement is {IAddressListBatchQuery} — a single function — rather than the whole of + 230 : : * {IAddressList}, because `areAddressesListed` is the only function the wrapper ever calls; + 231 : : * demanding the full interface would also require four write functions and three further reads, + 232 : : * excluding a read-only child that works perfectly. + 233 : : * + 234 : : * `ERC165Checker.supportsInterface` is itself non-reverting -- a bounded staticcall returning + 235 : : * false for a codeless address, a missing selector or malformed return data -- so a hostile + 236 : : * candidate cannot brick the setter screening it. + 237 : : * + 238 : : * WARNING: this cannot check POLARITY. A deny-list answers `areAddressesListed` just as + 239 : : * faithfully as an allow-list and advertises the same id, so it passes here and then inverts the + 240 : : * wrapper's meaning. Children must be allow-lists by configuration; see the contract-level note. + 241 : : * @param rule_ The candidate child rule. + 242 : : */ + 243 : 106 : function _checkRule(address rule_) internal view virtual override { + 244 : 106 : RulesManagementModule._checkRule(rule_); + 245 [ + + ]: 106 : require( + 246 : : ERC165Checker.supportsInterface(rule_, AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID), + 247 : : RuleWhitelistWrapper_ChildIsNotAnAddressList(rule_) + 248 : : ); + 249 : : // Membership alone is not enough: the child must also say what membership MEANS. Absence of the + 250 : : // declaration is a refusal, never an assumed allow-list -- the only reading that fails closed. + 251 [ + + ]: 104 : require( + 252 : : ERC165Checker.supportsInterface(rule_, AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID), + 253 : : RuleWhitelistWrapper_ChildDoesNotDeclarePolarity(rule_) + 254 : : ); + 255 [ + + ]: 103 : require(IAddressListPolarity(rule_).isAllowList(), RuleWhitelistWrapper_ChildIsNotAnAllowList(rule_)); + 256 : : } + 257 : : + 258 : : /** + 259 : : * @notice Evaluates target addresses across all child rules. + 260 : : * @param targetAddress Addresses to validate (from/to[/spender]). + 261 : : * @return result Boolean array aligned with targetAddress indicating if each address is listed. + 262 : : */ + 263 : 121 : function _detectTransferRestrictionForTargets(address[] memory targetAddress) + 264 : : internal + 265 : : view + 266 : : virtual + 267 : : returns (bool[] memory) + 268 : : { + 269 : 121 : uint256 rulesLength = rulesCount(); + 270 : 121 : uint256 targetsLength = targetAddress.length; + 271 : 121 : bool[] memory result = new bool[](targetsLength); + 272 : : // Number of targets not yet found in any child. Decremented the first time a target is + 273 : : // resolved, so the early exit below is an O(1) test rather than a full rescan of `result` + 274 : : // on every child rule. The observable result is identical. + 275 : 121 : uint256 unresolved = targetsLength; + 276 : 121 : for (uint256 i = 0; i < rulesLength; ++i) { + 277 : : // Call the whitelist rules + 278 : : // Gas cost grows with the number of rules. Keep the wrapper list bounded. + 279 : 174 : bool[] memory isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress); + 280 : 174 : for (uint256 j = 0; j < targetsLength; ++j) { + 281 [ + ]: 386 : if (isListed[j] && !result[j]) { + 282 : 188 : result[j] = true; + 283 : 188 : --unresolved; 284 : : } 285 : : } - 286 [ + ]: 47 : if (allListed) { - 287 : 47 : break; - 288 : : } - 289 : : } - 290 : 101 : return result; - 291 : : } - 292 : : - 293 : : /*////////////////////////////////////////////////////////////// - 294 : : ERC-2771 - 295 : : //////////////////////////////////////////////////////////////*/ - 296 : : - 297 : : /** - 298 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule - 299 : : * @return sender The effective message sender, unwrapped from the meta-transaction if present. - 300 : : */ - 301 : 175 : function _msgSender() internal view virtual override(ERC2771Context) returns (address sender) { - 302 : 175 : return ERC2771Context._msgSender(); - 303 : : } - 304 : : - 305 : : /** - 306 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule - 307 : : * @return The effective calldata, unwrapped from the meta-transaction if present. - 308 : : */ - 309 : 2 : function _msgData() internal view virtual override(ERC2771Context) returns (bytes calldata) { - 310 : 2 : return ERC2771Context._msgData(); - 311 : : } - 312 : : - 313 : : /** - 314 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule - 315 : : * @return The length of the ERC-2771 context suffix appended to calldata. - 316 : : */ - 317 : 177 : function _contextSuffixLength() internal view virtual override(ERC2771Context) returns (uint256) { - 318 : 177 : return ERC2771Context._contextSuffixLength(); - 319 : : } - 320 : : } + 286 : : + 287 : : // Break early if all listed + 288 [ + ]: 174 : if (unresolved == 0) { + 289 : 57 : break; + 290 : : } + 291 : : } + 292 : 121 : return result; + 293 : : } + 294 : : + 295 : : /*////////////////////////////////////////////////////////////// + 296 : : ERC-2771 + 297 : : //////////////////////////////////////////////////////////////*/ + 298 : : + 299 : : /** + 300 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule + 301 : : * @return sender The effective message sender, unwrapped from the meta-transaction if present. + 302 : : */ + 303 : 189 : function _msgSender() internal view virtual override(ERC2771Context) returns (address sender) { + 304 : 189 : return ERC2771Context._msgSender(); + 305 : : } + 306 : : + 307 : : /** + 308 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule + 309 : : * @return The effective calldata, unwrapped from the meta-transaction if present. + 310 : : */ + 311 : 2 : function _msgData() internal view virtual override(ERC2771Context) returns (bytes calldata) { + 312 : 2 : return ERC2771Context._msgData(); + 313 : : } + 314 : : + 315 : : /** + 316 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule + 317 : : * @return The length of the ERC-2771 context suffix appended to calldata. + 318 : : */ + 319 : 191 : function _contextSuffixLength() internal view virtual override(ERC2771Context) returns (uint256) { + 320 : 191 : return ERC2771Context._contextSuffixLength(); + 321 : : } + 322 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-b.html b/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-b.html index f7db9bb3..2cd0e3c6 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base + LCOV - lcov2.info - src/rules/validation/abstract/base @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 447 - 457 - 97.8 % + 545 + 552 + 98.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 117 - 127 - 92.1 % + 143 + 150 + 95.3 % Branches: - 124 - 124 + 159 + 159 100.0 % @@ -87,7 +87,7 @@
100.0%
100.0 % - 22 / 22 + 23 / 23 100.0 % 9 / 9 100.0 % @@ -96,26 +96,50 @@ RuleWhitelistBase.sol -
96.8%96.8%
+
100.0%
- 96.8 % - 30 / 31 - 88.9 % - 8 / 9 + 100.0 % + 27 / 27 + 100.0 % + 6 / 6 100.0 % 4 / 4 + + RuleReceiverWhitelistBase.sol + +
100.0%
+ + 100.0 % + 29 / 29 + 100.0 % + 10 / 10 + 100.0 % + 6 / 6 + RuleMaxTotalSupplyBase.sol -
97.4%97.4%
+
100.0%
- 97.4 % - 37 / 38 - 92.3 % - 12 / 13 + 100.0 % + 33 / 33 + 100.0 % + 10 / 10 + 100.0 % + 10 / 10 + + + RuleMaxBalanceBase.sol + +
100.0%
+ + 100.0 % + 37 / 37 100.0 % 11 / 11 + 100.0 % + 10 / 10 RuleBlacklistBase.sol @@ -123,9 +147,9 @@
100.0%
100.0 % - 34 / 34 + 38 / 38 100.0 % - 9 / 9 + 10 / 10 100.0 % 14 / 14 @@ -139,7 +163,19 @@ 92.9 % 13 / 14 100.0 % - 18 / 18 + 17 / 17 + + + RuleChainlinkPoRBase.sol + +
100.0%
+ + 100.0 % + 46 / 46 + 100.0 % + 10 / 10 + 100.0 % + 17 / 17 RuleIdentityRegistryBase.sol @@ -147,31 +183,31 @@
98.4%98.4%
98.4 % - 63 / 64 + 62 / 63 93.3 % 14 / 15 100.0 % - 19 / 19 + 18 / 18 RuleWhitelistWrapperBase.sol -
98.9%98.9%
+
100.0%
- 98.9 % - 87 / 88 - 93.8 % - 15 / 16 100.0 % - 20 / 20 + 80 / 80 + 100.0 % + 13 / 13 + 100.0 % + 25 / 25 RuleERC2980Base.sol -
96.2%96.2%
+
96.1%96.1%
- 96.2 % - 127 / 132 + 96.1 % + 123 / 128 88.1 % 37 / 42 100.0 % diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-f.html b/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-f.html index e99a4a39..f181b392 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base + LCOV - lcov2.info - src/rules/validation/abstract/base @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 447 - 457 - 97.8 % + 545 + 552 + 98.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 117 - 127 - 92.1 % + 143 + 150 + 95.3 % Branches: - 124 - 124 + 159 + 159 100.0 % @@ -84,39 +84,15 @@ RuleERC2980Base.sol -
96.2%96.2%
+
96.1%96.1%
- 96.2 % - 127 / 132 + 96.1 % + 123 / 128 88.1 % 37 / 42 100.0 % 34 / 34 - - RuleWhitelistBase.sol - -
96.8%96.8%
- - 96.8 % - 30 / 31 - 88.9 % - 8 / 9 - 100.0 % - 4 / 4 - - - RuleMaxTotalSupplyBase.sol - -
97.4%97.4%
- - 97.4 % - 37 / 38 - 92.3 % - 12 / 13 - 100.0 % - 11 / 11 - RuleSanctionsListBase.sol @@ -127,7 +103,7 @@ 92.9 % 13 / 14 100.0 % - 18 / 18 + 17 / 17 RuleIdentityRegistryBase.sol @@ -135,47 +111,107 @@
98.4%98.4%
98.4 % - 63 / 64 + 62 / 63 93.3 % 14 / 15 100.0 % - 19 / 19 + 18 / 18 - RuleWhitelistWrapperBase.sol + RuleWhitelistBase.sol -
98.9%98.9%
+
100.0%
- 98.9 % - 87 / 88 - 93.8 % - 15 / 16 100.0 % - 20 / 20 + 27 / 27 + 100.0 % + 6 / 6 + 100.0 % + 4 / 4 - RuleBlacklistBase.sol + RuleSpenderWhitelistBase.sol
100.0%
100.0 % - 34 / 34 + 23 / 23 100.0 % 9 / 9 100.0 % + 4 / 4 + + + RuleMaxTotalSupplyBase.sol + +
100.0%
+ + 100.0 % + 33 / 33 + 100.0 % + 10 / 10 + 100.0 % + 10 / 10 + + + RuleBlacklistBase.sol + +
100.0%
+ + 100.0 % + 38 / 38 + 100.0 % + 10 / 10 + 100.0 % 14 / 14 - RuleSpenderWhitelistBase.sol + RuleChainlinkPoRBase.sol
100.0%
100.0 % - 22 / 22 + 46 / 46 100.0 % - 9 / 9 + 10 / 10 100.0 % - 4 / 4 + 17 / 17 + + + RuleReceiverWhitelistBase.sol + +
100.0%
+ + 100.0 % + 29 / 29 + 100.0 % + 10 / 10 + 100.0 % + 6 / 6 + + + RuleMaxBalanceBase.sol + +
100.0%
+ + 100.0 % + 37 / 37 + 100.0 % + 11 / 11 + 100.0 % + 10 / 10 + + + RuleWhitelistWrapperBase.sol + +
100.0%
+ + 100.0 % + 80 / 80 + 100.0 % + 13 / 13 + 100.0 % + 25 / 25 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-l.html b/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-l.html index 210159a8..386be709 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base + LCOV - lcov2.info - src/rules/validation/abstract/base @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 447 - 457 - 97.8 % + 545 + 552 + 98.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 117 - 127 - 92.1 % + 143 + 150 + 95.3 % Branches: - 124 - 124 + 159 + 159 100.0 % @@ -84,39 +84,15 @@ RuleERC2980Base.sol -
96.2%96.2%
+
96.1%96.1%
- 96.2 % - 127 / 132 + 96.1 % + 123 / 128 88.1 % 37 / 42 100.0 % 34 / 34 - - RuleWhitelistBase.sol - -
96.8%96.8%
- - 96.8 % - 30 / 31 - 88.9 % - 8 / 9 - 100.0 % - 4 / 4 - - - RuleMaxTotalSupplyBase.sol - -
97.4%97.4%
- - 97.4 % - 37 / 38 - 92.3 % - 12 / 13 - 100.0 % - 11 / 11 - RuleSanctionsListBase.sol @@ -127,7 +103,7 @@ 92.9 % 13 / 14 100.0 % - 18 / 18 + 17 / 17 RuleIdentityRegistryBase.sol @@ -135,48 +111,108 @@
98.4%98.4%
98.4 % - 63 / 64 + 62 / 63 93.3 % 14 / 15 100.0 % - 19 / 19 + 18 / 18 - RuleWhitelistWrapperBase.sol + RuleSpenderWhitelistBase.sol -
98.9%98.9%
+
100.0%
- 98.9 % - 87 / 88 - 93.8 % - 15 / 16 100.0 % - 20 / 20 + 23 / 23 + 100.0 % + 9 / 9 + 100.0 % + 4 / 4 - RuleSpenderWhitelistBase.sol + RuleWhitelistBase.sol
100.0%
100.0 % - 22 / 22 + 27 / 27 100.0 % - 9 / 9 + 6 / 6 100.0 % 4 / 4 + + RuleReceiverWhitelistBase.sol + +
100.0%
+ + 100.0 % + 29 / 29 + 100.0 % + 10 / 10 + 100.0 % + 6 / 6 + + + RuleMaxTotalSupplyBase.sol + +
100.0%
+ + 100.0 % + 33 / 33 + 100.0 % + 10 / 10 + 100.0 % + 10 / 10 + + + RuleMaxBalanceBase.sol + +
100.0%
+ + 100.0 % + 37 / 37 + 100.0 % + 11 / 11 + 100.0 % + 10 / 10 + RuleBlacklistBase.sol
100.0%
100.0 % - 34 / 34 + 38 / 38 100.0 % - 9 / 9 + 10 / 10 100.0 % 14 / 14 + + RuleChainlinkPoRBase.sol + +
100.0%
+ + 100.0 % + 46 / 46 + 100.0 % + 10 / 10 + 100.0 % + 17 / 17 + + + RuleWhitelistWrapperBase.sol + +
100.0%
+ + 100.0 % + 80 / 80 + 100.0 % + 13 / 13 + 100.0 % + 25 / 25 +
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/base/index.html b/doc/coverage/coverage/src/rules/validation/abstract/base/index.html index 827de9b0..8fadea15 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/base/index.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/base/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/base + LCOV - lcov2.info - src/rules/validation/abstract/base @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 447 - 457 - 97.8 % + 545 + 552 + 98.7 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 117 - 127 - 92.1 % + 143 + 150 + 95.3 % Branches: - 124 - 124 + 159 + 159 100.0 % @@ -87,19 +87,31 @@
100.0%
100.0 % - 34 / 34 + 38 / 38 100.0 % - 9 / 9 + 10 / 10 100.0 % 14 / 14 + + RuleChainlinkPoRBase.sol + +
100.0%
+ + 100.0 % + 46 / 46 + 100.0 % + 10 / 10 + 100.0 % + 17 / 17 + RuleERC2980Base.sol -
96.2%96.2%
+
96.1%96.1%
- 96.2 % - 127 / 132 + 96.1 % + 123 / 128 88.1 % 37 / 42 100.0 % @@ -111,23 +123,47 @@
98.4%98.4%
98.4 % - 63 / 64 + 62 / 63 93.3 % 14 / 15 100.0 % - 19 / 19 + 18 / 18 - RuleMaxTotalSupplyBase.sol + RuleMaxBalanceBase.sol -
97.4%97.4%
+
100.0%
- 97.4 % - 37 / 38 - 92.3 % - 12 / 13 + 100.0 % + 37 / 37 100.0 % 11 / 11 + 100.0 % + 10 / 10 + + + RuleMaxTotalSupplyBase.sol + +
100.0%
+ + 100.0 % + 33 / 33 + 100.0 % + 10 / 10 + 100.0 % + 10 / 10 + + + RuleReceiverWhitelistBase.sol + +
100.0%
+ + 100.0 % + 29 / 29 + 100.0 % + 10 / 10 + 100.0 % + 6 / 6 RuleSanctionsListBase.sol @@ -139,7 +175,7 @@ 92.9 % 13 / 14 100.0 % - 18 / 18 + 17 / 17 RuleSpenderWhitelistBase.sol @@ -147,7 +183,7 @@
100.0%
100.0 % - 22 / 22 + 23 / 23 100.0 % 9 / 9 100.0 % @@ -156,26 +192,26 @@ RuleWhitelistBase.sol -
96.8%96.8%
+
100.0%
- 96.8 % - 30 / 31 - 88.9 % - 8 / 9 + 100.0 % + 27 / 27 + 100.0 % + 6 / 6 100.0 % 4 / 4 RuleWhitelistWrapperBase.sol -
98.9%98.9%
+
100.0%
- 98.9 % - 87 / 88 - 93.8 % - 15 / 16 100.0 % - 20 / 20 + 80 / 80 + 100.0 % + 13 / 13 + 100.0 % + 25 / 25 diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.func-sort-c.html new file mode 100644 index 00000000..9706bee4 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.func-sort-c.html @@ -0,0 +1,149 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/BalanceCapManager.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - BalanceCapManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:585998.3 %
Date:2026-08-19 15:38:25Functions:161794.1 %
Branches:1818100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
BalanceCapManager._authorizeMaxBalanceManager0
BalanceCapManager.removeExemptAddresses2
BalanceCapManager._removeExemptAddress3
BalanceCapManager.removeExemptAddress3
BalanceCapManager.addExemptAddresses4
BalanceCapManager.onlyMaxBalanceManager4
BalanceCapManager._remainingCapacity5
BalanceCapManager.isExemptAddress5
BalanceCapManager.setBalanceToken5
BalanceCapManager.exemptAddressCount6
BalanceCapManager.setMaxBalance6
BalanceCapManager._addExemptAddress10
BalanceCapManager.addExemptAddress12
BalanceCapManager._balanceOf44
BalanceCapManager._capExceeded46
BalanceCapManager._setMaxBalance64
BalanceCapManager._setBalanceToken67
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.func.html new file mode 100644 index 00000000..aa5d07d2 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.func.html @@ -0,0 +1,149 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/BalanceCapManager.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - BalanceCapManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:585998.3 %
Date:2026-08-19 15:38:25Functions:161794.1 %
Branches:1818100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
BalanceCapManager._addExemptAddress10
BalanceCapManager._authorizeMaxBalanceManager0
BalanceCapManager._balanceOf44
BalanceCapManager._capExceeded46
BalanceCapManager._remainingCapacity5
BalanceCapManager._removeExemptAddress3
BalanceCapManager._setBalanceToken67
BalanceCapManager._setMaxBalance64
BalanceCapManager.addExemptAddress12
BalanceCapManager.addExemptAddresses4
BalanceCapManager.exemptAddressCount6
BalanceCapManager.isExemptAddress5
BalanceCapManager.onlyMaxBalanceManager4
BalanceCapManager.removeExemptAddress3
BalanceCapManager.removeExemptAddresses2
BalanceCapManager.setBalanceToken5
BalanceCapManager.setMaxBalance6
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.gcov.html new file mode 100644 index 00000000..42373870 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/BalanceCapManager.sol.gcov.html @@ -0,0 +1,351 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/BalanceCapManager.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - BalanceCapManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:585998.3 %
Date:2026-08-19 15:38:25Functions:161794.1 %
Branches:1818100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleMaxBalanceInvariantStorage} from "../invariant/RuleMaxBalanceInvariantStorage.sol";
+       5                 :            : import {IBalanceOf} from "../../../interfaces/IBalanceOf.sol";
+       6                 :            : import {RuleAddressSetInternal} from "../RuleAddressSet/RuleAddressSetInternal.sol";
+       7                 :            : import {CapAccounting} from "./CapAccounting.sol";
+       8                 :            : 
+       9                 :            : /**
+      10                 :            :  * @title BalanceCapManager
+      11                 :            :  * @notice Per-address holding cap: the observed token, the cap, the exemption list, and how much a
+      12                 :            :  * given address may still receive.
+      13                 :            :  *
+      14                 :            :  * @dev Declares **no constructor**, so an upgradeable variant can set the same state from an
+      15                 :            :  * initializer. {_capExceeded} and {_remainingCapacity} answer in booleans and token units, leaving
+      16                 :            :  * the restriction codes to the rule.
+      17                 :            :  *
+      18                 :            :  * @dev `maxBalance = 0` forbids holding entirely; it does not disable the cap.
+      19                 :            :  *
+      20                 :            :  * @dev {_balanceOf} must never revert, because the rule calls it from MUST-NOT-revert views, so it is
+      21                 :            :  * `try/catch`-wrapped. That is only safe because the setter requires the token to have code and
+      22                 :            :  * EIP-6780 makes it permanent: a `try` to a codeless address reverts *uncatchably*. Assumes a
+      23                 :            :  * Cancun-or-later chain.
+      24                 :            :  *
+      25                 :            :  * @dev The exemption list reuses {RuleAddressSetInternal}, so the set storage, the zero-address guard
+      26                 :            :  * and the batch semantics are shared code rather than a second implementation.
+      27                 :            :  */
+      28                 :            : abstract contract BalanceCapManager is CapAccounting, RuleAddressSetInternal, RuleMaxBalanceInvariantStorage {
+      29                 :            :     /**
+      30                 :            :      * @notice The token whose balances are observed.
+      31                 :            :      * @dev Trusted to report an accurate balance; not trusted to stay callable.
+      32                 :            :      */
+      33                 :            :     IBalanceOf public balanceToken;
+      34                 :            :     /**
+      35                 :            :      * @notice Maximum number of tokens a single non-exempt address may hold.
+      36                 :            :      */
+      37                 :            :     uint256 public maxBalance;
+      38                 :            : 
+      39                 :            :     /*//////////////////////////////////////////////////////////////
+      40                 :            :                             ACCESS CONTROL
+      41                 :            :     //////////////////////////////////////////////////////////////*/
+      42                 :            : 
+      43                 :          4 :     modifier onlyMaxBalanceManager() {
+      44                 :          4 :         _authorizeMaxBalanceManager();
+      45                 :            :         _;
+      46                 :            :     }
+      47                 :            : 
+      48                 :            :     /*//////////////////////////////////////////////////////////////
+      49                 :            :                         PUBLIC FUNCTIONS
+      50                 :            :     //////////////////////////////////////////////////////////////*/
+      51                 :            : 
+      52                 :            :     /**
+      53                 :            :      * @notice Updates the maximum balance allowed per non-exempt address.
+      54                 :            :      * @dev Lowering the cap does **not** claw back balances that already exceed it. Existing holders
+      55                 :            :      * keep their tokens and may still send them away; they simply cannot receive more until they are
+      56                 :            :      * back under the cap.
+      57                 :            :      * @param newMaxBalance The new cap. `0` forbids holding entirely; it does not disable the cap.
+      58                 :            :      */
+      59                 :          6 :     function setMaxBalance(uint256 newMaxBalance) public virtual onlyMaxBalanceManager {
+      60                 :          4 :         _setMaxBalance(newMaxBalance);
+      61                 :            :     }
+      62                 :            : 
+      63                 :            :     /**
+      64                 :            :      * @notice Updates the token whose balances are observed.
+      65                 :            :      * @param newBalanceToken The new token contract; must be a contract exposing `balanceOf`.
+      66                 :            :      */
+      67                 :          5 :     function setBalanceToken(address newBalanceToken) public virtual onlyMaxBalanceManager {
+      68                 :          4 :         _setBalanceToken(newBalanceToken);
+      69                 :            :     }
+      70                 :            : 
+      71                 :            :     /**
+      72                 :            :      * @notice Exempts an address from the cap.
+      73                 :            :      * @dev Reverts if the address is already exempt, matching the single-item convention used
+      74                 :            :      * elsewhere in the library. `address(0)` is rejected: it is the mint/burn sentinel, never a
+      75                 :            :      * holder.
+      76                 :            :      * @param targetAddress The address to exempt.
+      77                 :            :      */
+      78                 :         12 :     function addExemptAddress(address targetAddress) public virtual onlyMaxBalanceManager {
+      79                 :         10 :         _addExemptAddress(targetAddress);
+      80                 :            :     }
+      81                 :            : 
+      82                 :            :     /**
+      83                 :            :      * @notice Removes an address's exemption.
+      84                 :            :      * @dev Reverts if the address is not exempt. The address keeps whatever it already holds; it
+      85                 :            :      * simply cannot receive more once over the cap.
+      86                 :            :      * @param targetAddress The address to bring back under the cap.
+      87                 :            :      */
+      88                 :          3 :     function removeExemptAddress(address targetAddress) public virtual onlyMaxBalanceManager {
+      89                 :          3 :         _removeExemptAddress(targetAddress);
+      90                 :            :     }
+      91                 :            : 
+      92                 :            :     /**
+      93                 :            :      * @notice Exempts several addresses in one call.
+      94                 :            :      * @dev Duplicates are skipped and counted rather than reverting; `address(0)` rejects the whole
+      95                 :            :      * batch. Both follow the library-wide batch convention.
+      96                 :            :      * @param targetAddresses The addresses to exempt.
+      97                 :            :      */
+      98                 :          4 :     function addExemptAddresses(address[] calldata targetAddresses) public virtual onlyMaxBalanceManager {
+      99                 :          4 :         (uint256 added, uint256 skipped) = _addAddresses(targetAddresses);
+     100                 :          3 :         emit ExemptAddressesAdded(targetAddresses, added, skipped);
+     101                 :            :     }
+     102                 :            : 
+     103                 :            :     /**
+     104                 :            :      * @notice Removes the exemption from several addresses in one call.
+     105                 :            :      * @dev Addresses that are not exempt are skipped and counted rather than reverting.
+     106                 :            :      * @param targetAddresses The addresses to bring back under the cap.
+     107                 :            :      */
+     108                 :          2 :     function removeExemptAddresses(address[] calldata targetAddresses) public virtual onlyMaxBalanceManager {
+     109                 :          2 :         (uint256 removed, uint256 skipped) = _removeAddresses(targetAddresses);
+     110                 :          2 :         emit ExemptAddressesRemoved(targetAddresses, removed, skipped);
+     111                 :            :     }
+     112                 :            : 
+     113                 :            :     /**
+     114                 :            :      * @notice Returns whether an address is exempt from the cap.
+     115                 :            :      * @param targetAddress The address to test.
+     116                 :            :      * @return True when the address may hold any amount.
+     117                 :            :      */
+     118                 :          5 :     function isExemptAddress(address targetAddress) public view virtual returns (bool) {
+     119                 :          5 :         return _isAddressListed(targetAddress);
+     120                 :            :     }
+     121                 :            : 
+     122                 :            :     /**
+     123                 :            :      * @notice Returns how many addresses are exempt.
+     124                 :            :      * @return The number of exempt addresses.
+     125                 :            :      */
+     126                 :          6 :     function exemptAddressCount() public view virtual returns (uint256) {
+     127                 :          6 :         return _listedAddressCount();
+     128                 :            :     }
+     129                 :            : 
+     130                 :            :     /*//////////////////////////////////////////////////////////////
+     131                 :            :                         INTERNAL FUNCTIONS
+     132                 :            :     //////////////////////////////////////////////////////////////*/
+     133                 :            : 
+     134                 :            :     /**
+     135                 :            :      * @notice Exempts an address: guards, writes and announces, in one place.
+     136                 :            :      * @dev Owns the guards as well as the event, so every write path gets both. A subclass that
+     137                 :            :      *      wanted to pre-exempt a treasury address from its constructor can call this instead of
+     138                 :            :      *      restating the two `require`s, which is what the scalar setters already do via
+     139                 :            :      *      {_setMaxBalance} and {_setBalanceToken}.
+     140                 :            :      *
+     141                 :            :      *      `_addAddress` does not guard the sentinel; the caller must, exactly as the whitelist
+     142                 :            :      *      rules and `IdentityRegistryWhitelist` do. The batch path is guarded separately, by the
+     143                 :            :      *      function pointer `_addAddresses` passes to `AddressSetBatchLib`. Invariant I-12.
+     144                 :            :      * @param targetAddress The address to exempt.
+     145                 :            :      */
+     146                 :         10 :     function _addExemptAddress(address targetAddress) internal virtual {
+     147         [ +  + ]:         10 :         require(targetAddress != address(0), RuleAddressSet_ZeroAddressNotAllowed());
+     148         [ +  + ]:          9 :         require(_addAddress(targetAddress), RuleAddressSet_AddressAlreadyListed());
+     149                 :          8 :         emit ExemptAddressAdded(targetAddress);
+     150                 :            :     }
+     151                 :            : 
+     152                 :            :     /**
+     153                 :            :      * @notice Removes an exemption: guards, writes and announces, in one place.
+     154                 :            :      * @param targetAddress The address to bring back under the cap.
+     155                 :            :      */
+     156                 :          3 :     function _removeExemptAddress(address targetAddress) internal virtual {
+     157         [ +  + ]:          3 :         require(_removeAddress(targetAddress), RuleAddressSet_AddressNotFound());
+     158                 :          2 :         emit ExemptAddressRemoved(targetAddress);
+     159                 :            :     }
+     160                 :            : 
+     161                 :            :     /**
+     162                 :            :      * @notice Stores the cap and emits {MaxBalanceUpdated}.
+     163                 :            :      * @param newMaxBalance The new cap.
+     164                 :            :      */
+     165                 :         64 :     function _setMaxBalance(uint256 newMaxBalance) internal virtual {
+     166                 :         64 :         maxBalance = newMaxBalance;
+     167                 :         64 :         emit MaxBalanceUpdated(newMaxBalance);
+     168                 :            :     }
+     169                 :            : 
+     170                 :            :     /**
+     171                 :            :      * @notice Stores the observed token and emits {MaxBalanceTokenUpdated}.
+     172                 :            :      * @dev Probes `balanceOf` at configuration time so a token that cannot serve the check fails
+     173                 :            :      * loudly at setup instead of silently blocking every transfer.
+     174                 :            :      * @param newBalanceToken The new token contract.
+     175                 :            :      */
+     176                 :         67 :     function _setBalanceToken(address newBalanceToken) internal virtual {
+     177         [ +  + ]:         67 :         require(newBalanceToken != address(0), RuleMaxBalance_TokenAddressZeroNotAllowed());
+     178                 :            :         // Explicit, rather than relying on the uncatchable extcodesize revert the probe below happens
+     179                 :            :         // to produce for a codeless address: that is compiler behaviour, not a check.
+     180         [ +  + ]:         66 :         require(newBalanceToken.code.length != 0, RuleMaxBalance_TokenIsNotAContract(newBalanceToken));
+     181            [ + ]:         64 :         try IBalanceOf(newBalanceToken).balanceOf(address(this)) returns (uint256) {
+     182                 :            :         // callable
+     183                 :            :         }
+     184            [ + ]:          2 :         catch {
+     185                 :          2 :             revert RuleMaxBalance_TokenBalanceUnavailable(newBalanceToken);
+     186                 :            :         }
+     187                 :         62 :         balanceToken = IBalanceOf(newBalanceToken);
+     188                 :         62 :         emit MaxBalanceTokenUpdated(newBalanceToken);
+     189                 :            :     }
+     190                 :            : 
+     191                 :            :     /**
+     192                 :            :      * @notice Authorization hook invoked before any configuration or exemption change.
+     193                 :            :      * @dev Implemented by concrete subclasses with the desired access-control policy.
+     194                 :            :      */
+     195                 :          0 :     function _authorizeMaxBalanceManager() internal view virtual;
+     196                 :            : 
+     197                 :            :     /**
+     198                 :            :      * @notice Returns the balance `to` may still receive before reaching the cap.
+     199                 :            :      * @dev Never reverts. Kept `internal` and code-free deliberately: the rule wraps it in a public
+     200                 :            :      * `remainingCapacity` that reports an ERC-1404 restriction code, which is a concern this
+     201                 :            :      * contract does not carry.
+     202                 :            :      * @param to The prospective receiver.
+     203                 :            :      * @return balanceAvailable False when the balance could not be read; `headroom` is then
+     204                 :            :      * meaningless and the caller should treat the query as failed.
+     205                 :            :      * @return headroom Remaining capacity in token units. `type(uint256).max` for an exempt address
+     206                 :            :      * or the burn sentinel.
+     207                 :            :      */
+     208                 :          5 :     function _remainingCapacity(address to) internal view virtual returns (bool balanceAvailable, uint256 headroom) {
+     209            [ + ]:          5 :         if (to == address(0) || _isAddressListed(to)) {
+     210                 :          1 :             return (true, type(uint256).max);
+     211                 :            :         }
+     212                 :          4 :         (bool available, uint256 balance) = _balanceOf(to);
+     213            [ + ]:          4 :         if (!available) {
+     214                 :          1 :             return (false, 0);
+     215                 :            :         }
+     216                 :          3 :         return (true, _capHeadroom(balance, maxBalance));
+     217                 :            :     }
+     218                 :            : 
+     219                 :            :     /**
+     220                 :            :      * @notice Reads an address's balance without ever reverting.
+     221                 :            :      * @dev Wrapped in `try/catch` so the rule's read path stays revert-free if the token breaks after
+     222                 :            :      * configuration -- a proxy upgraded to something that reverts, or a pausable implementation that
+     223                 :            :      * reverts while paused.
+     224                 :            :      * @param account The address to query.
+     225                 :            :      * @return available True when the balance could be read.
+     226                 :            :      * @return balance The balance; meaningless when `available` is false.
+     227                 :            :      */
+     228                 :         44 :     function _balanceOf(address account) internal view virtual returns (bool available, uint256 balance) {
+     229            [ + ]:         44 :         try balanceToken.balanceOf(account) returns (uint256 balance_) {
+     230                 :         41 :             return (true, balance_);
+     231            [ + ]:          3 :         } catch {
+     232                 :          3 :             return (false, 0);
+     233                 :            :         }
+     234                 :            :     }
+     235                 :            : 
+     236                 :            :     /**
+     237                 :            :      * @notice Reports whether `to` receiving `value` would breach its cap, without ever reverting.
+     238                 :            :      * @dev Answers in booleans rather than restriction codes, so the caller owns the ERC-1404
+     239                 :            :      * mapping. Burns and exempt receivers are resolved before any balance is read, so they keep
+     240                 :            :      * working while the token is unreadable. Overflow-safe: `balance + value` could exceed uint256 on
+     241                 :            :      * a MUST-NOT-revert path, so the comparison uses the remaining headroom instead.
+     242                 :            :      * @param to The receiver whose resulting balance is checked.
+     243                 :            :      * @param value The amount that would be received.
+     244                 :            :      * @return balanceAvailable False when the balance could not be read; `exceeded` is then
+     245                 :            :      * meaningless and the caller should treat the check as failed.
+     246                 :            :      * @return exceeded True when the transfer would push `to` past {maxBalance}.
+     247                 :            :      */
+     248                 :         46 :     function _capExceeded(address to, uint256 value)
+     249                 :            :         internal
+     250                 :            :         view
+     251                 :            :         virtual
+     252                 :            :         returns (bool balanceAvailable, bool exceeded)
+     253                 :            :     {
+     254                 :            :         // Burns cannot breach a maximum, and address(0) is the sentinel rather than a holder.
+     255                 :            :         // Exempt receivers may hold any amount. Neither reads a balance.
+     256            [ + ]:         46 :         if (to == address(0) || _isAddressListed(to)) {
+     257                 :          6 :             return (true, false);
+     258                 :            :         }
+     259                 :         40 :         uint256 balance;
+     260                 :         40 :         (balanceAvailable, balance) = _balanceOf(to);
+     261            [ + ]:         40 :         if (!balanceAvailable) {
+     262                 :          2 :             return (false, false);
+     263                 :            :         }
+     264                 :         38 :         return (true, _capExceededBy(balance, maxBalance, value));
+     265                 :            :     }
+     266                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.func-sort-c.html new file mode 100644 index 00000000..261bcb33 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.func-sort-c.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/CapAccounting.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - CapAccounting.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:66100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:11100.0 %
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
CapAccounting._capHeadroom3
CapAccounting._capExceededBy1356
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.func.html new file mode 100644 index 00000000..56cafbfa --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.func.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/CapAccounting.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - CapAccounting.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:66100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:11100.0 %
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
CapAccounting._capExceededBy1356
CapAccounting._capHeadroom3
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.gcov.html new file mode 100644 index 00000000..aae7afff --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/CapAccounting.sol.gcov.html @@ -0,0 +1,133 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/CapAccounting.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - CapAccounting.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:66100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:11100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : /**
+       5                 :            :  * @title CapAccounting
+       6                 :            :  * @notice The one question every cap rule ends in: would adding `value` leave an observed figure
+       7                 :            :  * above its cap? Owns that arithmetic; knows nothing about where either number came from.
+       8                 :            :  *
+       9                 :            :  * @dev Declares **no storage** and no constructor, so adding it to a rule's inheritance chain cannot
+      10                 :            :  * move a slot, and an upgradeable variant may adopt it freely.
+      11                 :            :  *
+      12                 :            :  * @dev Deliberately carries **no notion of pre- or post-update accounting**. Whether the observation
+      13                 :            :  * already includes the value being moved depends on WHICH PATH is running, not on the rule: a
+      14                 :            :  * pre-flight view always runs before the movement, while the write hook runs after it on a token that
+      15                 :            :  * notifies afterwards. A single flag here would answer for both and silently make the pre-flight view
+      16                 :            :  * disagree with enforcement. That distinction belongs one level up, in each rule's
+      17                 :            :  * `_detectTransferRestrictionOnNotify` hook.
+      18                 :            :  */
+      19                 :            : abstract contract CapAccounting {
+      20                 :            :     /**
+      21                 :            :      * @notice Whether adding `value` to `observed` would pass `cap`.
+      22                 :            :      * @dev Never reverts and never overflows: the projected total is never formed, the comparison is
+      23                 :            :      * against the remaining headroom instead. Both matter because every caller sits on a
+      24                 :            :      * MUST-NOT-revert ERC-1404 read path. Pass `value = 0` to ask only whether `observed` is already
+      25                 :            :      * over the cap -- which is exactly the question a post-update notification needs to answer.
+      26                 :            :      * @param observed The figure read for this check: a holder's balance, or a total supply.
+      27                 :            :      * @param cap The ceiling `observed` may not pass.
+      28                 :            :      * @param value The amount being added, or `0` when it is already counted in `observed`.
+      29                 :            :      * @return True when the result would breach the cap.
+      30                 :            :      */
+      31                 :       1356 :     function _capExceededBy(uint256 observed, uint256 cap, uint256 value) internal pure virtual returns (bool) {
+      32                 :            :         // Already over the line whatever is added. Also guarantees the subtraction below.
+      33            [ + ]:       1356 :         if (observed > cap) {
+      34                 :        448 :             return true;
+      35                 :            :         }
+      36                 :        908 :         return value > cap - observed;
+      37                 :            :     }
+      38                 :            : 
+      39                 :            :     /**
+      40                 :            :      * @notice How much may still be added before `observed` reaches `cap`.
+      41                 :            :      * @param observed The figure read for this check.
+      42                 :            :      * @param cap The ceiling.
+      43                 :            :      * @return The remaining headroom; `0` when already at or over the cap.
+      44                 :            :      */
+      45                 :          3 :     function _capHeadroom(uint256 observed, uint256 cap) internal pure virtual returns (uint256) {
+      46                 :          3 :         return observed >= cap ? 0 : cap - observed;
+      47                 :            :     }
+      48                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.func-sort-c.html new file mode 100644 index 00000000..d346ac00 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.func-sort-c.html @@ -0,0 +1,133 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - ChainlinkPoRFeedManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:686998.6 %
Date:2026-08-19 15:38:25Functions:121392.3 %
Branches:2929100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
ChainlinkPoRFeedManager._authorizeChainlinkPoRManager0
ChainlinkPoRFeedManager.feedDecimals4
ChainlinkPoRFeedManager.onlyChainlinkPoRManager8
ChainlinkPoRFeedManager.setMaxStalenessSeconds8
ChainlinkPoRFeedManager.setReservesFeed8
ChainlinkPoRFeedManager.setTokenMetadata12
ChainlinkPoRFeedManager._supplyToken470
ChainlinkPoRFeedManager._setMaxStalenessSeconds625
ChainlinkPoRFeedManager._setReservesFeed630
ChainlinkPoRFeedManager._setTokenMetadata632
ChainlinkPoRFeedManager.maxBackedSupply659
ChainlinkPoRFeedManager._scaleReserve1125
ChainlinkPoRFeedManager._maxBackedSupply1279
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.func.html new file mode 100644 index 00000000..8d25769a --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.func.html @@ -0,0 +1,133 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - ChainlinkPoRFeedManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:686998.6 %
Date:2026-08-19 15:38:25Functions:121392.3 %
Branches:2929100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
ChainlinkPoRFeedManager._authorizeChainlinkPoRManager0
ChainlinkPoRFeedManager._maxBackedSupply1279
ChainlinkPoRFeedManager._scaleReserve1125
ChainlinkPoRFeedManager._setMaxStalenessSeconds625
ChainlinkPoRFeedManager._setReservesFeed630
ChainlinkPoRFeedManager._setTokenMetadata632
ChainlinkPoRFeedManager._supplyToken470
ChainlinkPoRFeedManager.feedDecimals4
ChainlinkPoRFeedManager.maxBackedSupply659
ChainlinkPoRFeedManager.onlyChainlinkPoRManager8
ChainlinkPoRFeedManager.setMaxStalenessSeconds8
ChainlinkPoRFeedManager.setReservesFeed8
ChainlinkPoRFeedManager.setTokenMetadata12
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.gcov.html new file mode 100644 index 00000000..8641b143 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol.gcov.html @@ -0,0 +1,350 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - ChainlinkPoRFeedManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:686998.6 %
Date:2026-08-19 15:38:25Functions:121392.3 %
Branches:2929100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleChainlinkPoRInvariantStorage} from "../invariant/RuleChainlinkPoRInvariantStorage.sol";
+       5                 :            : import {IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol";
+       6                 :            : import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol";
+       7                 :            : import {IDecimals} from "../../../interfaces/IDecimals.sol";
+       8                 :            : import {ITotalSupply} from "../../../interfaces/ITotalSupply.sol";
+       9                 :            : import {TokenSupplyReader} from "./TokenSupplyReader.sol";
+      10                 :            : import {CapAccounting} from "./CapAccounting.sol";
+      11                 :            : 
+      12                 :            : /**
+      13                 :            :  * @title ChainlinkPoRFeedManager
+      14                 :            :  * @notice Configuration and reading of a Chainlink Proof of Reserve feed: which feed, which token it
+      15                 :            :  * backs, how stale an answer may be, and how to scale it into token units.
+      16                 :            :  *
+      17                 :            :  * @dev Declares **no constructor** and does not depend on **ERC-1404**, so the inheriting rule
+      18                 :            :  * decides when configuration happens (constructor or initializer) and owns the code-to-message
+      19                 :            :  * mapping; {_maxBackedSupply} returns a plain `uint8` describing why an answer is unusable.
+      20                 :            :  *
+      21                 :            :  * @dev The feed's `decimals()` is read **live on every check, never cached**. Caching saves one call
+      22                 :            :  * but lets a feed that changes decimals mis-scale reserves by `10 ** delta` with no on-chain signal
+      23                 :            :  * -- in the overstating direction that silently authorises unbacked minting.
+      24                 :            :  *
+      25                 :            :  * @dev {_maxBackedSupply} must never revert, because the rule calls it from MUST-NOT-revert views,
+      26                 :            :  * so every feed interaction is `try/catch`-wrapped. That is only safe because the setters require
+      27                 :            :  * both the feed and the token to have code and EIP-6780 makes it permanent: a `try` to a codeless
+      28                 :            :  * address reverts *uncatchably*. Assumes a Cancun-or-later chain.
+      29                 :            :  */
+      30                 :            : abstract contract ChainlinkPoRFeedManager is CapAccounting, TokenSupplyReader, RuleChainlinkPoRInvariantStorage {
+      31                 :            :     /**
+      32                 :            :      * @notice The Proof of Reserve data feed consulted before every mint.
+      33                 :            :      */
+      34                 :            :     AggregatorV3Interface public reservesFeed;
+      35                 :            :     /**
+      36                 :            :      * @dev tokenContract is trusted to return a correct totalSupply.
+      37                 :            :      */
+      38                 :            :     ITotalSupply public tokenContract;
+      39                 :            :     /**
+      40                 :            :      * @notice Decimals of the protected token, used to scale the reserve answer.
+      41                 :            :      */
+      42                 :            :     uint8 public tokenDecimals;
+      43                 :            :     /**
+      44                 :            :      * @notice Maximum accepted age of the reserve data, in seconds; 0 disables the staleness check.
+      45                 :            :      */
+      46                 :            :     uint256 public maxStalenessSeconds;
+      47                 :            : 
+      48                 :            :     /*//////////////////////////////////////////////////////////////
+      49                 :            :                             ACCESS CONTROL
+      50                 :            :     //////////////////////////////////////////////////////////////*/
+      51                 :            : 
+      52                 :          8 :     modifier onlyChainlinkPoRManager() {
+      53                 :          8 :         _authorizeChainlinkPoRManager();
+      54                 :            :         _;
+      55                 :            :     }
+      56                 :            : 
+      57                 :            :     /*//////////////////////////////////////////////////////////////
+      58                 :            :                         PUBLIC FUNCTIONS
+      59                 :            :     //////////////////////////////////////////////////////////////*/
+      60                 :            : 
+      61                 :            :     /**
+      62                 :            :      * @notice Sets the Proof of Reserve data feed and caches its decimals.
+      63                 :            :      * @dev The feed must be a contract whose `decimals()` call succeeds and reports at most
+      64                 :            :      * {MAX_FEED_DECIMALS}; both are validated here so the read path stays revert-free.
+      65                 :            :      * @param newReservesFeed The new data feed.
+      66                 :            :      */
+      67                 :          8 :     function setReservesFeed(AggregatorV3Interface newReservesFeed) public virtual onlyChainlinkPoRManager {
+      68                 :          6 :         _setReservesFeed(newReservesFeed);
+      69                 :            :     }
+      70                 :            : 
+      71                 :            :     /**
+      72                 :            :      * @notice Sets the protected token and the decimals used to scale the reserve answer.
+      73                 :            :      * @param newTokenContract The new token contract; must not be the zero address.
+      74                 :            :      * @param newTokenDecimals The token decimals; must be at most {MAX_TOKEN_DECIMALS} and, when the
+      75                 :            :      * token exposes `decimals()`, must match it. `0` is valid and common for CMTAT equity tokens.
+      76                 :            :      */
+      77                 :         12 :     function setTokenMetadata(address newTokenContract, uint8 newTokenDecimals) public virtual onlyChainlinkPoRManager {
+      78                 :         10 :         _setTokenMetadata(newTokenContract, newTokenDecimals);
+      79                 :            :     }
+      80                 :            : 
+      81                 :            :     /**
+      82                 :            :      * @notice Sets the maximum accepted age of the reserve data.
+      83                 :            :      * @param newMaxStalenessSeconds The new threshold in seconds; 0 disables the staleness check.
+      84                 :            :      */
+      85                 :          8 :     function setMaxStalenessSeconds(uint256 newMaxStalenessSeconds) public virtual onlyChainlinkPoRManager {
+      86                 :          5 :         _setMaxStalenessSeconds(newMaxStalenessSeconds);
+      87                 :            :     }
+      88                 :            : 
+      89                 :            :     /**
+      90                 :            :      * @notice Returns the decimals currently reported by {reservesFeed}.
+      91                 :            :      * @dev Read live from the feed rather than from storage, so it always agrees with what the
+      92                 :            :      * restriction checks use. Unlike the ERC-1404 views this getter is allowed to revert: it
+      93                 :            :      * forwards whatever the feed does, which is the honest answer for a diagnostic accessor.
+      94                 :            :      * @return The feed's current decimals.
+      95                 :            :      */
+      96                 :          4 :     function feedDecimals() public view virtual returns (uint8) {
+      97                 :          4 :         return reservesFeed.decimals();
+      98                 :            :     }
+      99                 :            : 
+     100                 :            :     /**
+     101                 :            :      * @notice Returns the supply currently backed by the reserves, i.e. the maximum total supply a
+     102                 :            :      * mint may reach. This is the reported reserves scaled into token units, with no margin applied.
+     103                 :            :      * @dev Mirrors what the rule's restriction check computes, so integrators can preview the limit
+     104                 :            :      * without simulating a mint. Never reverts.
+     105                 :            :      * @return restrictionCode `0` when the feed answer is usable, otherwise the restriction code
+     106                 :            :      * that a mint would return.
+     107                 :            :      * @return backedSupply The backed supply expressed in token units; meaningless when
+     108                 :            :      * `restrictionCode` is non-zero.
+     109                 :            :      */
+     110                 :        659 :     function maxBackedSupply() public view virtual returns (uint8 restrictionCode, uint256 backedSupply) {
+     111                 :        659 :         return _maxBackedSupply();
+     112                 :            :     }
+     113                 :            : 
+     114                 :            :     /*//////////////////////////////////////////////////////////////
+     115                 :            :                         INTERNAL FUNCTIONS
+     116                 :            :     //////////////////////////////////////////////////////////////*/
+     117                 :            : 
+     118                 :            :     /**
+     119                 :            :      * @notice Stores the data feed and emits {ReservesFeedUpdated}.
+     120                 :            :      * @dev The feed's `decimals()` is validated here so a misconfigured feed is rejected up front
+     121                 :            :      * rather than silently blocking every mint later, but the value is deliberately NOT cached --
+     122                 :            :      * {_maxBackedSupply} re-reads it on every check. The emitted decimals are informational: they
+     123                 :            :      * record what the feed reported at configuration time.
+     124                 :            :      * @param newReservesFeed The new data feed.
+     125                 :            :      */
+     126                 :        630 :     function _setReservesFeed(AggregatorV3Interface newReservesFeed) internal virtual {
+     127                 :        630 :         address feed = address(newReservesFeed);
+     128         [ +  + ]:        630 :         require(feed != address(0), RuleChainlinkPoR_FeedAddressZeroNotAllowed());
+     129         [ +  + ]:        629 :         require(feed.code.length != 0, RuleChainlinkPoR_FeedIsNotAContract(feed));
+     130                 :        628 :         uint8 newFeedDecimals;
+     131            [ + ]:        628 :         try newReservesFeed.decimals() returns (uint8 decimals_) {
+     132                 :        627 :             newFeedDecimals = decimals_;
+     133            [ + ]:          1 :         } catch {
+     134                 :          1 :             revert RuleChainlinkPoR_FeedDecimalsUnavailable(feed);
+     135                 :            :         }
+     136         [ +  + ]:        627 :         require(newFeedDecimals <= MAX_FEED_DECIMALS, RuleChainlinkPoR_FeedDecimalsTooLarge(newFeedDecimals));
+     137                 :        626 :         reservesFeed = newReservesFeed;
+     138                 :        626 :         emit ReservesFeedUpdated(feed, newFeedDecimals);
+     139                 :            :     }
+     140                 :            : 
+     141                 :            :     /**
+     142                 :            :      * @notice Stores the protected token and its decimals and emits {TokenMetadataUpdated}.
+     143                 :            :      * @dev When the token exposes `decimals()`, the provided value must match it; otherwise the
+     144                 :            :      * provided value is used as-is. WARNING: an incorrect value for a token that does not expose
+     145                 :            :      * `decimals()` skews the reserve comparison in either direction.
+     146                 :            :      * @param newTokenContract The new token contract.
+     147                 :            :      * @param newTokenDecimals The token decimals.
+     148                 :            :      */
+     149                 :        632 :     function _setTokenMetadata(address newTokenContract, uint8 newTokenDecimals) internal virtual {
+     150         [ +  + ]:        632 :         require(newTokenContract != address(0), RuleChainlinkPoR_TokenAddressZeroNotAllowed());
+     151                 :            :         // Explicit, rather than relying on the uncatchable extcodesize revert that the `decimals()`
+     152                 :            :         // probe below happens to produce for a codeless address: that is compiler behaviour, not a
+     153                 :            :         // check, and it would vanish if the probe were ever rewritten as a low-level staticcall.
+     154         [ +  + ]:        630 :         require(newTokenContract.code.length != 0, RuleChainlinkPoR_TokenIsNotAContract(newTokenContract));
+     155         [ +  + ]:        628 :         require(newTokenDecimals <= MAX_TOKEN_DECIMALS, RuleChainlinkPoR_InvalidTokenDecimals(newTokenDecimals));
+     156            [ + ]:        627 :         try IDecimals(newTokenContract).decimals() returns (uint8 onChainDecimals) {
+     157         [ +  + ]:        611 :             require(
+     158                 :            :                 onChainDecimals == newTokenDecimals,
+     159                 :            :                 RuleChainlinkPoR_TokenDecimalsMismatch(newTokenDecimals, onChainDecimals)
+     160                 :            :             );
+     161                 :            :         } catch {
+     162                 :            :             // The token does not expose `decimals()`; the provided value is used as-is.
+     163                 :            :         }
+     164                 :            :         // `totalSupply()` is mandatory, unlike `decimals()`: the restriction check cannot work
+     165                 :            :         // without it. Probing here turns a silent read-path failure into a configuration error.
+     166         [ +  + ]:        626 :         require(
+     167                 :            :             _probeTotalSupplyCallable(newTokenContract), RuleChainlinkPoR_TokenTotalSupplyUnavailable(newTokenContract)
+     168                 :            :         );
+     169                 :        625 :         tokenContract = ITotalSupply(newTokenContract);
+     170                 :        625 :         tokenDecimals = newTokenDecimals;
+     171                 :        625 :         emit TokenMetadataUpdated(newTokenContract, newTokenDecimals);
+     172                 :            :     }
+     173                 :            : 
+     174                 :            :     /**
+     175                 :            :      * @notice Stores the staleness threshold and emits {MaxStalenessSecondsUpdated}.
+     176                 :            :      * @param newMaxStalenessSeconds The new threshold in seconds; 0 disables the check.
+     177                 :            :      */
+     178                 :        625 :     function _setMaxStalenessSeconds(uint256 newMaxStalenessSeconds) internal virtual {
+     179                 :        625 :         maxStalenessSeconds = newMaxStalenessSeconds;
+     180                 :        625 :         emit MaxStalenessSecondsUpdated(newMaxStalenessSeconds);
+     181                 :            :     }
+     182                 :            : 
+     183                 :            :     /**
+     184                 :            :      * @notice Authorization hook invoked before any configuration change.
+     185                 :            :      * @dev Implemented by concrete subclasses with the desired access-control policy.
+     186                 :            :      */
+     187                 :          0 :     function _authorizeChainlinkPoRManager() internal view virtual;
+     188                 :            : 
+     189                 :            :     /**
+     190                 :            :      * @notice Reads the feed and derives the supply currently backed by the reserves.
+     191                 :            :      * @dev Never reverts: the feed address is code-checked and the call is wrapped in `try/catch`.
+     192                 :            :      * @return restrictionCode `0` when the answer is usable, otherwise the reason it is not.
+     193                 :            :      * @return backedSupply The backed supply in token units; `0` when `restrictionCode` is non-zero.
+     194                 :            :      */
+     195                 :       1279 :     function _maxBackedSupply() internal view virtual returns (uint8 restrictionCode, uint256 backedSupply) {
+     196                 :       1279 :         AggregatorV3Interface feed = reservesFeed;
+     197                 :            :         // Read live, never cached: see the contract-level note on why the extra call is worth it.
+     198                 :            :         // No code-length guard: `_setReservesFeed` requires code and EIP-6780 makes that permanent.
+     199                 :       1279 :         uint8 currentFeedDecimals;
+     200            [ + ]:       1279 :         try feed.decimals() returns (uint8 decimals_) {
+     201                 :       1277 :             currentFeedDecimals = decimals_;
+     202            [ + ]:          2 :         } catch {
+     203                 :          2 :             return (CODE_RESERVES_FEED_UNAVAILABLE, 0);
+     204                 :            :         }
+     205                 :            :         // Re-checked at read time, not just at configuration: a feed that raised its decimals past
+     206                 :            :         // the bound would otherwise overflow the scaling exponent and revert this view.
+     207            [ + ]:       1277 :         if (currentFeedDecimals > MAX_FEED_DECIMALS) {
+     208                 :          3 :             return (CODE_RESERVES_FEED_UNAVAILABLE, 0);
+     209                 :            :         }
+     210            [ + ]:       1274 :         try feed.latestRoundData() returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80) {
+     211                 :            :             // Three malformed answers, not stale ones: a negative reserve is meaningless, `updatedAt == 0`
+     212                 :            :             // marks a round that never completed, and a round stamped in the FUTURE cannot have been written
+     213                 :            :             // by an aggregator on this chain. Rejecting the future stamp here rather than as a staleness case
+     214                 :            :             // is deliberate -- `maxStalenessSeconds == 0` disables freshness checking, and a forged timestamp
+     215                 :            :             // must not become acceptable because an operator chose not to police staleness.
+     216            [ + ]:       1271 :             if (answer < 0 || updatedAt == 0 || updatedAt > block.timestamp) {
+     217                 :        143 :                 return (CODE_RESERVES_ANSWER_INVALID, 0);
+     218                 :            :             }
+     219                 :       1128 :             uint256 staleness = maxStalenessSeconds;
+     220                 :            :             // `updatedAt <= block.timestamp` is guaranteed above, so the subtraction cannot underflow.
+     221            [ + ]:       1128 :             if (staleness != 0 && block.timestamp - updatedAt > staleness) {
+     222                 :          3 :                 return (CODE_RESERVES_FEED_STALE, 0);
+     223                 :            :             }
+     224                 :            :             // `answer >= 0` was just checked, so the cast to uint256 preserves the value.
+     225                 :            :             // forge-lint: disable-next-line(unsafe-typecast)
+     226                 :       1125 :             uint256 backed = _scaleReserve(uint256(answer), currentFeedDecimals);
+     227                 :       1125 :             return (uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), backed);
+     228            [ + ]:          3 :         } catch {
+     229                 :          3 :             return (CODE_RESERVES_FEED_UNAVAILABLE, 0);
+     230                 :            :         }
+     231                 :            :     }
+     232                 :            : 
+     233                 :            :     /**
+     234                 :            :      * @inheritdoc TokenSupplyReader
+     235                 :            :      */
+     236                 :        470 :     function _supplyToken() internal view virtual override returns (ITotalSupply) {
+     237                 :        470 :         return tokenContract;
+     238                 :            :     }
+     239                 :            : 
+     240                 :            :     /**
+     241                 :            :      * @notice Converts a reserve answer from the feed's decimals to the token's decimals.
+     242                 :            :      * @dev Saturates at `type(uint256).max` instead of overflowing: this function is on a
+     243                 :            :      * MUST-NOT-revert read path, and a reserve that large backs any representable supply anyway.
+     244                 :            :      * Scaling down truncates, which rounds the backed supply in the conservative direction.
+     245                 :            :      * @param answer The raw feed answer, expressed with `from` decimals.
+     246                 :            :      * @param from The feed's decimals, as read live for this check.
+     247                 :            :      * @return The reserve expressed with {tokenDecimals} decimals.
+     248                 :            :      */
+     249                 :       1125 :     function _scaleReserve(uint256 answer, uint8 from) internal view virtual returns (uint256) {
+     250                 :       1125 :         uint8 to = tokenDecimals;
+     251            [ + ]:       1125 :         if (to == from) {
+     252                 :         76 :             return answer;
+     253                 :            :         }
+     254            [ + ]:       1049 :         if (to > from) {
+     255                 :            :             // to <= MAX_TOKEN_DECIMALS, so the factor is at most 10 ** 18.
+     256                 :        622 :             uint256 factor = 10 ** uint256(to - from);
+     257            [ + ]:        622 :             if (answer > type(uint256).max / factor) {
+     258                 :         26 :                 return type(uint256).max;
+     259                 :            :             }
+     260                 :        596 :             return answer * factor;
+     261                 :            :         }
+     262                 :            :         // `from` was bounded by MAX_FEED_DECIMALS above, so the divisor cannot overflow.
+     263                 :        427 :         return answer / (10 ** uint256(from - to));
+     264                 :            :     }
+     265                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func-sort-c.html index 4b427ac6..56a439b9 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleNFTAdapter.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleNFTAdapter.sol - functions @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 20 - 22 - 90.9 % + 24 + 26 + 92.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 8 - 10 - 80.0 % + 9 + 11 + 81.8 % Branches: - 4 - 4 + 6 + 6 100.0 % @@ -69,44 +69,48 @@ Hit count Sort by hit count - RuleNFTAdapter._transferred + RuleNFTAdapter._transferred 0 - RuleNFTAdapter._transferredFrom + RuleNFTAdapter._transferredFrom 0 - RuleNFTAdapter.transferred.3 - 23 + RuleNFTAdapter.transferred.2 + 28 - RuleNFTAdapter.canTransferFrom - 25 + RuleNFTAdapter.canTransfer + 29 - RuleNFTAdapter.detectTransferRestrictionFrom - 27 + RuleNFTAdapter.detectTransferRestriction + 32 - RuleNFTAdapter.transferred.2 - 28 + RuleNFTAdapter.canTransferFrom + 38 - RuleNFTAdapter.canTransfer - 29 + RuleNFTAdapter.transferred.3 + 38 + + + RuleNFTAdapter.detectTransferRestrictionFrom + 42 - RuleNFTAdapter.detectTransferRestriction - 31 + RuleNFTAdapter.transferred.0 + 47 - RuleNFTAdapter.transferred.0 - 34 + RuleNFTAdapter.transferred.1 + 50 - RuleNFTAdapter.transferred.1 - 36 + RuleNFTAdapter._isDelegated + 215
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func.html index ed01ccc5..d4c47697 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleNFTAdapter.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleNFTAdapter.sol - functions @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 20 - 22 - 90.9 % + 24 + 26 + 92.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 8 - 10 - 80.0 % + 9 + 11 + 81.8 % Branches: - 4 - 4 + 6 + 6 100.0 % @@ -69,44 +69,48 @@ Hit count Sort by hit count - RuleNFTAdapter._transferred + RuleNFTAdapter._isDelegated + 215 + + + RuleNFTAdapter._transferred 0 - RuleNFTAdapter._transferredFrom + RuleNFTAdapter._transferredFrom 0 - RuleNFTAdapter.canTransfer + RuleNFTAdapter.canTransfer 29 - RuleNFTAdapter.canTransferFrom - 25 + RuleNFTAdapter.canTransferFrom + 38 - RuleNFTAdapter.detectTransferRestriction - 31 + RuleNFTAdapter.detectTransferRestriction + 32 - RuleNFTAdapter.detectTransferRestrictionFrom - 27 + RuleNFTAdapter.detectTransferRestrictionFrom + 42 - RuleNFTAdapter.transferred.0 - 34 + RuleNFTAdapter.transferred.0 + 47 - RuleNFTAdapter.transferred.1 - 36 + RuleNFTAdapter.transferred.1 + 50 - RuleNFTAdapter.transferred.2 + RuleNFTAdapter.transferred.2 28 - RuleNFTAdapter.transferred.3 - 23 + RuleNFTAdapter.transferred.3 + 38
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.gcov.html index adc64bdb..ca31941b 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleNFTAdapter.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleNFTAdapter.sol + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleNFTAdapter.sol @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 20 - 22 - 90.9 % + 24 + 26 + 92.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 8 - 10 - 80.0 % + 9 + 11 + 81.8 % Branches: - 4 - 4 + 6 + 6 100.0 % @@ -86,191 +86,223 @@ 15 : : * @title Rule NFT Adapter 16 : : * @notice Provides ERC-7943 overloads for rules that already implement core transfer checks. 17 : : * @dev Delegates tokenId overloads to RuleTransferValidation's internal hooks. - 18 : : */ - 19 : : abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleComplianceExtend, ITransferContext { - 20 : : /** - 21 : : * @notice Selector of the ERC-3643 compliance `transferred` hook. - 22 : : */ - 23 : : bytes4 internal constant TRANSFERRED_SELECTOR_ERC3643 = IERC3643IComplianceContract.transferred.selector; - 24 : : /** - 25 : : * @notice Selector of the RuleEngine `transferred` hook. - 26 : : */ - 27 : : bytes4 internal constant TRANSFERRED_SELECTOR_RULE_ENGINE = IRuleEngine.transferred.selector; - 28 : : /** - 29 : : * @notice Selector of the ERC-7943 `transferred(from,to,tokenId,value)` hook. - 30 : : */ - 31 : : bytes4 internal constant TRANSFERRED_SELECTOR_ERC7943 = - 32 : : bytes4(keccak256("transferred(address,address,uint256,uint256)")); - 33 : : /** - 34 : : * @notice Selector of the ERC-7943 `transferred(spender,from,to,tokenId,value)` hook. - 35 : : */ - 36 : : bytes4 internal constant TRANSFERRED_SELECTOR_ERC7943_FROM = - 37 : : bytes4(keccak256("transferred(address,address,address,uint256,uint256)")); - 38 : : - 39 : : /*////////////////////////////////////////////////////////////// - 40 : : EXTERNAL FUNCTIONS - 41 : : //////////////////////////////////////////////////////////////*/ - 42 : : + 18 : : * + 19 : : * @dev **The interfaces here signal "direct transfer" differently, and {_isDelegated} is where that is + 20 : : * reconciled.** ERC-7943 documents its `spender` as "the address performing the transfer + 21 : : * (owner/operator)" and {ITransferContext} documents `sender` as the token's `msg.sender`, so on BOTH + 22 : : * an owner moving their own tokens arrives as `spender == from`. The CMTAT 3-arg/4-arg pair instead + 23 : : * signals it with `spender == address(0)` and the 3-arg overload. Every entrypoint on this adapter + 24 : : * therefore normalises `spender == from` to the direct hook; the 4-arg CMTAT path deliberately does + 25 : : * NOT, because its own convention already distinguishes the two. Do not "align" them: an owner- + 26 : : * initiated ERC-721 `transferFrom` would then be screened as a delegated transfer, which + 27 : : * {RuleSpenderWhitelistBase} documents as always allowed. + 28 : : */ + 29 : : abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleComplianceExtend, ITransferContext { + 30 : : /** + 31 : : * @notice Selector of the ERC-3643 compliance `transferred` hook. + 32 : : */ + 33 : : bytes4 internal constant TRANSFERRED_SELECTOR_ERC3643 = IERC3643IComplianceContract.transferred.selector; + 34 : : /** + 35 : : * @notice Selector of the RuleEngine `transferred` hook. + 36 : : */ + 37 : : bytes4 internal constant TRANSFERRED_SELECTOR_RULE_ENGINE = IRuleEngine.transferred.selector; + 38 : : /** + 39 : : * @notice Selector of the ERC-7943 `transferred(from,to,tokenId,value)` hook. + 40 : : */ + 41 : : bytes4 internal constant TRANSFERRED_SELECTOR_ERC7943 = + 42 : : bytes4(keccak256("transferred(address,address,uint256,uint256)")); 43 : : /** - 44 : : * @inheritdoc ITransferContext + 44 : : * @notice Selector of the ERC-7943 `transferred(spender,from,to,tokenId,value)` hook. 45 : : */ - 46 : 34 : function transferred(MultiTokenTransferContext calldata ctx) external virtual override { - 47 [ + + ]: 34 : if (ctx.sender != address(0) && ctx.sender != ctx.from) { - 48 : 17 : _transferredFrom(ctx.sender, ctx.from, ctx.to, ctx.value); - 49 : : } else { - 50 : 17 : _transferred(ctx.from, ctx.to, ctx.value); - 51 : : } - 52 : : } - 53 : : - 54 : : /** - 55 : : * @inheritdoc ITransferContext - 56 : : */ - 57 : 36 : function transferred(FungibleTransferContext calldata ctx) external virtual override { - 58 [ + + ]: 36 : if (ctx.sender != address(0) && ctx.sender != ctx.from) { - 59 : 17 : _transferredFrom(ctx.sender, ctx.from, ctx.to, ctx.value); - 60 : : } else { - 61 : 19 : _transferred(ctx.from, ctx.to, ctx.value); - 62 : : } - 63 : : } - 64 : : - 65 : : /*////////////////////////////////////////////////////////////// - 66 : : PUBLIC FUNCTIONS - 67 : : //////////////////////////////////////////////////////////////*/ - 68 : : - 69 : : /** - 70 : : * @inheritdoc IERC7943NonFungibleComplianceExtend - 71 : : */ - 72 : 28 : function transferred( - 73 : : address from, - 74 : : address to, - 75 : : uint256, - 76 : : /* tokenId */ - 77 : : uint256 value - 78 : : ) - 79 : : public - 80 : : virtual - 81 : : override(IERC7943NonFungibleComplianceExtend) - 82 : : { - 83 : 28 : _transferred(from, to, value); - 84 : : } - 85 : : - 86 : : /** - 87 : : * @inheritdoc IERC7943NonFungibleComplianceExtend - 88 : : */ - 89 : 23 : function transferred( - 90 : : address spender, - 91 : : address from, - 92 : : address to, - 93 : : uint256, - 94 : : /* tokenId */ - 95 : : uint256 value - 96 : : ) - 97 : : public - 98 : : virtual - 99 : : override(IERC7943NonFungibleComplianceExtend) - 100 : : { - 101 : 23 : _transferredFrom(spender, from, to, value); - 102 : : } - 103 : : - 104 : : /** - 105 : : * @inheritdoc IERC7943NonFungibleComplianceExtend - 106 : : */ - 107 : 31 : function detectTransferRestriction( - 108 : : address from, - 109 : : address to, - 110 : : uint256, - 111 : : /* tokenId */ - 112 : : uint256 value - 113 : : ) - 114 : : public - 115 : : view - 116 : : virtual - 117 : : override(IERC7943NonFungibleComplianceExtend) - 118 : : returns (uint8) - 119 : : { - 120 : 31 : return _detectTransferRestriction(from, to, value); - 121 : : } - 122 : : - 123 : : /** - 124 : : * @inheritdoc IERC7943NonFungibleComplianceExtend - 125 : : */ - 126 : 27 : function detectTransferRestrictionFrom( - 127 : : address spender, - 128 : : address from, - 129 : : address to, - 130 : : uint256, - 131 : : /* tokenId */ - 132 : : uint256 value - 133 : : ) - 134 : : public - 135 : : view - 136 : : virtual - 137 : : override(IERC7943NonFungibleComplianceExtend) - 138 : : returns (uint8) - 139 : : { - 140 : 27 : return _detectTransferRestrictionFrom(spender, from, to, value); - 141 : : } - 142 : : - 143 : : /** - 144 : : * @inheritdoc IERC7943NonFungibleCompliance - 145 : : */ - 146 : 29 : function canTransfer( - 147 : : address from, - 148 : : address to, - 149 : : uint256, - 150 : : /* tokenId */ - 151 : : uint256 amount - 152 : : ) - 153 : : public - 154 : : view - 155 : : override(IERC7943NonFungibleCompliance) - 156 : : returns (bool) - 157 : : { - 158 : 29 : return _detectTransferRestriction(from, to, amount) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 159 : : } - 160 : : - 161 : : /** - 162 : : * @inheritdoc IERC7943NonFungibleComplianceExtend - 163 : : */ - 164 : 25 : function canTransferFrom( - 165 : : address spender, - 166 : : address from, - 167 : : address to, - 168 : : uint256, - 169 : : /* tokenId */ - 170 : : uint256 value - 171 : : ) - 172 : : public - 173 : : view - 174 : : virtual - 175 : : override(IERC7943NonFungibleComplianceExtend) - 176 : : returns (bool) - 177 : : { - 178 : 25 : return _detectTransferRestrictionFrom(spender, from, to, value) - 179 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 180 : : } - 181 : : - 182 : : /*////////////////////////////////////////////////////////////// - 183 : : INTERNAL FUNCTIONS - 184 : : //////////////////////////////////////////////////////////////*/ - 185 : : - 186 : : /** - 187 : : * @notice Internal hook for post-transfer validation or state updates. - 188 : : * @param from Address tokens are transferred from. - 189 : : * @param to Address tokens are transferred to. - 190 : : * @param value Amount transferred. - 191 : : */ - 192 : 0 : function _transferred(address from, address to, uint256 value) internal virtual; - 193 : : - 194 : : /** - 195 : : * @notice Internal hook for post-transfer validation or state updates (spender-aware). - 196 : : * @param spender Address executing the transfer on behalf of `from`. - 197 : : * @param from Address tokens are transferred from. - 198 : : * @param to Address tokens are transferred to. - 199 : : * @param value Amount transferred. - 200 : : */ - 201 : 0 : function _transferredFrom(address spender, address from, address to, uint256 value) internal virtual; - 202 : : } + 46 : : bytes4 internal constant TRANSFERRED_SELECTOR_ERC7943_FROM = + 47 : : bytes4(keccak256("transferred(address,address,address,uint256,uint256)")); + 48 : : + 49 : : /*////////////////////////////////////////////////////////////// + 50 : : EXTERNAL FUNCTIONS + 51 : : //////////////////////////////////////////////////////////////*/ + 52 : : + 53 : : /** + 54 : : * @inheritdoc ITransferContext + 55 : : */ + 56 : 47 : function transferred(MultiTokenTransferContext calldata ctx) external virtual override { + 57 [ + + ]: 47 : if (_isDelegated(ctx.sender, ctx.from)) { + 58 : 17 : _transferredFrom(ctx.sender, ctx.from, ctx.to, ctx.value); + 59 : : } else { + 60 : 30 : _transferred(ctx.from, ctx.to, ctx.value); + 61 : : } + 62 : : } + 63 : : + 64 : : /** + 65 : : * @inheritdoc ITransferContext + 66 : : */ + 67 : 50 : function transferred(FungibleTransferContext calldata ctx) external virtual override { + 68 [ + + ]: 50 : if (_isDelegated(ctx.sender, ctx.from)) { + 69 : 17 : _transferredFrom(ctx.sender, ctx.from, ctx.to, ctx.value); + 70 : : } else { + 71 : 33 : _transferred(ctx.from, ctx.to, ctx.value); + 72 : : } + 73 : : } + 74 : : + 75 : : /*////////////////////////////////////////////////////////////// + 76 : : PUBLIC FUNCTIONS + 77 : : //////////////////////////////////////////////////////////////*/ + 78 : : + 79 : : /** + 80 : : * @inheritdoc IERC7943NonFungibleComplianceExtend + 81 : : */ + 82 : 28 : function transferred( + 83 : : address from, + 84 : : address to, + 85 : : uint256, + 86 : : /* tokenId */ + 87 : : uint256 value + 88 : : ) + 89 : : public + 90 : : virtual + 91 : : override(IERC7943NonFungibleComplianceExtend) + 92 : : { + 93 : 28 : _transferred(from, to, value); + 94 : : } + 95 : : + 96 : : /** + 97 : : * @inheritdoc IERC7943NonFungibleComplianceExtend + 98 : : */ + 99 : 38 : function transferred( + 100 : : address spender, + 101 : : address from, + 102 : : address to, + 103 : : uint256, + 104 : : /* tokenId */ + 105 : : uint256 value + 106 : : ) + 107 : : public + 108 : : virtual + 109 : : override(IERC7943NonFungibleComplianceExtend) + 110 : : { + 111 [ + + ]: 38 : if (_isDelegated(spender, from)) { + 112 : 24 : _transferredFrom(spender, from, to, value); + 113 : : } else { + 114 : 14 : _transferred(from, to, value); + 115 : : } + 116 : : } + 117 : : + 118 : : /** + 119 : : * @inheritdoc IERC7943NonFungibleComplianceExtend + 120 : : */ + 121 : 32 : function detectTransferRestriction( + 122 : : address from, + 123 : : address to, + 124 : : uint256, + 125 : : /* tokenId */ + 126 : : uint256 value + 127 : : ) + 128 : : public + 129 : : view + 130 : : virtual + 131 : : override(IERC7943NonFungibleComplianceExtend) + 132 : : returns (uint8) + 133 : : { + 134 : 32 : return _detectTransferRestriction(from, to, value); + 135 : : } + 136 : : + 137 : : /** + 138 : : * @inheritdoc IERC7943NonFungibleComplianceExtend + 139 : : */ + 140 : 42 : function detectTransferRestrictionFrom( + 141 : : address spender, + 142 : : address from, + 143 : : address to, + 144 : : uint256, + 145 : : /* tokenId */ + 146 : : uint256 value + 147 : : ) + 148 : : public + 149 : : view + 150 : : virtual + 151 : : override(IERC7943NonFungibleComplianceExtend) + 152 : : returns (uint8) + 153 : : { + 154 : 80 : return _isDelegated(spender, from) + 155 : : ? _detectTransferRestrictionFrom(spender, from, to, value) + 156 : : : _detectTransferRestriction(from, to, value); + 157 : : } + 158 : : + 159 : : /** + 160 : : * @inheritdoc IERC7943NonFungibleCompliance + 161 : : */ + 162 : 29 : function canTransfer( + 163 : : address from, + 164 : : address to, + 165 : : uint256, + 166 : : /* tokenId */ + 167 : : uint256 amount + 168 : : ) + 169 : : public + 170 : : view + 171 : : virtual + 172 : : override(IERC7943NonFungibleCompliance) + 173 : : returns (bool) + 174 : : { + 175 : 29 : return _detectTransferRestriction(from, to, amount) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 176 : : } + 177 : : + 178 : : /** + 179 : : * @inheritdoc IERC7943NonFungibleComplianceExtend + 180 : : */ + 181 : 38 : function canTransferFrom( + 182 : : address spender, + 183 : : address from, + 184 : : address to, + 185 : : uint256, + 186 : : /* tokenId */ + 187 : : uint256 value + 188 : : ) + 189 : : public + 190 : : view + 191 : : virtual + 192 : : override(IERC7943NonFungibleComplianceExtend) + 193 : : returns (bool) + 194 : : { + 195 : 38 : return detectTransferRestrictionFrom(spender, from, to, 0, value) + 196 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 197 : : } + 198 : : + 199 : : /*////////////////////////////////////////////////////////////// + 200 : : INTERNAL FUNCTIONS + 201 : : //////////////////////////////////////////////////////////////*/ + 202 : : + 203 : : /** + 204 : : * @notice Returns whether `spender` acts on behalf of `from`, rather than being `from` itself. + 205 : : * @dev The whole adapter routes on this. `spender == from` is an owner-initiated transfer and takes + 206 : : * the direct hook, matching what a plain `transfer` produces on the CMTAT path (`spender == 0`, + 207 : : * 3-arg overload). Nethermind AuditAgent NM-6: the ERC-7943 overloads used to call the + 208 : : * spender-aware hook unconditionally, so an owner-initiated ERC-721 `transferFrom` was screened as + 209 : : * delegated while the identical {ITransferContext} call was not. + 210 : : * @param spender Address performing the transfer, as reported by the calling interface. + 211 : : * @param from Address the tokens leave. + 212 : : * @return True when the transfer is delegated and the spender must be screened. + 213 : : */ + 214 : 215 : function _isDelegated(address spender, address from) internal pure virtual returns (bool) { + 215 : 215 : return spender != address(0) && spender != from; + 216 : : } + 217 : : + 218 : : /** + 219 : : * @notice Internal hook for post-transfer validation or state updates. + 220 : : * @param from Address tokens are transferred from. + 221 : : * @param to Address tokens are transferred to. + 222 : : * @param value Amount transferred. + 223 : : */ + 224 : 0 : function _transferred(address from, address to, uint256 value) internal virtual; + 225 : : + 226 : : /** + 227 : : * @notice Internal hook for post-transfer validation or state updates (spender-aware). + 228 : : * @param spender Address executing the transfer on behalf of `from`. + 229 : : * @param from Address tokens are transferred from. + 230 : : * @param to Address tokens are transferred to. + 231 : : * @param value Amount transferred. + 232 : : */ + 233 : 0 : function _transferredFrom(address spender, address from, address to, uint256 value) internal virtual; + 234 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func-sort-c.html index 98ecf64b..646fe6b2 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleTransferValidation.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleTransferValidation.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 @@ -69,32 +69,32 @@ Hit count Sort by hit count - RuleTransferValidation._detectTransferRestriction + RuleTransferValidation._detectTransferRestriction 0 - RuleTransferValidation._detectTransferRestrictionFrom + RuleTransferValidation._detectTransferRestrictionFrom 0 - RuleTransferValidation.canTransferFrom - 30 + RuleTransferValidation.canTransferFrom + 36 RuleTransferValidation.canTransfer - 37 + 64 RuleTransferValidation.detectTransferRestrictionFrom - 58 + 95 - RuleTransferValidation.supportsInterface - 230 + RuleTransferValidation.supportsInterface + 655 RuleTransferValidation.detectTransferRestriction - 923 + 1615
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func.html index 514ce56f..99f830ca 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleTransferValidation.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleTransferValidation.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 @@ -69,32 +69,32 @@ Hit count Sort by hit count - RuleTransferValidation._detectTransferRestriction + RuleTransferValidation._detectTransferRestriction 0 - RuleTransferValidation._detectTransferRestrictionFrom + RuleTransferValidation._detectTransferRestrictionFrom 0 RuleTransferValidation.canTransfer - 37 + 64 - RuleTransferValidation.canTransferFrom - 30 + RuleTransferValidation.canTransferFrom + 36 RuleTransferValidation.detectTransferRestriction - 923 + 1615 RuleTransferValidation.detectTransferRestrictionFrom - 58 + 95 - RuleTransferValidation.supportsInterface - 230 + RuleTransferValidation.supportsInterface + 655
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.gcov.html index b36cf0a1..47a4df1e 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleTransferValidation.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleTransferValidation.sol + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleTransferValidation.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 @@ -104,27 +104,27 @@ 33 : : /** 34 : : * @inheritdoc IERC1404 35 : : */ - 36 : 923 : function detectTransferRestriction(address from, address to, uint256 value) + 36 : 1615 : function detectTransferRestriction(address from, address to, uint256 value) 37 : : public 38 : : view 39 : : virtual 40 : : override(IERC1404) 41 : : returns (uint8) 42 : : { - 43 : 923 : return _detectTransferRestriction(from, to, value); + 43 : 1615 : return _detectTransferRestriction(from, to, value); 44 : : } 45 : : 46 : : /** 47 : : * @inheritdoc IERC1404Extend 48 : : */ - 49 : 58 : function detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 49 : 95 : function detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) 50 : : public 51 : : view 52 : : virtual 53 : : override(IERC1404Extend) 54 : : returns (uint8) 55 : : { - 56 : 58 : return _detectTransferRestrictionFrom(spender, from, to, value); + 56 : 95 : return _detectTransferRestrictionFrom(spender, from, to, value); 57 : : } 58 : : 59 : : /** @@ -135,72 +135,73 @@ 64 : : * @return isValid => true if the transfer is valid, false otherwise 65 : : * 66 : : */ - 67 : 37 : function canTransfer(address from, address to, uint256 amount) + 67 : 64 : function canTransfer(address from, address to, uint256 amount) 68 : : public 69 : : view - 70 : : override(IERC3643ComplianceRead) - 71 : : returns (bool isValid) - 72 : : { - 73 : 37 : return _detectTransferRestriction(from, to, amount) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 74 : : } - 75 : : - 76 : : /** - 77 : : * @inheritdoc IERC7551Compliance - 78 : : */ - 79 : 30 : function canTransferFrom(address spender, address from, address to, uint256 value) - 80 : : public - 81 : : view - 82 : : virtual - 83 : : override(IERC7551Compliance) - 84 : : returns (bool) - 85 : : { - 86 : 30 : return _detectTransferRestrictionFrom(spender, from, to, value) - 87 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - 88 : : } - 89 : : - 90 : : /** - 91 : : * @notice Returns whether this contract implements the given interface. - 92 : : * @param interfaceId The ERC-165 interface identifier to query. - 93 : : * @return True if the interface is supported. - 94 : : */ - 95 : 230 : function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { - 96 : 230 : return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID - 97 : 229 : || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID - 98 : 228 : || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId - 99 : 133 : || interfaceId == type(IERC3643IComplianceContract).interfaceId; - 100 : : } - 101 : : - 102 : : /*////////////////////////////////////////////////////////////// - 103 : : INTERNAL FUNCTIONS - 104 : : //////////////////////////////////////////////////////////////*/ - 105 : : - 106 : : /** - 107 : : * @notice Internal transfer restriction check. - 108 : : * @param from the origin address - 109 : : * @param to the destination address - 110 : : * @param value amount to transfer - 111 : : * @return restrictionCode The restriction code for this rule. - 112 : : */ - 113 : 0 : function _detectTransferRestriction(address from, address to, uint256 value) - 114 : : internal - 115 : : view - 116 : : virtual - 117 : : returns (uint8 restrictionCode); - 118 : : - 119 : : /** - 120 : : * @notice Internal transfer restriction check for spender-initiated transfers. - 121 : : * @param spender the caller executing the transfer - 122 : : * @param from the origin address - 123 : : * @param to the destination address - 124 : : * @param value amount to transfer - 125 : : * @return restrictionCode The restriction code for this rule. - 126 : : */ - 127 : 0 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) - 128 : : internal - 129 : : view - 130 : : virtual - 131 : : returns (uint8 restrictionCode); - 132 : : } + 70 : : virtual + 71 : : override(IERC3643ComplianceRead) + 72 : : returns (bool isValid) + 73 : : { + 74 : 64 : return _detectTransferRestriction(from, to, amount) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 75 : : } + 76 : : + 77 : : /** + 78 : : * @inheritdoc IERC7551Compliance + 79 : : */ + 80 : 36 : function canTransferFrom(address spender, address from, address to, uint256 value) + 81 : : public + 82 : : view + 83 : : virtual + 84 : : override(IERC7551Compliance) + 85 : : returns (bool) + 86 : : { + 87 : 36 : return _detectTransferRestrictionFrom(spender, from, to, value) + 88 : : == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + 89 : : } + 90 : : + 91 : : /** + 92 : : * @notice Returns whether this contract implements the given interface. + 93 : : * @param interfaceId The ERC-165 interface identifier to query. + 94 : : * @return True if the interface is supported. + 95 : : */ + 96 : 655 : function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + 97 : 655 : return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID + 98 : 652 : || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID + 99 : 649 : || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId + 100 : 452 : || interfaceId == type(IERC3643IComplianceContract).interfaceId; + 101 : : } + 102 : : + 103 : : /*////////////////////////////////////////////////////////////// + 104 : : INTERNAL FUNCTIONS + 105 : : //////////////////////////////////////////////////////////////*/ + 106 : : + 107 : : /** + 108 : : * @notice Internal transfer restriction check. + 109 : : * @param from the origin address + 110 : : * @param to the destination address + 111 : : * @param value amount to transfer + 112 : : * @return restrictionCode The restriction code for this rule. + 113 : : */ + 114 : 0 : function _detectTransferRestriction(address from, address to, uint256 value) + 115 : : internal + 116 : : view + 117 : : virtual + 118 : : returns (uint8 restrictionCode); + 119 : : + 120 : : /** + 121 : : * @notice Internal transfer restriction check for spender-initiated transfers. + 122 : : * @param spender the caller executing the transfer + 123 : : * @param from the origin address + 124 : : * @param to the destination address + 125 : : * @param value amount to transfer + 126 : : * @return restrictionCode The restriction code for this rule. + 127 : : */ + 128 : 0 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value) + 129 : : internal + 130 : : view + 131 : : virtual + 132 : : returns (uint8 restrictionCode); + 133 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func-sort-c.html index 8719eec6..6eec888b 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleWhitelistShared.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleWhitelistShared.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 46 - 47 - 97.9 % + 53 + 55 + 96.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 11 - 12 - 91.7 % + 14 + 16 + 87.5 % @@ -69,52 +69,68 @@ Hit count Sort by hit count - RuleWhitelistShared._authorizeMintBurnManager + RuleWhitelistShared._authorizeCheckSpenderManager 0 - RuleWhitelistShared.setAllowBurn - 7 + RuleWhitelistShared._authorizeMintBurnManager + 0 - RuleWhitelistShared.canReturnTransferRestrictionCode - 10 + RuleWhitelistShared.onlyCheckSpenderManager + 8 - RuleWhitelistShared.onlyMintBurnManager - 13 + RuleWhitelistShared.setAllowBurn + 8 - RuleWhitelistShared.setAllowMint - 13 + RuleWhitelistShared.setCheckSpender + 8 - RuleWhitelistShared.transferred.1 - 13 + RuleWhitelistShared.canReturnTransferRestrictionCode + 10 - RuleWhitelistShared.messageForTransferRestriction - 19 + RuleWhitelistShared.transferred.1 + 18 - RuleWhitelistShared.transferred.0 + RuleWhitelistShared.messageForTransferRestriction 19 - RuleWhitelistShared._transferredFrom + RuleWhitelistShared.onlyMintBurnManager + 32 + + + RuleWhitelistShared.setAllowMint 32 - RuleWhitelistShared._transferred + RuleWhitelistShared._transferredFrom + 37 + + + RuleWhitelistShared.transferred.0 40 - RuleWhitelistShared._detectMintBurnRestriction - 166 + RuleWhitelistShared._transferred + 73 + + + RuleWhitelistShared._detectMintBurnRestriction + 237 + + + RuleWhitelistShared._setAllowMintBurn + 290 - RuleWhitelistShared._setAllowMintBurn - 245 + RuleWhitelistShared._setCheckSpender + 296
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func.html index bdcb47fb..be4373d9 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleWhitelistShared.sol - functions + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleWhitelistShared.sol - functions @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 46 - 47 - 97.9 % + 53 + 55 + 96.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 11 - 12 - 91.7 % + 14 + 16 + 87.5 % @@ -69,52 +69,68 @@ Hit count Sort by hit count - RuleWhitelistShared._authorizeMintBurnManager + RuleWhitelistShared._authorizeCheckSpenderManager 0 - RuleWhitelistShared._detectMintBurnRestriction - 166 + RuleWhitelistShared._authorizeMintBurnManager + 0 - RuleWhitelistShared._setAllowMintBurn - 245 + RuleWhitelistShared._detectMintBurnRestriction + 237 - RuleWhitelistShared._transferred - 40 + RuleWhitelistShared._setAllowMintBurn + 290 - RuleWhitelistShared._transferredFrom - 32 + RuleWhitelistShared._setCheckSpender + 296 + + + RuleWhitelistShared._transferred + 73 + + + RuleWhitelistShared._transferredFrom + 37 - RuleWhitelistShared.canReturnTransferRestrictionCode + RuleWhitelistShared.canReturnTransferRestrictionCode 10 - RuleWhitelistShared.messageForTransferRestriction + RuleWhitelistShared.messageForTransferRestriction 19 + + RuleWhitelistShared.onlyCheckSpenderManager + 8 + RuleWhitelistShared.onlyMintBurnManager - 13 + 32 + + + RuleWhitelistShared.setAllowBurn + 8 - RuleWhitelistShared.setAllowBurn - 7 + RuleWhitelistShared.setAllowMint + 32 - RuleWhitelistShared.setAllowMint - 13 + RuleWhitelistShared.setCheckSpender + 8 - RuleWhitelistShared.transferred.0 - 19 + RuleWhitelistShared.transferred.0 + 40 - RuleWhitelistShared.transferred.1 - 13 + RuleWhitelistShared.transferred.1 + 18
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.gcov.html index 23a6117d..d57253df 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/RuleWhitelistShared.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core/RuleWhitelistShared.sol + LCOV - lcov2.info - src/rules/validation/abstract/core/RuleWhitelistShared.sol @@ -28,21 +28,21 @@ Test: - lcov.info + lcov2.info Lines: - 46 - 47 - 97.9 % + 53 + 55 + 96.4 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 11 - 12 - 91.7 % + 14 + 16 + 87.5 % @@ -114,169 +114,203 @@ 43 : : ACCESS CONTROL 44 : : //////////////////////////////////////////////////////////////*/ 45 : : - 46 : 13 : modifier onlyMintBurnManager() { - 47 : 13 : _authorizeMintBurnManager(); + 46 : 32 : modifier onlyMintBurnManager() { + 47 : 32 : _authorizeMintBurnManager(); 48 : : _; 49 : : } 50 : : - 51 : : /*////////////////////////////////////////////////////////////// - 52 : : EXTERNAL FUNCTIONS - 53 : : //////////////////////////////////////////////////////////////*/ - 54 : : - 55 : : /** - 56 : : * @notice Checks whether a restriction code is recognized by this rule. - 57 : : * @dev - 58 : : * Used to verify if a returned restriction code belongs to the whitelist rule. - 59 : : * @param restrictionCode The restriction code to validate. - 60 : : * @return isKnown True if the restriction code is recognized by this rule, false otherwise. - 61 : : */ - 62 : 10 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool isKnown) { - 63 : 10 : return restrictionCode == CODE_ADDRESS_FROM_NOT_WHITELISTED - 64 : 5 : || restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED - 65 : 2 : || restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED || restrictionCode == CODE_MINT_NOT_ALLOWED - 66 : 2 : || restrictionCode == CODE_BURN_NOT_ALLOWED; - 67 : : } - 68 : : - 69 : : /** - 70 : : * @notice Enables or disables minting through this rule. - 71 : : * @param value The new value of the `allowMint` flag. - 72 : : */ - 73 : 13 : function setAllowMint(bool value) public virtual onlyMintBurnManager { - 74 : 10 : allowMint = value; - 75 : 10 : emit AllowMintUpdated(value); - 76 : : } - 77 : : - 78 : : /** - 79 : : * @notice Enables or disables burning through this rule. - 80 : : * @param value The new value of the `allowBurn` flag. - 81 : : */ - 82 : 7 : function setAllowBurn(bool value) public virtual onlyMintBurnManager { - 83 : 5 : allowBurn = value; - 84 : 5 : emit AllowBurnUpdated(value); - 85 : : } - 86 : : - 87 : : /** - 88 : : * @notice Returns the human-readable message corresponding to a restriction code. - 89 : : * @dev - 90 : : * Returns a descriptive text that explains why a transfer was restricted. - 91 : : * @param restrictionCode The restriction code to decode. - 92 : : * @return message A human-readable explanation of the restriction. - 93 : : */ - 94 : 19 : function messageForTransferRestriction(uint8 restrictionCode) - 95 : : external - 96 : : pure - 97 : : override - 98 : : returns (string memory message) - 99 : : { - 100 [ + + ]: 19 : if (restrictionCode == CODE_ADDRESS_FROM_NOT_WHITELISTED) { - 101 : 6 : return TEXT_ADDRESS_FROM_NOT_WHITELISTED; - 102 [ + + ]: 13 : } else if (restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED) { - 103 : 4 : return TEXT_ADDRESS_TO_NOT_WHITELISTED; - 104 [ + + ]: 9 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED) { - 105 : 2 : return TEXT_ADDRESS_SPENDER_NOT_WHITELISTED; - 106 [ + + ]: 7 : } else if (restrictionCode == CODE_MINT_NOT_ALLOWED) { - 107 : 3 : return TEXT_MINT_NOT_ALLOWED; - 108 [ + + ]: 4 : } else if (restrictionCode == CODE_BURN_NOT_ALLOWED) { - 109 : 2 : return TEXT_BURN_NOT_ALLOWED; - 110 : : } else { - 111 : 2 : return TEXT_CODE_NOT_FOUND; - 112 : : } + 51 : 8 : modifier onlyCheckSpenderManager() { + 52 : 8 : _authorizeCheckSpenderManager(); + 53 : : _; + 54 : : } + 55 : : + 56 : : /*////////////////////////////////////////////////////////////// + 57 : : EXTERNAL FUNCTIONS + 58 : : //////////////////////////////////////////////////////////////*/ + 59 : : + 60 : : /** + 61 : : * @notice Checks whether a restriction code is recognized by this rule. + 62 : : * @dev + 63 : : * Used to verify if a returned restriction code belongs to the whitelist rule. + 64 : : * @param restrictionCode The restriction code to validate. + 65 : : * @return isKnown True if the restriction code is recognized by this rule, false otherwise. + 66 : : */ + 67 : 10 : function canReturnTransferRestrictionCode(uint8 restrictionCode) external pure override returns (bool isKnown) { + 68 : 10 : return restrictionCode == CODE_ADDRESS_FROM_NOT_WHITELISTED + 69 : 5 : || restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED + 70 : 2 : || restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED || restrictionCode == CODE_MINT_NOT_ALLOWED + 71 : 2 : || restrictionCode == CODE_BURN_NOT_ALLOWED; + 72 : : } + 73 : : + 74 : : /** + 75 : : * @notice Returns the human-readable message corresponding to a restriction code. + 76 : : * @dev + 77 : : * Returns a descriptive text that explains why a transfer was restricted. + 78 : : * @param restrictionCode The restriction code to decode. + 79 : : * @return message A human-readable explanation of the restriction. + 80 : : */ + 81 : 19 : function messageForTransferRestriction(uint8 restrictionCode) + 82 : : external + 83 : : pure + 84 : : override + 85 : : returns (string memory message) + 86 : : { + 87 [ + + ]: 19 : if (restrictionCode == CODE_ADDRESS_FROM_NOT_WHITELISTED) { + 88 : 6 : return TEXT_ADDRESS_FROM_NOT_WHITELISTED; + 89 [ + + ]: 13 : } else if (restrictionCode == CODE_ADDRESS_TO_NOT_WHITELISTED) { + 90 : 4 : return TEXT_ADDRESS_TO_NOT_WHITELISTED; + 91 [ + + ]: 9 : } else if (restrictionCode == CODE_ADDRESS_SPENDER_NOT_WHITELISTED) { + 92 : 2 : return TEXT_ADDRESS_SPENDER_NOT_WHITELISTED; + 93 [ + + ]: 7 : } else if (restrictionCode == CODE_MINT_NOT_ALLOWED) { + 94 : 3 : return TEXT_MINT_NOT_ALLOWED; + 95 [ + + ]: 4 : } else if (restrictionCode == CODE_BURN_NOT_ALLOWED) { + 96 : 2 : return TEXT_BURN_NOT_ALLOWED; + 97 : : } else { + 98 : 2 : return TEXT_CODE_NOT_FOUND; + 99 : : } + 100 : : } + 101 : : + 102 : : /*////////////////////////////////////////////////////////////// + 103 : : PUBLIC FUNCTIONS + 104 : : //////////////////////////////////////////////////////////////*/ + 105 : : + 106 : : /** + 107 : : * @notice Enables or disables spender verification on delegated transfers. + 108 : : * @dev Restricted to the check-spender manager; emits {CheckSpenderUpdated}. + 109 : : * @param value The new state of the `checkSpender` flag. + 110 : : */ + 111 : 8 : function setCheckSpender(bool value) public virtual onlyCheckSpenderManager { + 112 : 6 : _setCheckSpender(value); 113 : : } 114 : : - 115 : : /*////////////////////////////////////////////////////////////// - 116 : : PUBLIC FUNCTIONS - 117 : : //////////////////////////////////////////////////////////////*/ - 118 : : - 119 : : /** - 120 : : * @notice ERC-3643 hook called when a transfer occurs. - 121 : : * @dev - 122 : : * - Validates that both `from` and `to` addresses are whitelisted. - 123 : : * - Reverts if any restriction code other than `TRANSFER_OK` is returned. - 124 : : * - Validation only; does not modify state. - 125 : : * - Should be called during token transfer logic to enforce whitelist compliance. - 126 : : * @param from The address sending tokens. - 127 : : * @param to The address receiving tokens. - 128 : : * @param value The token amount being transferred. - 129 : : */ - 130 : 19 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { - 131 : 19 : _transferred(from, to, value); - 132 : : } - 133 : : - 134 : : /** - 135 : : * @notice hook called when a delegated transfer occurs (`transferFrom`). - 136 : : * @dev - 137 : : * - Validates that `spender`, `from`, and `to` are all whitelisted. - 138 : : * - Reverts if any restriction code other than `TRANSFER_OK` is returned. - 139 : : * - Validation only; does not modify state. - 140 : : * @param spender The address performing the transfer on behalf of another. - 141 : : * @param from The address from which tokens are transferred. - 142 : : * @param to The recipient address. - 143 : : * @param value The token amount being transferred. - 144 : : */ - 145 : 13 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { - 146 : 13 : _transferredFrom(spender, from, to, value); - 147 : : } - 148 : : - 149 : : /*////////////////////////////////////////////////////////////// - 150 : : INTERNAL FUNCTIONS - 151 : : //////////////////////////////////////////////////////////////*/ - 152 : : - 153 : : /** - 154 : : * @notice Sets both mint/burn flags at once (deployment helper). - 155 : : * @param allowMint_ Whether minting is permitted. - 156 : : * @param allowBurn_ Whether burning is permitted. - 157 : : */ - 158 : 245 : function _setAllowMintBurn(bool allowMint_, bool allowBurn_) internal virtual { - 159 : 245 : allowMint = allowMint_; - 160 : 245 : allowBurn = allowBurn_; - 161 : 245 : emit AllowMintUpdated(allowMint_); - 162 : 245 : emit AllowBurnUpdated(allowBurn_); - 163 : : } - 164 : : - 165 : : /** - 166 : : * @notice Gates the mint/burn OPERATION, before any address is screened. - 167 : : * @dev Shared by {RuleWhitelistBase} and {RuleWhitelistWrapperBase} so the two can never drift. - 168 : : * @param from The sender (zero address for a mint). - 169 : : * @param to The recipient (zero address for a burn). - 170 : : * @return The restriction code, or TRANSFER_OK when the operation is permitted. - 171 : : */ - 172 : 166 : function _detectMintBurnRestriction(address from, address to) internal view virtual returns (uint8) { - 173 [ + ]: 166 : if (from == address(0) && !allowMint) { - 174 : 8 : return CODE_MINT_NOT_ALLOWED; - 175 : : } - 176 [ + ]: 158 : if (to == address(0) && !allowBurn) { - 177 : 3 : return CODE_BURN_NOT_ALLOWED; - 178 : : } - 179 : 155 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); - 180 : : } - 181 : : - 182 : : /** - 183 : : * @notice Authorizes the caller to toggle `allowMint` / `allowBurn`; reverts otherwise. + 115 : : /** + 116 : : * @notice Enables or disables minting through this rule. + 117 : : * @param value The new value of the `allowMint` flag. + 118 : : */ + 119 : 32 : function setAllowMint(bool value) public virtual onlyMintBurnManager { + 120 : 29 : allowMint = value; + 121 : 29 : emit AllowMintUpdated(value); + 122 : : } + 123 : : + 124 : : /** + 125 : : * @notice Enables or disables burning through this rule. + 126 : : * @param value The new value of the `allowBurn` flag. + 127 : : */ + 128 : 8 : function setAllowBurn(bool value) public virtual onlyMintBurnManager { + 129 : 6 : allowBurn = value; + 130 : 6 : emit AllowBurnUpdated(value); + 131 : : } + 132 : : + 133 : : /** + 134 : : * @notice ERC-3643 hook called when a transfer occurs. + 135 : : * @dev + 136 : : * - Validates that both `from` and `to` addresses are whitelisted. + 137 : : * - Reverts if any restriction code other than `TRANSFER_OK` is returned. + 138 : : * - Validation only; does not modify state. + 139 : : * - Should be called during token transfer logic to enforce whitelist compliance. + 140 : : * @param from The address sending tokens. + 141 : : * @param to The address receiving tokens. + 142 : : * @param value The token amount being transferred. + 143 : : */ + 144 : 40 : function transferred(address from, address to, uint256 value) public view override(IERC3643IComplianceContract) { + 145 : 40 : _transferred(from, to, value); + 146 : : } + 147 : : + 148 : : /** + 149 : : * @notice hook called when a delegated transfer occurs (`transferFrom`). + 150 : : * @dev + 151 : : * - Validates that `spender`, `from`, and `to` are all whitelisted. + 152 : : * - Reverts if any restriction code other than `TRANSFER_OK` is returned. + 153 : : * - Validation only; does not modify state. + 154 : : * @param spender The address performing the transfer on behalf of another. + 155 : : * @param from The address from which tokens are transferred. + 156 : : * @param to The recipient address. + 157 : : * @param value The token amount being transferred. + 158 : : */ + 159 : 18 : function transferred(address spender, address from, address to, uint256 value) public view override(IRuleEngine) { + 160 : 18 : _transferredFrom(spender, from, to, value); + 161 : : } + 162 : : + 163 : : /*////////////////////////////////////////////////////////////// + 164 : : INTERNAL FUNCTIONS + 165 : : //////////////////////////////////////////////////////////////*/ + 166 : : + 167 : : /** + 168 : : * @notice Internal helper to update the {checkSpender} flag and emit {CheckSpenderUpdated}. + 169 : : * @dev The event lives here rather than at the call site so the constructors of the inheriting + 170 : : * rules announce the initial value too, matching {_setAllowMintBurn}. Without it an indexer + 171 : : * could reconstruct `allowMint` and `allowBurn` from genesis but had to special-case + 172 : : * `checkSpender` (`CLAUDE_ANALYSIS.md` C-2). + 173 : : * @param value New flag value. + 174 : : */ + 175 : 296 : function _setCheckSpender(bool value) internal virtual { + 176 : 296 : checkSpender = value; + 177 : 296 : emit CheckSpenderUpdated(value); + 178 : : } + 179 : : + 180 : : /** + 181 : : * @notice Sets both mint/burn flags at once (deployment helper). + 182 : : * @param allowMint_ Whether minting is permitted. + 183 : : * @param allowBurn_ Whether burning is permitted. 184 : : */ - 185 : 0 : function _authorizeMintBurnManager() internal view virtual; - 186 : : - 187 : : /** - 188 : : * @inheritdoc RuleNFTAdapter - 189 : : */ - 190 : 40 : function _transferred(address from, address to, uint256 value) internal view virtual override { - 191 : 40 : uint8 code = _detectTransferRestriction(from, to, value); - 192 [ + + ]: 40 : require( - 193 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), - 194 : : RuleWhitelist_InvalidTransfer(address(this), from, to, value, code) - 195 : : ); - 196 : : } - 197 : : - 198 : : /** - 199 : : * @inheritdoc RuleNFTAdapter - 200 : : */ - 201 : 32 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { - 202 : 32 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); - 203 [ + + ]: 32 : require( - 204 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), - 205 : : RuleWhitelist_InvalidTransferFrom(address(this), spender, from, to, value, code) - 206 : : ); + 185 : 290 : function _setAllowMintBurn(bool allowMint_, bool allowBurn_) internal virtual { + 186 : 290 : allowMint = allowMint_; + 187 : 290 : allowBurn = allowBurn_; + 188 : 290 : emit AllowMintUpdated(allowMint_); + 189 : 290 : emit AllowBurnUpdated(allowBurn_); + 190 : : } + 191 : : + 192 : : /** + 193 : : * @notice Gates the mint/burn OPERATION, before any address is screened. + 194 : : * @dev Shared by {RuleWhitelistBase} and {RuleWhitelistWrapperBase} so the two can never drift. + 195 : : * @param from The sender (zero address for a mint). + 196 : : * @param to The recipient (zero address for a burn). + 197 : : * @return The restriction code, or TRANSFER_OK when the operation is permitted. + 198 : : */ + 199 : 237 : function _detectMintBurnRestriction(address from, address to) internal view virtual returns (uint8) { + 200 [ + ]: 237 : if (from == address(0) && !allowMint) { + 201 : 11 : return CODE_MINT_NOT_ALLOWED; + 202 : : } + 203 [ + ]: 226 : if (to == address(0) && !allowBurn) { + 204 : 4 : return CODE_BURN_NOT_ALLOWED; + 205 : : } + 206 : 222 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK); 207 : : } - 208 : : } + 208 : : + 209 : : /** + 210 : : * @notice Authorizes the caller to toggle `allowMint` / `allowBurn`; reverts otherwise. + 211 : : */ + 212 : 0 : function _authorizeMintBurnManager() internal view virtual; + 213 : : + 214 : : /** + 215 : : * @notice Authorizes the caller as check-spender manager; reverts otherwise. + 216 : : * @dev Implemented by concrete subclasses with the desired access-control policy. + 217 : : * `view` by convention: an access-control hook checks and reverts, it never mutates state. + 218 : : */ + 219 : 0 : function _authorizeCheckSpenderManager() internal view virtual; + 220 : : + 221 : : /** + 222 : : * @inheritdoc RuleNFTAdapter + 223 : : */ + 224 : 73 : function _transferred(address from, address to, uint256 value) internal view virtual override { + 225 : 73 : uint8 code = _detectTransferRestriction(from, to, value); + 226 [ + + ]: 73 : require( + 227 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), + 228 : : RuleWhitelist_InvalidTransfer(address(this), from, to, value, code) + 229 : : ); + 230 : : } + 231 : : + 232 : : /** + 233 : : * @inheritdoc RuleNFTAdapter + 234 : : */ + 235 : 37 : function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual override { + 236 : 37 : uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + 237 [ + + ]: 37 : require( + 238 : : code == uint8(REJECTED_CODE_BASE.TRANSFER_OK), + 239 : : RuleWhitelist_InvalidTransferFrom(address(this), spender, from, to, value, code) + 240 : : ); + 241 : : } + 242 : : } diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.func-sort-c.html new file mode 100644 index 00000000..3f533645 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.func-sort-c.html @@ -0,0 +1,93 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/TokenSupplyReader.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - TokenSupplyReader.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:101190.9 %
Date:2026-08-19 15:38:25Functions:2366.7 %
Branches:44100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenSupplyReader._supplyToken0
TokenSupplyReader._probeTotalSupplyCallable1216
TokenSupplyReader._currentSupply1324
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.func.html new file mode 100644 index 00000000..05ef77fd --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.func.html @@ -0,0 +1,93 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/TokenSupplyReader.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - TokenSupplyReader.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:101190.9 %
Date:2026-08-19 15:38:25Functions:2366.7 %
Branches:44100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenSupplyReader._currentSupply1324
TokenSupplyReader._probeTotalSupplyCallable1216
TokenSupplyReader._supplyToken0
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.gcov.html new file mode 100644 index 00000000..e63a8c1e --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/TokenSupplyReader.sol.gcov.html @@ -0,0 +1,157 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/TokenSupplyReader.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - TokenSupplyReader.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:101190.9 %
Date:2026-08-19 15:38:25Functions:2366.7 %
Branches:44100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {ITotalSupply} from "../../../interfaces/ITotalSupply.sol";
+       5                 :            : 
+       6                 :            : /**
+       7                 :            :  * @title TokenSupplyReader
+       8                 :            :  * @notice Revert-free `totalSupply()` read shared by {RuleMaxTotalSupplyBase} and
+       9                 :            :  * {RuleChainlinkPoRBase}, which both cap minting against a foreign token's supply.
+      10                 :            :  *
+      11                 :            :  * @dev Declares **no storage**: each rule keeps its own token variable and implements
+      12                 :            :  * {_supplyToken}. Holding it here would reorder every inheriting rule's slots for no benefit.
+      13                 :            :  *
+      14                 :            :  * @dev Only the `try/catch` probe is shared, not the validation. Each rule composes
+      15                 :            :  * {_probeTotalSupplyCallable} with its own `require`s so its three configuration failures keep three
+      16                 :            :  * distinct, named errors, per the one-error-namespace-per-rule convention.
+      17                 :            :  *
+      18                 :            :  * @dev **Deployment precondition.** {_currentSupply} performs no code-length check, because a `try`
+      19                 :            :  * to a codeless address reverts *uncatchably* -- the ABI decoder fails in this frame after the call
+      20                 :            :  * returns 0 bytes, out of `catch`'s reach (not `EXTCODESIZE`, which solc >= 0.8.10 skips when return
+      21                 :            :  * data is expected). Safety comes from configuration: each setter rejects a codeless candidate and
+      22                 :            :  * EIP-6780 makes that permanent. **Assumes a Cancun-or-later chain**; on an older one a validated
+      23                 :            :  * token could still become codeless and the ERC-1404 views would revert.
+      24                 :            :  */
+      25                 :            : abstract contract TokenSupplyReader {
+      26                 :            :     /**
+      27                 :            :      * @notice The token whose `totalSupply()` this rule reads.
+      28                 :            :      * @dev Implemented by each rule against its own storage, so this base stays stateless.
+      29                 :            :      * @return The configured token.
+      30                 :            :      */
+      31                 :          0 :     function _supplyToken() internal view virtual returns (ITotalSupply);
+      32                 :            : 
+      33                 :            :     /**
+      34                 :            :      * @notice Reads the configured token's current total supply without ever reverting.
+      35                 :            :      * @dev Wrapped in `try/catch` so the ERC-1404 / ERC-3643 read path stays revert-free if the token
+      36                 :            :      * breaks after configuration -- a proxy upgraded to something that reverts, or a pausable
+      37                 :            :      * implementation that reverts while paused. Configuration already probes `totalSupply()`, so
+      38                 :            :      * reaching the failure branch means the token changed behaviour since. Callers translate
+      39                 :            :      * `available == false` into their own "supply unavailable" restriction code.
+      40                 :            :      * @return available True when the supply could be read.
+      41                 :            :      * @return supply The total supply; meaningless when `available` is false.
+      42                 :            :      */
+      43                 :       1324 :     function _currentSupply() internal view virtual returns (bool available, uint256 supply) {
+      44            [ + ]:       1324 :         try _supplyToken().totalSupply() returns (uint256 totalSupply_) {
+      45                 :       1316 :             return (true, totalSupply_);
+      46            [ + ]:          8 :         } catch {
+      47                 :          8 :             return (false, 0);
+      48                 :            :         }
+      49                 :            :     }
+      50                 :            : 
+      51                 :            :     /**
+      52                 :            :      * @notice Returns whether `candidate` answers `totalSupply()` without reverting.
+      53                 :            :      * @dev Used at configuration time to turn what would otherwise be a silent read-path failure
+      54                 :            :      * into an immediate, named error raised by the calling rule. `totalSupply()` is mandatory for
+      55                 :            :      * both rules -- the cap check cannot work without it -- unlike `decimals()`, which only
+      56                 :            :      * `RuleChainlinkPoR` consults and treats as optional.
+      57                 :            :      *
+      58                 :            :      * WARNING: the caller MUST have already established that `candidate` has code. A `try` call to a
+      59                 :            :      * codeless address reverts uncatchably -- the ABI decoder fails in the caller's frame, outside `catch`'s
+      60                 :            :      * reach -- and this probe cannot contain it. Note code alone is not sufficient either: a contract that
+      61                 :            :      * returns 0 bytes fails the same way.
+      62                 :            :      * @param candidate The token contract to probe.
+      63                 :            :      * @return True when `totalSupply()` is callable.
+      64                 :            :      */
+      65                 :       1216 :     function _probeTotalSupplyCallable(address candidate) internal view virtual returns (bool) {
+      66            [ + ]:       1216 :         try ITotalSupply(candidate).totalSupply() returns (uint256) {
+      67                 :       1213 :             return true;
+      68            [ + ]:          3 :         } catch {
+      69                 :          3 :             return false;
+      70                 :            :         }
+      71                 :            :     }
+      72                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.func-sort-c.html new file mode 100644 index 00000000..c6b64fda --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.func-sort-c.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/TotalSupplyCapManager.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - TotalSupplyCapManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:252696.2 %
Date:2026-08-19 15:38:25Functions:8988.9 %
Branches:77100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TotalSupplyCapManager._authorizeMaxTotalSupplyManager0
TotalSupplyCapManager.setTokenContract8
TotalSupplyCapManager.onlyMaxTotalSupplyManager263
TotalSupplyCapManager.setMaxTotalSupply264
TotalSupplyCapManager._setTokenContract594
TotalSupplyCapManager._validateTokenContract594
TotalSupplyCapManager._setMaxTotalSupply846
TotalSupplyCapManager._supplyToken854
TotalSupplyCapManager._capExceeded856
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.func.html b/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.func.html new file mode 100644 index 00000000..a761d0ea --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.func.html @@ -0,0 +1,117 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/TotalSupplyCapManager.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - TotalSupplyCapManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:252696.2 %
Date:2026-08-19 15:38:25Functions:8988.9 %
Branches:77100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TotalSupplyCapManager._authorizeMaxTotalSupplyManager0
TotalSupplyCapManager._capExceeded856
TotalSupplyCapManager._setMaxTotalSupply846
TotalSupplyCapManager._setTokenContract594
TotalSupplyCapManager._supplyToken854
TotalSupplyCapManager._validateTokenContract594
TotalSupplyCapManager.onlyMaxTotalSupplyManager263
TotalSupplyCapManager.setMaxTotalSupply264
TotalSupplyCapManager.setTokenContract8
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.gcov.html new file mode 100644 index 00000000..9dffa6fd --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/TotalSupplyCapManager.sol.gcov.html @@ -0,0 +1,219 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/abstract/core/TotalSupplyCapManager.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/abstract/core - TotalSupplyCapManager.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:252696.2 %
Date:2026-08-19 15:38:25Functions:8988.9 %
Branches:77100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleMaxTotalSupplyInvariantStorage} from "../invariant/RuleMaxTotalSupplyInvariantStorage.sol";
+       5                 :            : import {ITotalSupply} from "../../../interfaces/ITotalSupply.sol";
+       6                 :            : import {TokenSupplyReader} from "./TokenSupplyReader.sol";
+       7                 :            : import {CapAccounting} from "./CapAccounting.sol";
+       8                 :            : 
+       9                 :            : /**
+      10                 :            :  * @title TotalSupplyCapManager
+      11                 :            :  * @notice A static total-supply ceiling: which token to observe, what the cap is, and whether a
+      12                 :            :  * prospective mint fits under it.
+      13                 :            :  *
+      14                 :            :  * @dev Declares **no constructor** and does not depend on **ERC-1404**, so the inheriting rule
+      15                 :            :  * decides when configuration happens (constructor or initializer) and owns the restriction-code
+      16                 :            :  * mapping; {_capExceeded} answers in booleans.
+      17                 :            :  *
+      18                 :            :  * @dev The revert-free `totalSupply()` read and the configuration probe come from
+      19                 :            :  * {TokenSupplyReader} via {_supplyToken}; the deployment precondition documented there applies
+      20                 :            :  * unchanged.
+      21                 :            :  */
+      22                 :            : abstract contract TotalSupplyCapManager is CapAccounting, TokenSupplyReader, RuleMaxTotalSupplyInvariantStorage {
+      23                 :            :     /**
+      24                 :            :      * @dev tokenContract is trusted to report an *accurate* totalSupply -- nothing on-chain can
+      25                 :            :      * verify that -- but it is NOT trusted to stay callable: a reverting or codeless token yields
+      26                 :            :      * {CODE_SUPPLY_ORACLE_UNAVAILABLE} instead of reverting the MUST-NOT-revert views.
+      27                 :            :      */
+      28                 :            :     ITotalSupply public tokenContract;
+      29                 :            :     /**
+      30                 :            :      * @notice Maximum total supply; minting that would exceed this value is rejected.
+      31                 :            :      */
+      32                 :            :     uint256 public maxTotalSupply;
+      33                 :            : 
+      34                 :            :     /*//////////////////////////////////////////////////////////////
+      35                 :            :                             ACCESS CONTROL
+      36                 :            :     //////////////////////////////////////////////////////////////*/
+      37                 :            : 
+      38                 :        263 :     modifier onlyMaxTotalSupplyManager() {
+      39                 :        263 :         _authorizeMaxTotalSupplyManager();
+      40                 :            :         _;
+      41                 :            :     }
+      42                 :            : 
+      43                 :            :     /*//////////////////////////////////////////////////////////////
+      44                 :            :                         PUBLIC FUNCTIONS
+      45                 :            :     //////////////////////////////////////////////////////////////*/
+      46                 :            : 
+      47                 :            :     /**
+      48                 :            :      * @notice Updates the maximum total supply.
+      49                 :            :      * @param newMaxTotalSupply New maximum total supply value.
+      50                 :            :      */
+      51                 :        264 :     function setMaxTotalSupply(uint256 newMaxTotalSupply) public virtual onlyMaxTotalSupplyManager {
+      52                 :        261 :         _setMaxTotalSupply(newMaxTotalSupply);
+      53                 :            :     }
+      54                 :            : 
+      55                 :            :     /**
+      56                 :            :      * @notice Updates the token contract whose total supply is checked.
+      57                 :            :      * @param newTokenContract New token contract address; must not be the zero address.
+      58                 :            :      */
+      59                 :          8 :     function setTokenContract(address newTokenContract) public virtual onlyMaxTotalSupplyManager {
+      60                 :          6 :         _setTokenContract(newTokenContract);
+      61                 :            :     }
+      62                 :            : 
+      63                 :            :     /*//////////////////////////////////////////////////////////////
+      64                 :            :                         INTERNAL FUNCTIONS
+      65                 :            :     //////////////////////////////////////////////////////////////*/
+      66                 :            : 
+      67                 :            :     /**
+      68                 :            :      * @notice Stores the supply cap and emits {MaxTotalSupplyUpdated}.
+      69                 :            :      * @dev Shared by the constructor and {setMaxTotalSupply} so the event is emitted on every
+      70                 :            :      * assignment, including the initial one.
+      71                 :            :      * @param newMaxTotalSupply The new maximum total supply.
+      72                 :            :      */
+      73                 :        846 :     function _setMaxTotalSupply(uint256 newMaxTotalSupply) internal virtual {
+      74                 :        846 :         maxTotalSupply = newMaxTotalSupply;
+      75                 :        846 :         emit MaxTotalSupplyUpdated(newMaxTotalSupply);
+      76                 :            :     }
+      77                 :            : 
+      78                 :            :     /**
+      79                 :            :      * @notice Validates and stores the observed token and emits {TokenContractUpdated}.
+      80                 :            :      * @dev Shared by the constructor and {setTokenContract}; see {_setMaxTotalSupply}.
+      81                 :            :      * @param newTokenContract The new token contract.
+      82                 :            :      */
+      83                 :        594 :     function _setTokenContract(address newTokenContract) internal virtual {
+      84                 :        594 :         _validateTokenContract(newTokenContract);
+      85                 :        588 :         tokenContract = ITotalSupply(newTokenContract);
+      86                 :        588 :         emit TokenContractUpdated(newTokenContract);
+      87                 :            :     }
+      88                 :            : 
+      89                 :            :     /**
+      90                 :            :      * @notice Validates a candidate token contract before it is stored.
+      91                 :            :      * @dev `totalSupply()` is mandatory -- the cap check cannot work without it -- so it is probed
+      92                 :            :      * here, turning what would otherwise be a silent read-path failure into a named configuration
+      93                 :            :      * error. The code-length check is explicit rather than relying on the uncatchable extcodesize
+      94                 :            :      * revert that the probe would incidentally produce.
+      95                 :            :      * @param candidate The token contract to validate.
+      96                 :            :      */
+      97                 :        594 :     function _validateTokenContract(address candidate) internal view virtual {
+      98         [ +  + ]:        594 :         require(candidate != address(0), RuleMaxTotalSupply_TokenAddressZeroNotAllowed());
+      99         [ +  + ]:        592 :         require(candidate.code.length != 0, RuleMaxTotalSupply_TokenIsNotAContract(candidate));
+     100         [ +  + ]:        590 :         require(_probeTotalSupplyCallable(candidate), RuleMaxTotalSupply_TokenTotalSupplyUnavailable(candidate));
+     101                 :            :     }
+     102                 :            : 
+     103                 :            :     /**
+     104                 :            :      * @notice Authorization hook invoked before updating the max total supply or token contract.
+     105                 :            :      * @dev Implemented by concrete subclasses with the desired access-control policy.
+     106                 :            :      */
+     107                 :          0 :     function _authorizeMaxTotalSupplyManager() internal view virtual;
+     108                 :            : 
+     109                 :            :     /**
+     110                 :            :      * @inheritdoc TokenSupplyReader
+     111                 :            :      */
+     112                 :        854 :     function _supplyToken() internal view virtual override returns (ITotalSupply) {
+     113                 :        854 :         return tokenContract;
+     114                 :            :     }
+     115                 :            : 
+     116                 :            :     /**
+     117                 :            :      * @notice Reports whether minting `value` would breach the cap, without ever reverting.
+     118                 :            :      * @dev Answers in booleans rather than restriction codes, so the caller owns the ERC-1404
+     119                 :            :      * mapping. Overflow-safe: `currentSupply + value` could exceed uint256 on a MUST-NOT-revert
+     120                 :            :      * path, so the comparison uses the remaining headroom instead.
+     121                 :            :      * @param value The amount that would be minted.
+     122                 :            :      * @return supplyAvailable False when `totalSupply()` could not be read; the other return value
+     123                 :            :      * is then meaningless and the caller should treat the check as failed.
+     124                 :            :      * @return exceeded True when the mint would push total supply past {maxTotalSupply}.
+     125                 :            :      */
+     126                 :        856 :     function _capExceeded(uint256 value) internal view virtual returns (bool supplyAvailable, bool exceeded) {
+     127                 :        856 :         uint256 currentSupply;
+     128                 :        856 :         (supplyAvailable, currentSupply) = _currentSupply();
+     129            [ + ]:        856 :         if (!supplyAvailable) {
+     130                 :          4 :             return (false, false);
+     131                 :            :         }
+     132                 :        852 :         return (true, _capExceededBy(currentSupply, maxTotalSupply, value));
+     133                 :            :     }
+     134                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-b.html b/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-b.html index cecfe8ef..1633d672 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core + LCOV - lcov2.info - src/rules/validation/abstract/core @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 79 - 84 - 94.0 % + 257 + 267 + 96.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 24 - 29 - 82.8 % + 68 + 78 + 87.2 % Branches: - 20 - 20 + 81 + 81 100.0 % @@ -94,29 +94,89 @@ 0 / 0 - RuleNFTAdapter.sol + CapAccounting.sol + +
100.0%
+ + 100.0 % + 6 / 6 + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + + + TokenSupplyReader.sol
90.9%90.9%
90.9 % - 20 / 22 - 80.0 % - 8 / 10 + 10 / 11 + 66.7 % + 2 / 3 100.0 % 4 / 4 + + RuleNFTAdapter.sol + +
92.3%92.3%
+ + 92.3 % + 24 / 26 + 81.8 % + 9 / 11 + 100.0 % + 6 / 6 + + + TotalSupplyCapManager.sol + +
96.2%96.2%
+ + 96.2 % + 25 / 26 + 88.9 % + 8 / 9 + 100.0 % + 7 / 7 + RuleWhitelistShared.sol -
97.9%97.9%
+
96.4%96.4%
- 97.9 % - 46 / 47 - 91.7 % - 11 / 12 + 96.4 % + 53 / 55 + 87.5 % + 14 / 16 100.0 % 16 / 16 + + BalanceCapManager.sol + +
98.3%98.3%
+ + 98.3 % + 58 / 59 + 94.1 % + 16 / 17 + 100.0 % + 18 / 18 + + + ChainlinkPoRFeedManager.sol + +
98.6%98.6%
+ + 98.6 % + 68 / 69 + 92.3 % + 12 / 13 + 100.0 % + 29 / 29 +
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-f.html b/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-f.html index 83d45b47..d46bf0ce 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core + LCOV - lcov2.info - src/rules/validation/abstract/core @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 79 - 84 - 94.0 % + 257 + 267 + 96.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 24 - 29 - 82.8 % + 68 + 78 + 87.2 % Branches: - 20 - 20 + 81 + 81 100.0 % @@ -81,6 +81,18 @@ Functions Sort by function coverage Branches Sort by branch coverage + + TokenSupplyReader.sol + +
90.9%90.9%
+ + 90.9 % + 10 / 11 + 66.7 % + 2 / 3 + 100.0 % + 4 / 4 + RuleTransferValidation.sol @@ -96,27 +108,75 @@ RuleNFTAdapter.sol -
90.9%90.9%
+
92.3%92.3%
- 90.9 % - 20 / 22 - 80.0 % - 8 / 10 + 92.3 % + 24 / 26 + 81.8 % + 9 / 11 100.0 % - 4 / 4 + 6 / 6 RuleWhitelistShared.sol -
97.9%97.9%
+
96.4%96.4%
- 97.9 % - 46 / 47 - 91.7 % - 11 / 12 + 96.4 % + 53 / 55 + 87.5 % + 14 / 16 100.0 % 16 / 16 + + TotalSupplyCapManager.sol + +
96.2%96.2%
+ + 96.2 % + 25 / 26 + 88.9 % + 8 / 9 + 100.0 % + 7 / 7 + + + ChainlinkPoRFeedManager.sol + +
98.6%98.6%
+ + 98.6 % + 68 / 69 + 92.3 % + 12 / 13 + 100.0 % + 29 / 29 + + + BalanceCapManager.sol + +
98.3%98.3%
+ + 98.3 % + 58 / 59 + 94.1 % + 16 / 17 + 100.0 % + 18 / 18 + + + CapAccounting.sol + +
100.0%
+ + 100.0 % + 6 / 6 + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 +
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-l.html b/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-l.html index c1ef5f89..14430349 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core + LCOV - lcov2.info - src/rules/validation/abstract/core @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 79 - 84 - 94.0 % + 257 + 267 + 96.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 24 - 29 - 82.8 % + 68 + 78 + 87.2 % Branches: - 20 - 20 + 81 + 81 100.0 % @@ -94,29 +94,89 @@ 0 / 0 - RuleNFTAdapter.sol + TokenSupplyReader.sol
90.9%90.9%
90.9 % - 20 / 22 - 80.0 % - 8 / 10 + 10 / 11 + 66.7 % + 2 / 3 100.0 % 4 / 4 + + RuleNFTAdapter.sol + +
92.3%92.3%
+ + 92.3 % + 24 / 26 + 81.8 % + 9 / 11 + 100.0 % + 6 / 6 + + + TotalSupplyCapManager.sol + +
96.2%96.2%
+ + 96.2 % + 25 / 26 + 88.9 % + 8 / 9 + 100.0 % + 7 / 7 + RuleWhitelistShared.sol -
97.9%97.9%
+
96.4%96.4%
- 97.9 % - 46 / 47 - 91.7 % - 11 / 12 + 96.4 % + 53 / 55 + 87.5 % + 14 / 16 100.0 % 16 / 16 + + BalanceCapManager.sol + +
98.3%98.3%
+ + 98.3 % + 58 / 59 + 94.1 % + 16 / 17 + 100.0 % + 18 / 18 + + + ChainlinkPoRFeedManager.sol + +
98.6%98.6%
+ + 98.6 % + 68 / 69 + 92.3 % + 12 / 13 + 100.0 % + 29 / 29 + + + CapAccounting.sol + +
100.0%
+ + 100.0 % + 6 / 6 + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 +
diff --git a/doc/coverage/coverage/src/rules/validation/abstract/core/index.html b/doc/coverage/coverage/src/rules/validation/abstract/core/index.html index e4067be7..232d7bd1 100644 --- a/doc/coverage/coverage/src/rules/validation/abstract/core/index.html +++ b/doc/coverage/coverage/src/rules/validation/abstract/core/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/abstract/core + LCOV - lcov2.info - src/rules/validation/abstract/core @@ -28,29 +28,29 @@ Test: - lcov.info + lcov2.info Lines: - 79 - 84 - 94.0 % + 257 + 267 + 96.3 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 24 - 29 - 82.8 % + 68 + 78 + 87.2 % Branches: - 20 - 20 + 81 + 81 100.0 % @@ -81,17 +81,53 @@ Functions Sort by function coverage Branches Sort by branch coverage + + BalanceCapManager.sol + +
98.3%98.3%
+ + 98.3 % + 58 / 59 + 94.1 % + 16 / 17 + 100.0 % + 18 / 18 + + + CapAccounting.sol + +
100.0%
+ + 100.0 % + 6 / 6 + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + + + ChainlinkPoRFeedManager.sol + +
98.6%98.6%
+ + 98.6 % + 68 / 69 + 92.3 % + 12 / 13 + 100.0 % + 29 / 29 + RuleNFTAdapter.sol -
90.9%90.9%
+
92.3%92.3%
- 90.9 % - 20 / 22 - 80.0 % - 8 / 10 + 92.3 % + 24 / 26 + 81.8 % + 9 / 11 100.0 % - 4 / 4 + 6 / 6 RuleTransferValidation.sol @@ -108,15 +144,39 @@ RuleWhitelistShared.sol -
97.9%97.9%
+
96.4%96.4%
- 97.9 % - 46 / 47 - 91.7 % - 11 / 12 + 96.4 % + 53 / 55 + 87.5 % + 14 / 16 100.0 % 16 / 16 + + TokenSupplyReader.sol + +
90.9%90.9%
+ + 90.9 % + 10 / 11 + 66.7 % + 2 / 3 + 100.0 % + 4 / 4 + + + TotalSupplyCapManager.sol + +
96.2%96.2%
+ + 96.2 % + 25 / 26 + 88.9 % + 8 / 9 + 100.0 % + 7 / 7 +
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func-sort-c.html index fecfc7db..8587eee8 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleBlacklist.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleBlacklist.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 @@ -69,28 +69,28 @@ Hit count Sort by hit count - RuleBlacklist._authorizeAddressListRemove + RuleBlacklist._msgData 1 - RuleBlacklist._msgData - 1 + RuleBlacklist._authorizeAddressListRemove + 2 RuleBlacklist._authorizeAddressListAdd - 30 + 39 RuleBlacklist._msgSender - 88 + 128 RuleBlacklist._contextSuffixLength - 89 + 129 RuleBlacklist.supportsInterface - 92 + 157
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func.html index 298b807b..075a9b80 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleBlacklist.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleBlacklist.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 @@ -70,15 +70,15 @@ RuleBlacklist._authorizeAddressListAdd - 30 + 39 RuleBlacklist._authorizeAddressListRemove - 1 + 2 RuleBlacklist._contextSuffixLength - 89 + 129 RuleBlacklist._msgData @@ -86,11 +86,11 @@ RuleBlacklist._msgSender - 88 + 128 RuleBlacklist.supportsInterface - 92 + 157
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.gcov.html index c13246c3..93c0d729 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklist.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleBlacklist.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleBlacklist.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 @@ -105,15 +105,15 @@ 34 : : * @param interfaceId The interface identifier, as specified in ERC-165. 35 : : * @return True if the interface is supported. 36 : : */ - 37 : 92 : function supportsInterface(bytes4 interfaceId) + 37 : 157 : function supportsInterface(bytes4 interfaceId) 38 : : public 39 : : view 40 : : virtual 41 : : override(AccessControlEnumerable, RuleBlacklistBase) 42 : : returns (bool) 43 : : { - 44 : 92 : return AccessControlEnumerable.supportsInterface(interfaceId) - 45 : 62 : || RuleBlacklistBase.supportsInterface(interfaceId); + 44 : 157 : return AccessControlEnumerable.supportsInterface(interfaceId) + 45 : 106 : || RuleBlacklistBase.supportsInterface(interfaceId); 46 : : } 47 : : 48 : : /*////////////////////////////////////////////////////////////// @@ -123,12 +123,12 @@ 52 : : /** 53 : : * @notice Restricts adding addresses to the blacklist to holders of ADDRESS_LIST_ADD_ROLE. 54 : : */ - 55 : 30 : function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + 55 : 39 : function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} 56 : : 57 : : /** 58 : : * @notice Restricts removing addresses from the blacklist to holders of ADDRESS_LIST_REMOVE_ROLE. 59 : : */ - 60 : 1 : function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + 60 : 2 : function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} 61 : : 62 : : /*////////////////////////////////////////////////////////////// 63 : : INTERNAL FUNCTIONS @@ -138,8 +138,8 @@ 67 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. 68 : : * @return sender The address of the message sender. 69 : : */ - 70 : 88 : function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) { - 71 : 88 : return super._msgSender(); + 70 : 128 : function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) { + 71 : 128 : return super._msgSender(); 72 : : } 73 : : 74 : : /** @@ -154,8 +154,8 @@ 83 : : * @notice Returns the length of the context suffix appended by the forwarder. 84 : : * @return The context suffix length in bytes. 85 : : */ - 86 : 89 : function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) { - 87 : 89 : return super._contextSuffixLength(); + 86 : 129 : function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) { + 87 : 129 : return super._contextSuffixLength(); 88 : : } 89 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func-sort-c.html index 75b32fc0..5bcf739d 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func.html index 9af0941a..c47c6cb3 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.gcov.html index b7a1e4db..15c03046 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.func-sort-c.html new file mode 100644 index 00000000..f980d853 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.func-sort-c.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoR.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoR.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoR._authorizeChainlinkPoRManager20
RuleChainlinkPoR.supportsInterface21
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.func.html new file mode 100644 index 00000000..fd69ca9f --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.func.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoR.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoR.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoR._authorizeChainlinkPoRManager20
RuleChainlinkPoR.supportsInterface21
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.gcov.html new file mode 100644 index 00000000..95763244 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoR.sol.gcov.html @@ -0,0 +1,151 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoR.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoR.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
+       5                 :            : import {AccessControlModuleStandalone} from "../../../modules/AccessControlModuleStandalone.sol";
+       6                 :            : import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol";
+       7                 :            : import {RuleChainlinkPoRBase} from "../abstract/base/RuleChainlinkPoRBase.sol";
+       8                 :            : import {RuleTransferValidation} from "../abstract/core/RuleTransferValidation.sol";
+       9                 :            : 
+      10                 :            : /**
+      11                 :            :  * @title RuleChainlinkPoR
+      12                 :            :  * @notice Restricts minting so that the token's total supply never exceeds the reserves reported by
+      13                 :            :  * a Chainlink Proof of Reserve data feed.
+      14                 :            :  */
+      15                 :            : contract RuleChainlinkPoR is AccessControlModuleStandalone, RuleChainlinkPoRBase {
+      16                 :            :     /*//////////////////////////////////////////////////////////////
+      17                 :            :                              CONSTRUCTOR
+      18                 :            :     //////////////////////////////////////////////////////////////*/
+      19                 :            : 
+      20                 :            :     /**
+      21                 :            :      * @param admin Address that receives the default admin role.
+      22                 :            :      * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero).
+      23                 :            :      * @param tokenDecimals_ Decimals of that token (0 to 18, checked against `decimals()` when exposed).
+      24                 :            :      * @param reservesFeed_ Proof of Reserve data feed implementing `AggregatorV3Interface`.
+      25                 :            :      * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check.
+      26                 :            :      */
+      27                 :            :     constructor(
+      28                 :            :         address admin,
+      29                 :            :         address tokenContract_,
+      30                 :            :         uint8 tokenDecimals_,
+      31                 :            :         AggregatorV3Interface reservesFeed_,
+      32                 :            :         uint256 maxStalenessSeconds_
+      33                 :            :     )
+      34                 :            :         AccessControlModuleStandalone(admin)
+      35                 :            :         RuleChainlinkPoRBase(tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_)
+      36                 :            :     {}
+      37                 :            : 
+      38                 :            :     /*//////////////////////////////////////////////////////////////
+      39                 :            :                           PUBLIC FUNCTIONS
+      40                 :            :     //////////////////////////////////////////////////////////////*/
+      41                 :            : 
+      42                 :            :     /**
+      43                 :            :      * @notice Indicates whether this contract supports a given interface.
+      44                 :            :      * @param interfaceId The interface identifier, as specified in ERC-165.
+      45                 :            :      * @return True if the interface is supported.
+      46                 :            :      */
+      47                 :         21 :     function supportsInterface(bytes4 interfaceId)
+      48                 :            :         public
+      49                 :            :         view
+      50                 :            :         virtual
+      51                 :            :         override(AccessControlEnumerable, RuleTransferValidation)
+      52                 :            :         returns (bool)
+      53                 :            :     {
+      54                 :         21 :         return AccessControlEnumerable.supportsInterface(interfaceId)
+      55                 :         14 :             || RuleTransferValidation.supportsInterface(interfaceId);
+      56                 :            :     }
+      57                 :            : 
+      58                 :            :     /*//////////////////////////////////////////////////////////////
+      59                 :            :                             ACCESS CONTROL
+      60                 :            :     //////////////////////////////////////////////////////////////*/
+      61                 :            : 
+      62                 :            :     /**
+      63                 :            :      * @notice Restricts Proof of Reserve configuration to holders of DEFAULT_ADMIN_ROLE.
+      64                 :            :      */
+      65                 :         20 :     function _authorizeChainlinkPoRManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {}
+      66                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.func-sort-c.html new file mode 100644 index 00000000..7c61750d --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.func-sort-c.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoRERC3643.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoRERC3643._detectTransferRestrictionOnNotify9
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.func.html new file mode 100644 index 00000000..1a3374f9 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.func.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoRERC3643.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoRERC3643._detectTransferRestrictionOnNotify9
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.gcov.html new file mode 100644 index 00000000..fb54da96 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol.gcov.html @@ -0,0 +1,153 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoRERC3643.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol";
+       5                 :            : import {RuleChainlinkPoR} from "./RuleChainlinkPoR.sol";
+       6                 :            : 
+       7                 :            : /**
+       8                 :            :  * @title RuleChainlinkPoRERC3643
+       9                 :            :  * @notice {RuleChainlinkPoR} for **ERC-3643 tokens only**. Identical reserve logic; the sole difference is WHEN the
+      10                 :            :  * token reports the mint.
+      11                 :            :  *
+      12                 :            :  * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 /
+      13                 :            :  * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes
+      14                 :            :  * the new tokens. CMTAT calls the rule first and must use plain {RuleChainlinkPoR}.
+      15                 :            :  *
+      16                 :            :  * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The
+      17                 :            :  * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the reserves reported by the feed; this variant
+      18                 :            :  * on CMTAT ignores the pending amount and weakens enforcement.
+      19                 :            :  *
+      20                 :            :  * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643
+      21                 :            :  * calls `canTransfer` before `_mint`.
+      22                 :            :  */
+      23                 :            : contract RuleChainlinkPoRERC3643 is RuleChainlinkPoR {
+      24                 :            :     /*//////////////////////////////////////////////////////////////
+      25                 :            :                              CONSTRUCTOR
+      26                 :            :     //////////////////////////////////////////////////////////////*/
+      27                 :            : 
+      28                 :            :     /**
+      29                 :            :      * @param admin Address that receives the default admin role.
+      30                 :            :      * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero).
+      31                 :            :      * @param tokenDecimals_ Decimals of that token (0 to 18, checked against `decimals()` when exposed).
+      32                 :            :      * @param reservesFeed_ Proof of Reserve data feed implementing `AggregatorV3Interface`.
+      33                 :            :      * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check.
+      34                 :            :      */
+      35                 :            :     constructor(
+      36                 :            :         address admin,
+      37                 :            :         address tokenContract_,
+      38                 :            :         uint8 tokenDecimals_,
+      39                 :            :         AggregatorV3Interface reservesFeed_,
+      40                 :            :         uint256 maxStalenessSeconds_
+      41                 :            :     ) RuleChainlinkPoR(admin, tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_) {}
+      42                 :            : 
+      43                 :            :     /*//////////////////////////////////////////////////////////////
+      44                 :            :                         INTERNAL FUNCTIONS
+      45                 :            :     //////////////////////////////////////////////////////////////*/
+      46                 :            : 
+      47                 :            :     /**
+      48                 :            :      * @notice Enforcement for a token that reports the mint after performing it.
+      49                 :            :      * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the
+      50                 :            :      * minted amount, so the comparison reduces to "is the post-mint supply within the reserves".
+      51                 :            :      * @param from Sender address; the zero address denotes the mint this rule gates.
+      52                 :            :      * @param to Recipient address.
+      53                 :            :      * @return The restriction code the write hook enforces.
+      54                 :            :      */
+      55                 :          9 :     function _detectTransferRestrictionOnNotify(
+      56                 :            :         address from,
+      57                 :            :         address to,
+      58                 :            :         uint256 /* value */
+      59                 :            :     )
+      60                 :            :         internal
+      61                 :            :         view
+      62                 :            :         virtual
+      63                 :            :         override
+      64                 :            :         returns (uint8)
+      65                 :            :     {
+      66                 :          9 :         return _detectTransferRestriction(from, to, 0);
+      67                 :            :     }
+      68                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.func-sort-c.html new file mode 100644 index 00000000..5962c7ec --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.func-sort-c.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoRERC3643Ownable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoRERC3643Ownable2Step._detectTransferRestrictionOnNotify2
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.func.html new file mode 100644 index 00000000..c106a448 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.func.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoRERC3643Ownable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoRERC3643Ownable2Step._detectTransferRestrictionOnNotify2
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.gcov.html new file mode 100644 index 00000000..0f512f9a --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol.gcov.html @@ -0,0 +1,153 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoRERC3643Ownable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol";
+       5                 :            : import {RuleChainlinkPoROwnable2Step} from "./RuleChainlinkPoROwnable2Step.sol";
+       6                 :            : 
+       7                 :            : /**
+       8                 :            :  * @title RuleChainlinkPoRERC3643Ownable2Step
+       9                 :            :  * @notice {RuleChainlinkPoROwnable2Step} for **ERC-3643 tokens only**. Identical reserve logic; the sole difference is WHEN the
+      10                 :            :  * token reports the mint.
+      11                 :            :  *
+      12                 :            :  * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 /
+      13                 :            :  * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes
+      14                 :            :  * the new tokens. CMTAT calls the rule first and must use plain {RuleChainlinkPoROwnable2Step}.
+      15                 :            :  *
+      16                 :            :  * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The
+      17                 :            :  * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the reserves reported by the feed; this variant
+      18                 :            :  * on CMTAT ignores the pending amount and weakens enforcement.
+      19                 :            :  *
+      20                 :            :  * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643
+      21                 :            :  * calls `canTransfer` before `_mint`.
+      22                 :            :  */
+      23                 :            : contract RuleChainlinkPoRERC3643Ownable2Step is RuleChainlinkPoROwnable2Step {
+      24                 :            :     /*//////////////////////////////////////////////////////////////
+      25                 :            :                              CONSTRUCTOR
+      26                 :            :     //////////////////////////////////////////////////////////////*/
+      27                 :            : 
+      28                 :            :     /**
+      29                 :            :      * @param owner Contract owner.
+      30                 :            :      * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero).
+      31                 :            :      * @param tokenDecimals_ Decimals of that token (0 to 18, checked against `decimals()` when exposed).
+      32                 :            :      * @param reservesFeed_ Proof of Reserve data feed implementing `AggregatorV3Interface`.
+      33                 :            :      * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check.
+      34                 :            :      */
+      35                 :            :     constructor(
+      36                 :            :         address owner,
+      37                 :            :         address tokenContract_,
+      38                 :            :         uint8 tokenDecimals_,
+      39                 :            :         AggregatorV3Interface reservesFeed_,
+      40                 :            :         uint256 maxStalenessSeconds_
+      41                 :            :     ) RuleChainlinkPoROwnable2Step(owner, tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_) {}
+      42                 :            : 
+      43                 :            :     /*//////////////////////////////////////////////////////////////
+      44                 :            :                         INTERNAL FUNCTIONS
+      45                 :            :     //////////////////////////////////////////////////////////////*/
+      46                 :            : 
+      47                 :            :     /**
+      48                 :            :      * @notice Enforcement for a token that reports the mint after performing it.
+      49                 :            :      * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the
+      50                 :            :      * minted amount, so the comparison reduces to "is the post-mint supply within the reserves".
+      51                 :            :      * @param from Sender address; the zero address denotes the mint this rule gates.
+      52                 :            :      * @param to Recipient address.
+      53                 :            :      * @return The restriction code the write hook enforces.
+      54                 :            :      */
+      55                 :          2 :     function _detectTransferRestrictionOnNotify(
+      56                 :            :         address from,
+      57                 :            :         address to,
+      58                 :            :         uint256 /* value */
+      59                 :            :     )
+      60                 :            :         internal
+      61                 :            :         view
+      62                 :            :         virtual
+      63                 :            :         override
+      64                 :            :         returns (uint8)
+      65                 :            :     {
+      66                 :          2 :         return _detectTransferRestriction(from, to, 0);
+      67                 :            :     }
+      68                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.func-sort-c.html new file mode 100644 index 00000000..eea9c02d --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.func-sort-c.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoROwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoROwnable2Step.supportsInterface5
RuleChainlinkPoROwnable2Step._authorizeChainlinkPoRManager8
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.func.html new file mode 100644 index 00000000..cc7aacc8 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.func.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoROwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleChainlinkPoROwnable2Step._authorizeChainlinkPoRManager8
RuleChainlinkPoROwnable2Step.supportsInterface5
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.gcov.html new file mode 100644 index 00000000..dddadc09 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol.gcov.html @@ -0,0 +1,148 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleChainlinkPoROwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+       5                 :            : import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
+       6                 :            : import {Ownable2StepERC165Module} from "../../../modules/Ownable2StepERC165Module.sol";
+       7                 :            : import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol";
+       8                 :            : import {RuleChainlinkPoRBase} from "../abstract/base/RuleChainlinkPoRBase.sol";
+       9                 :            : import {RuleTransferValidation} from "../abstract/core/RuleTransferValidation.sol";
+      10                 :            : 
+      11                 :            : /**
+      12                 :            :  * @title RuleChainlinkPoROwnable2Step
+      13                 :            :  * @notice Ownable2Step variant of RuleChainlinkPoR.
+      14                 :            :  */
+      15                 :            : contract RuleChainlinkPoROwnable2Step is RuleChainlinkPoRBase, Ownable2Step, Ownable2StepERC165Module {
+      16                 :            :     /*//////////////////////////////////////////////////////////////
+      17                 :            :                              CONSTRUCTOR
+      18                 :            :     //////////////////////////////////////////////////////////////*/
+      19                 :            : 
+      20                 :            :     /**
+      21                 :            :      * @param owner Contract owner.
+      22                 :            :      * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero).
+      23                 :            :      * @param tokenDecimals_ Decimals of that token (0 to 18, checked against `decimals()` when exposed).
+      24                 :            :      * @param reservesFeed_ Proof of Reserve data feed implementing `AggregatorV3Interface`.
+      25                 :            :      * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check.
+      26                 :            :      */
+      27                 :            :     constructor(
+      28                 :            :         address owner,
+      29                 :            :         address tokenContract_,
+      30                 :            :         uint8 tokenDecimals_,
+      31                 :            :         AggregatorV3Interface reservesFeed_,
+      32                 :            :         uint256 maxStalenessSeconds_
+      33                 :            :     ) RuleChainlinkPoRBase(tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_) Ownable(owner) {}
+      34                 :            : 
+      35                 :            :     /*//////////////////////////////////////////////////////////////
+      36                 :            :                           PUBLIC FUNCTIONS
+      37                 :            :     //////////////////////////////////////////////////////////////*/
+      38                 :            : 
+      39                 :            :     /**
+      40                 :            :      * @notice Indicates whether this contract supports a given interface.
+      41                 :            :      * @param interfaceId The interface identifier, as specified in ERC-165.
+      42                 :            :      * @return True if the interface is supported.
+      43                 :            :      */
+      44                 :          5 :     function supportsInterface(bytes4 interfaceId)
+      45                 :            :         public
+      46                 :            :         view
+      47                 :            :         virtual
+      48                 :            :         override(RuleTransferValidation, Ownable2StepERC165Module)
+      49                 :            :         returns (bool)
+      50                 :            :     {
+      51                 :          5 :         return Ownable2StepERC165Module.supportsInterface(interfaceId)
+      52                 :          4 :             || RuleTransferValidation.supportsInterface(interfaceId);
+      53                 :            :     }
+      54                 :            : 
+      55                 :            :     /*//////////////////////////////////////////////////////////////
+      56                 :            :                             ACCESS CONTROL
+      57                 :            :     //////////////////////////////////////////////////////////////*/
+      58                 :            : 
+      59                 :            :     /**
+      60                 :            :      * @notice Restricts Proof of Reserve configuration to the contract owner.
+      61                 :            :      */
+      62                 :          8 :     function _authorizeChainlinkPoRManager() internal view virtual override onlyOwner {}
+      63                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func-sort-c.html index 055e2d8e..af2879ba 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleERC2980.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleERC2980.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -69,40 +69,40 @@ Hit count Sort by hit count - RuleERC2980._msgData + RuleERC2980._msgData 1 - RuleERC2980.supportsInterface + RuleERC2980.supportsInterface 1 - RuleERC2980._authorizeMintBurnManager + RuleERC2980._authorizeMintBurnManager 5 - RuleERC2980._authorizeFrozenlistRemove - 7 + RuleERC2980._authorizeFrozenlistRemove + 8 - RuleERC2980._authorizeWhitelistRemove - 8 + RuleERC2980._authorizeWhitelistRemove + 9 - RuleERC2980._authorizeFrozenlistAdd - 24 + RuleERC2980._authorizeFrozenlistAdd + 28 - RuleERC2980._authorizeWhitelistAdd - 48 + RuleERC2980._authorizeWhitelistAdd + 57 - RuleERC2980._msgSender - 259 + RuleERC2980._msgSender + 286 - RuleERC2980._contextSuffixLength - 260 + RuleERC2980._contextSuffixLength + 287
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func.html index 540a6a99..a2e4d5eb 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleERC2980.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleERC2980.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -69,39 +69,39 @@ Hit count Sort by hit count - RuleERC2980._authorizeFrozenlistAdd - 24 + RuleERC2980._authorizeFrozenlistAdd + 28 - RuleERC2980._authorizeFrozenlistRemove - 7 + RuleERC2980._authorizeFrozenlistRemove + 8 - RuleERC2980._authorizeMintBurnManager + RuleERC2980._authorizeMintBurnManager 5 - RuleERC2980._authorizeWhitelistAdd - 48 + RuleERC2980._authorizeWhitelistAdd + 57 - RuleERC2980._authorizeWhitelistRemove - 8 + RuleERC2980._authorizeWhitelistRemove + 9 - RuleERC2980._contextSuffixLength - 260 + RuleERC2980._contextSuffixLength + 287 - RuleERC2980._msgData + RuleERC2980._msgData 1 - RuleERC2980._msgSender - 259 + RuleERC2980._msgSender + 286 - RuleERC2980.supportsInterface + RuleERC2980.supportsInterface 1 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.gcov.html index 52866589..5ca4cfd5 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleERC2980.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleERC2980.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -81,116 +81,106 @@ 10 : : /** 11 : : * @title RuleERC2980 12 : : * @notice ERC-2980 Swiss Compliant transfer rule combining a whitelist and a frozenlist. - 13 : : * @dev - 14 : : * - Whitelist: only whitelisted addresses may receive tokens. - 15 : : * Senders do not need to be whitelisted. - 16 : : * - Frozenlist: frozen addresses are blocked from both sending and receiving. - 17 : : * Frozenlist check takes priority over the whitelist check. - 18 : : * - 19 : : * Access control uses {AccessControlModuleStandalone}: - 20 : : * - `WHITELIST_ADD_ROLE` — may add addresses to the whitelist. - 21 : : * - `WHITELIST_REMOVE_ROLE` — may remove addresses from the whitelist. - 22 : : * - `FROZENLIST_ADD_ROLE` — may add addresses to the frozenlist. - 23 : : * - `FROZENLIST_REMOVE_ROLE`— may remove addresses from the frozenlist. - 24 : : * - `DEFAULT_ADMIN_ROLE` — implicitly holds all roles. - 25 : : * - 26 : : * Restriction codes: - 27 : : * - 60: sender is frozen - 28 : : * - 61: recipient is frozen - 29 : : * - 62: spender is frozen - 30 : : * - 63: recipient is not whitelisted - 31 : : */ - 32 : : contract RuleERC2980 is RuleERC2980Base, AccessControlModuleStandalone { - 33 : : /*////////////////////////////////////////////////////////////// - 34 : : CONSTRUCTOR - 35 : : //////////////////////////////////////////////////////////////*/ + 13 : : * @dev Whitelist: only whitelisted addresses may **receive**; senders need not be listed. + 14 : : * Frozenlist: frozen addresses may neither send nor receive, and it takes priority over the + 15 : : * whitelist. Codes 60 (sender frozen), 61 (recipient frozen), 62 (spender frozen), 63 (recipient not + 16 : : * whitelisted). + 17 : : * + 18 : : * @dev Access control via {AccessControlModuleStandalone}: `WHITELIST_ADD_ROLE`, + 19 : : * `WHITELIST_REMOVE_ROLE`, `FROZENLIST_ADD_ROLE`, `FROZENLIST_REMOVE_ROLE`, with + 20 : : * `DEFAULT_ADMIN_ROLE` implicitly holding all of them. + 21 : : */ + 22 : : contract RuleERC2980 is RuleERC2980Base, AccessControlModuleStandalone { + 23 : : /*////////////////////////////////////////////////////////////// + 24 : : CONSTRUCTOR + 25 : : //////////////////////////////////////////////////////////////*/ + 26 : : + 27 : : /** + 28 : : * @param admin Address that receives `DEFAULT_ADMIN_ROLE` (implicitly holds all roles). + 29 : : * @param forwarderIrrevocable Address of the ERC-2771 forwarder for meta-transactions. + 30 : : * @param allowMintBurn When true, permits both minting and burning (sets `allowMint` and `allowBurn`). + 31 : : */ + 32 : : constructor(address admin, address forwarderIrrevocable, bool allowMintBurn) + 33 : : RuleERC2980Base(forwarderIrrevocable, allowMintBurn) + 34 : : AccessControlModuleStandalone(admin) + 35 : : {} 36 : : - 37 : : /** - 38 : : * @param admin Address that receives `DEFAULT_ADMIN_ROLE` (implicitly holds all roles). - 39 : : * @param forwarderIrrevocable Address of the ERC-2771 forwarder for meta-transactions. - 40 : : * @param allowMintBurn When true, permits both minting and burning (sets `allowMint` and `allowBurn`). - 41 : : */ - 42 : : constructor(address admin, address forwarderIrrevocable, bool allowMintBurn) - 43 : : RuleERC2980Base(forwarderIrrevocable, allowMintBurn) - 44 : : AccessControlModuleStandalone(admin) - 45 : : {} - 46 : : - 47 : : /*////////////////////////////////////////////////////////////// - 48 : : PUBLIC FUNCTIONS - 49 : : //////////////////////////////////////////////////////////////*/ - 50 : : - 51 : : /** - 52 : : * @notice Indicates whether this contract supports a given interface. - 53 : : * @param interfaceId The interface identifier, as specified in ERC-165. - 54 : : * @return True if the interface is supported. - 55 : : */ - 56 : 1 : function supportsInterface(bytes4 interfaceId) - 57 : : public - 58 : : view - 59 : : virtual - 60 : : override(AccessControlEnumerable, RuleERC2980Base) - 61 : : returns (bool) - 62 : : { - 63 : 1 : return AccessControlEnumerable.supportsInterface(interfaceId) || RuleERC2980Base.supportsInterface(interfaceId); - 64 : : } - 65 : : - 66 : : /*////////////////////////////////////////////////////////////// - 67 : : ACCESS CONTROL - 68 : : //////////////////////////////////////////////////////////////*/ + 37 : : /*////////////////////////////////////////////////////////////// + 38 : : PUBLIC FUNCTIONS + 39 : : //////////////////////////////////////////////////////////////*/ + 40 : : + 41 : : /** + 42 : : * @notice Indicates whether this contract supports a given interface. + 43 : : * @param interfaceId The interface identifier, as specified in ERC-165. + 44 : : * @return True if the interface is supported. + 45 : : */ + 46 : 1 : function supportsInterface(bytes4 interfaceId) + 47 : : public + 48 : : view + 49 : : virtual + 50 : : override(AccessControlEnumerable, RuleERC2980Base) + 51 : : returns (bool) + 52 : : { + 53 : 1 : return AccessControlEnumerable.supportsInterface(interfaceId) || RuleERC2980Base.supportsInterface(interfaceId); + 54 : : } + 55 : : + 56 : : /*////////////////////////////////////////////////////////////// + 57 : : ACCESS CONTROL + 58 : : //////////////////////////////////////////////////////////////*/ + 59 : : + 60 : : /** + 61 : : * @notice Restricts toggling `allowMint` / `allowBurn` to holders of DEFAULT_ADMIN_ROLE. + 62 : : */ + 63 : 5 : function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 64 : : + 65 : : /** + 66 : : * @notice Restricts adding addresses to the whitelist to holders of WHITELIST_ADD_ROLE. + 67 : : */ + 68 : 57 : function _authorizeWhitelistAdd() internal view virtual override onlyRole(WHITELIST_ADD_ROLE) {} 69 : : 70 : : /** - 71 : : * @notice Restricts adding addresses to the whitelist to holders of WHITELIST_ADD_ROLE. + 71 : : * @notice Restricts removing addresses from the whitelist to holders of WHITELIST_REMOVE_ROLE. 72 : : */ - 73 : : /** - 74 : : * @notice Restricts toggling `allowMint` / `allowBurn` to holders of DEFAULT_ADMIN_ROLE. - 75 : : */ - 76 : 5 : function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} - 77 : : - 78 : 48 : function _authorizeWhitelistAdd() internal view virtual override onlyRole(WHITELIST_ADD_ROLE) {} + 73 : 9 : function _authorizeWhitelistRemove() internal view virtual override onlyRole(WHITELIST_REMOVE_ROLE) {} + 74 : : + 75 : : /** + 76 : : * @notice Restricts adding addresses to the frozenlist to holders of FROZENLIST_ADD_ROLE. + 77 : : */ + 78 : 28 : function _authorizeFrozenlistAdd() internal view virtual override onlyRole(FROZENLIST_ADD_ROLE) {} 79 : : 80 : : /** - 81 : : * @notice Restricts removing addresses from the whitelist to holders of WHITELIST_REMOVE_ROLE. + 81 : : * @notice Restricts removing addresses from the frozenlist to holders of FROZENLIST_REMOVE_ROLE. 82 : : */ - 83 : 8 : function _authorizeWhitelistRemove() internal view virtual override onlyRole(WHITELIST_REMOVE_ROLE) {} + 83 : 8 : function _authorizeFrozenlistRemove() internal view virtual override onlyRole(FROZENLIST_REMOVE_ROLE) {} 84 : : - 85 : : /** - 86 : : * @notice Restricts adding addresses to the frozenlist to holders of FROZENLIST_ADD_ROLE. - 87 : : */ - 88 : 24 : function _authorizeFrozenlistAdd() internal view virtual override onlyRole(FROZENLIST_ADD_ROLE) {} - 89 : : - 90 : : /** - 91 : : * @notice Restricts removing addresses from the frozenlist to holders of FROZENLIST_REMOVE_ROLE. + 85 : : /*////////////////////////////////////////////////////////////// + 86 : : INTERNAL FUNCTIONS + 87 : : //////////////////////////////////////////////////////////////*/ + 88 : : + 89 : : /** + 90 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. + 91 : : * @return sender The address of the message sender. 92 : : */ - 93 : 7 : function _authorizeFrozenlistRemove() internal view virtual override onlyRole(FROZENLIST_REMOVE_ROLE) {} - 94 : : - 95 : : /*////////////////////////////////////////////////////////////// - 96 : : INTERNAL FUNCTIONS - 97 : : //////////////////////////////////////////////////////////////*/ - 98 : : - 99 : : /** - 100 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. - 101 : : * @return sender The address of the message sender. - 102 : : */ - 103 : 259 : function _msgSender() internal view virtual override(Context, RuleERC2980Base) returns (address sender) { - 104 : 259 : return super._msgSender(); - 105 : : } - 106 : : - 107 : : /** - 108 : : * @notice Returns the message calldata, accounting for meta-transaction (ERC-2771) context. - 109 : : * @return The message calldata. - 110 : : */ - 111 : 1 : function _msgData() internal view virtual override(Context, RuleERC2980Base) returns (bytes calldata) { - 112 : 1 : return super._msgData(); - 113 : : } - 114 : : - 115 : : /** - 116 : : * @notice Returns the length of the context suffix appended by the forwarder. - 117 : : * @return The context suffix length in bytes. - 118 : : */ - 119 : 260 : function _contextSuffixLength() internal view virtual override(Context, RuleERC2980Base) returns (uint256) { - 120 : 260 : return super._contextSuffixLength(); - 121 : : } - 122 : : } + 93 : 286 : function _msgSender() internal view virtual override(Context, RuleERC2980Base) returns (address sender) { + 94 : 286 : return super._msgSender(); + 95 : : } + 96 : : + 97 : : /** + 98 : : * @notice Returns the message calldata, accounting for meta-transaction (ERC-2771) context. + 99 : : * @return The message calldata. + 100 : : */ + 101 : 1 : function _msgData() internal view virtual override(Context, RuleERC2980Base) returns (bytes calldata) { + 102 : 1 : return super._msgData(); + 103 : : } + 104 : : + 105 : : /** + 106 : : * @notice Returns the length of the context suffix appended by the forwarder. + 107 : : * @return The context suffix length in bytes. + 108 : : */ + 109 : 287 : function _contextSuffixLength() internal view virtual override(Context, RuleERC2980Base) returns (uint256) { + 110 : 287 : return super._contextSuffixLength(); + 111 : : } + 112 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func-sort-c.html index 3a8b6d3f..8c351b61 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleERC2980Ownable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleERC2980Ownable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -77,7 +77,7 @@ 2 - RuleERC2980Ownable2Step._authorizeMintBurnManager + RuleERC2980Ownable2Step._authorizeMintBurnManager 3 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func.html index 4ec630f7..0e3a3703 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleERC2980Ownable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleERC2980Ownable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -77,7 +77,7 @@ 2 - RuleERC2980Ownable2Step._authorizeMintBurnManager + RuleERC2980Ownable2Step._authorizeMintBurnManager 3 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.gcov.html index e4524c2b..e5c05a5f 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleERC2980Ownable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleERC2980Ownable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleERC2980Ownable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 9 @@ -122,13 +122,13 @@ 51 : : //////////////////////////////////////////////////////////////*/ 52 : : 53 : : /** - 54 : : * @notice Restricts adding addresses to the whitelist to the contract owner. + 54 : : * @notice Restricts toggling `allowMint` / `allowBurn` to the contract owner. 55 : : */ - 56 : : /** - 57 : : * @notice Restricts toggling `allowMint` / `allowBurn` to the contract owner. - 58 : : */ - 59 : 3 : function _authorizeMintBurnManager() internal view virtual override onlyOwner {} - 60 : : + 56 : 3 : function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + 57 : : + 58 : : /** + 59 : : * @notice Restricts adding addresses to the whitelist to the contract owner. + 60 : : */ 61 : 7 : function _authorizeWhitelistAdd() internal view virtual override onlyOwner {} 62 : : 63 : : /** diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func-sort-c.html index 36898ac8..2809cb12 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleIdentityRegistry.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleIdentityRegistry.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -69,12 +69,12 @@ Hit count Sort by hit count - RuleIdentityRegistry._authorizeIdentityRegistryManager - 12 + RuleIdentityRegistry._authorizeIdentityRegistryManager + 14 - RuleIdentityRegistry.supportsInterface - 27 + RuleIdentityRegistry.supportsInterface + 66
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func.html index 807e4c0e..09383059 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleIdentityRegistry.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleIdentityRegistry.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -69,12 +69,12 @@ Hit count Sort by hit count - RuleIdentityRegistry._authorizeIdentityRegistryManager - 12 + RuleIdentityRegistry._authorizeIdentityRegistryManager + 14 - RuleIdentityRegistry.supportsInterface - 27 + RuleIdentityRegistry.supportsInterface + 66
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.gcov.html index 1bda4e15..f6454a64 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistry.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleIdentityRegistry.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleIdentityRegistry.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -89,43 +89,50 @@ 18 : : 19 : : /** 20 : : * @notice Deploys the rule, sets the admin and the ERC-3643 identity registry. - 21 : : * @param admin Address that receives the default admin role. - 22 : : * @param identityRegistry_ Address of the ERC-3643 identity registry to query. - 23 : : */ - 24 : : constructor(address admin, address identityRegistry_, bool checkSender_, bool checkSpender_) - 25 : : AccessControlModuleStandalone(admin) - 26 : : RuleIdentityRegistryBase(identityRegistry_, checkSender_, checkSpender_) - 27 : : {} - 28 : : - 29 : : /*////////////////////////////////////////////////////////////// - 30 : : PUBLIC FUNCTIONS - 31 : : //////////////////////////////////////////////////////////////*/ - 32 : : - 33 : : /** - 34 : : * @notice Indicates whether this contract supports a given interface. - 35 : : * @param interfaceId The interface identifier, as specified in ERC-165. - 36 : : * @return True if the interface is supported. - 37 : : */ - 38 : 27 : function supportsInterface(bytes4 interfaceId) - 39 : : public - 40 : : view - 41 : : virtual - 42 : : override(AccessControlEnumerable, RuleTransferValidation) - 43 : : returns (bool) - 44 : : { - 45 : 27 : return AccessControlEnumerable.supportsInterface(interfaceId) - 46 : 18 : || RuleTransferValidation.supportsInterface(interfaceId); - 47 : : } - 48 : : - 49 : : /*////////////////////////////////////////////////////////////// - 50 : : ACCESS CONTROL - 51 : : //////////////////////////////////////////////////////////////*/ - 52 : : - 53 : : /** - 54 : : * @notice Restricts identity registry management to holders of DEFAULT_ADMIN_ROLE. - 55 : : */ - 56 : 12 : function _authorizeIdentityRegistryManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} - 57 : : } + 21 : : * @dev Pass `false, false` for the ERC-3643-conformant default: the spec requires only the + 22 : : * RECEIVER to be verified. The two flags below are stricter-than-spec opt-ins. + 23 : : * @param admin Address that receives the default admin role. + 24 : : * @param identityRegistry_ Address of the ERC-3643 identity registry to query. + 25 : : * @param checkSender_ When true, also require the sender to be verified. Stricter than + 26 : : * ERC-3643, and it traps de-listed holders: a holder whose identity lapses can no + 27 : : * longer exit their position. Defaults to false. + 28 : : * @param checkSpender_ When true, also require the `transferFrom` spender to be verified. + 29 : : * Mint and burn stay exempt from this check. Defaults to false. + 30 : : */ + 31 : : constructor(address admin, address identityRegistry_, bool checkSender_, bool checkSpender_) + 32 : : AccessControlModuleStandalone(admin) + 33 : : RuleIdentityRegistryBase(identityRegistry_, checkSender_, checkSpender_) + 34 : : {} + 35 : : + 36 : : /*////////////////////////////////////////////////////////////// + 37 : : PUBLIC FUNCTIONS + 38 : : //////////////////////////////////////////////////////////////*/ + 39 : : + 40 : : /** + 41 : : * @notice Indicates whether this contract supports a given interface. + 42 : : * @param interfaceId The interface identifier, as specified in ERC-165. + 43 : : * @return True if the interface is supported. + 44 : : */ + 45 : 66 : function supportsInterface(bytes4 interfaceId) + 46 : : public + 47 : : view + 48 : : virtual + 49 : : override(AccessControlEnumerable, RuleTransferValidation) + 50 : : returns (bool) + 51 : : { + 52 : 66 : return AccessControlEnumerable.supportsInterface(interfaceId) + 53 : 44 : || RuleTransferValidation.supportsInterface(interfaceId); + 54 : : } + 55 : : + 56 : : /*////////////////////////////////////////////////////////////// + 57 : : ACCESS CONTROL + 58 : : //////////////////////////////////////////////////////////////*/ + 59 : : + 60 : : /** + 61 : : * @notice Restricts identity registry management to holders of DEFAULT_ADMIN_ROLE. + 62 : : */ + 63 : 14 : function _authorizeIdentityRegistryManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 64 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func-sort-c.html index 769a213b..ef63237e 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -69,11 +69,11 @@ Hit count Sort by hit count - RuleIdentityRegistryOwnable2Step._authorizeIdentityRegistryManager + RuleIdentityRegistryOwnable2Step._authorizeIdentityRegistryManager 4 - RuleIdentityRegistryOwnable2Step.supportsInterface + RuleIdentityRegistryOwnable2Step.supportsInterface 5 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func.html index 311468c2..6f9d5154 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -69,11 +69,11 @@ Hit count Sort by hit count - RuleIdentityRegistryOwnable2Step._authorizeIdentityRegistryManager + RuleIdentityRegistryOwnable2Step._authorizeIdentityRegistryManager 4 - RuleIdentityRegistryOwnable2Step.supportsInterface + RuleIdentityRegistryOwnable2Step.supportsInterface 5 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.gcov.html index dbf90759..a886a9dc 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -89,43 +89,50 @@ 18 : : 19 : : /** 20 : : * @notice Deploys the rule, sets the owner and the ERC-3643 identity registry. - 21 : : * @param owner Contract owner. - 22 : : * @param identityRegistry_ Address of the ERC-3643 identity registry to query. - 23 : : */ - 24 : : constructor(address owner, address identityRegistry_, bool checkSender_, bool checkSpender_) - 25 : : RuleIdentityRegistryBase(identityRegistry_, checkSender_, checkSpender_) - 26 : : Ownable(owner) - 27 : : {} - 28 : : - 29 : : /*////////////////////////////////////////////////////////////// - 30 : : PUBLIC FUNCTIONS - 31 : : //////////////////////////////////////////////////////////////*/ - 32 : : - 33 : : /** - 34 : : * @notice Indicates whether this contract supports a given interface. - 35 : : * @param interfaceId The interface identifier, as specified in ERC-165. - 36 : : * @return True if the interface is supported. - 37 : : */ - 38 : 5 : function supportsInterface(bytes4 interfaceId) - 39 : : public - 40 : : view - 41 : : virtual - 42 : : override(RuleTransferValidation, Ownable2StepERC165Module) - 43 : : returns (bool) - 44 : : { - 45 : 5 : return Ownable2StepERC165Module.supportsInterface(interfaceId) - 46 : 2 : || RuleTransferValidation.supportsInterface(interfaceId); - 47 : : } - 48 : : - 49 : : /*////////////////////////////////////////////////////////////// - 50 : : ACCESS CONTROL - 51 : : //////////////////////////////////////////////////////////////*/ - 52 : : - 53 : : /** - 54 : : * @notice Restricts identity registry management to the contract owner. - 55 : : */ - 56 : 4 : function _authorizeIdentityRegistryManager() internal view virtual override onlyOwner {} - 57 : : } + 21 : : * @dev Pass `false, false` for the ERC-3643-conformant default: the spec requires only the + 22 : : * RECEIVER to be verified. The two flags below are stricter-than-spec opt-ins. + 23 : : * @param owner Contract owner. + 24 : : * @param identityRegistry_ Address of the ERC-3643 identity registry to query. + 25 : : * @param checkSender_ When true, also require the sender to be verified. Stricter than + 26 : : * ERC-3643, and it traps de-listed holders: a holder whose identity lapses can no + 27 : : * longer exit their position. Defaults to false. + 28 : : * @param checkSpender_ When true, also require the `transferFrom` spender to be verified. + 29 : : * Mint and burn stay exempt from this check. Defaults to false. + 30 : : */ + 31 : : constructor(address owner, address identityRegistry_, bool checkSender_, bool checkSpender_) + 32 : : RuleIdentityRegistryBase(identityRegistry_, checkSender_, checkSpender_) + 33 : : Ownable(owner) + 34 : : {} + 35 : : + 36 : : /*////////////////////////////////////////////////////////////// + 37 : : PUBLIC FUNCTIONS + 38 : : //////////////////////////////////////////////////////////////*/ + 39 : : + 40 : : /** + 41 : : * @notice Indicates whether this contract supports a given interface. + 42 : : * @param interfaceId The interface identifier, as specified in ERC-165. + 43 : : * @return True if the interface is supported. + 44 : : */ + 45 : 5 : function supportsInterface(bytes4 interfaceId) + 46 : : public + 47 : : view + 48 : : virtual + 49 : : override(RuleTransferValidation, Ownable2StepERC165Module) + 50 : : returns (bool) + 51 : : { + 52 : 5 : return Ownable2StepERC165Module.supportsInterface(interfaceId) + 53 : 2 : || RuleTransferValidation.supportsInterface(interfaceId); + 54 : : } + 55 : : + 56 : : /*////////////////////////////////////////////////////////////// + 57 : : ACCESS CONTROL + 58 : : //////////////////////////////////////////////////////////////*/ + 59 : : + 60 : : /** + 61 : : * @notice Restricts identity registry management to the contract owner. + 62 : : */ + 63 : 4 : function _authorizeIdentityRegistryManager() internal view virtual override onlyOwner {} + 64 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.func-sort-c.html new file mode 100644 index 00000000..1908cefe --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.func-sort-c.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxBalance.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxBalance.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxBalance._authorizeMaxBalanceManager23
RuleMaxBalance.supportsInterface24
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.func.html new file mode 100644 index 00000000..6d4bbfbc --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.func.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxBalance.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxBalance.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxBalance._authorizeMaxBalanceManager23
RuleMaxBalance.supportsInterface24
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.gcov.html new file mode 100644 index 00000000..d4bcc219 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalance.sol.gcov.html @@ -0,0 +1,144 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxBalance.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxBalance.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
+       5                 :            : import {AccessControlModuleStandalone} from "../../../modules/AccessControlModuleStandalone.sol";
+       6                 :            : import {RuleMaxBalanceBase} from "../abstract/base/RuleMaxBalanceBase.sol";
+       7                 :            : import {RuleTransferValidation} from "../abstract/core/RuleTransferValidation.sol";
+       8                 :            : 
+       9                 :            : /**
+      10                 :            :  * @title RuleMaxBalance
+      11                 :            :  * @notice Caps how many tokens a single address may hold, with an operator-managed exemption list.
+      12                 :            :  * @dev WARNING: pair this with a rule that admits one address per investor (`RuleWhitelist`,
+      13                 :            :  * `RuleReceiverWhitelist` or `RuleIdentityRegistry`). The cap counts tokens per address, so a holder
+      14                 :            :  * with several addresses can otherwise exceed it.
+      15                 :            :  */
+      16                 :            : contract RuleMaxBalance is AccessControlModuleStandalone, RuleMaxBalanceBase {
+      17                 :            :     /*//////////////////////////////////////////////////////////////
+      18                 :            :                              CONSTRUCTOR
+      19                 :            :     //////////////////////////////////////////////////////////////*/
+      20                 :            : 
+      21                 :            :     /**
+      22                 :            :      * @param admin Address that receives the default admin role.
+      23                 :            :      * @param balanceToken_ Token contract that exposes `balanceOf` (must be a contract).
+      24                 :            :      * @param maxBalance_ Initial maximum balance per non-exempt address.
+      25                 :            :      */
+      26                 :            :     constructor(address admin, address balanceToken_, uint256 maxBalance_)
+      27                 :            :         AccessControlModuleStandalone(admin)
+      28                 :            :         RuleMaxBalanceBase(balanceToken_, maxBalance_)
+      29                 :            :     {}
+      30                 :            : 
+      31                 :            :     /*//////////////////////////////////////////////////////////////
+      32                 :            :                           PUBLIC FUNCTIONS
+      33                 :            :     //////////////////////////////////////////////////////////////*/
+      34                 :            : 
+      35                 :            :     /**
+      36                 :            :      * @notice Indicates whether this contract supports a given interface.
+      37                 :            :      * @param interfaceId The interface identifier, as specified in ERC-165.
+      38                 :            :      * @return True if the interface is supported.
+      39                 :            :      */
+      40                 :         24 :     function supportsInterface(bytes4 interfaceId)
+      41                 :            :         public
+      42                 :            :         view
+      43                 :            :         virtual
+      44                 :            :         override(AccessControlEnumerable, RuleTransferValidation)
+      45                 :            :         returns (bool)
+      46                 :            :     {
+      47                 :         24 :         return AccessControlEnumerable.supportsInterface(interfaceId)
+      48                 :         16 :             || RuleTransferValidation.supportsInterface(interfaceId);
+      49                 :            :     }
+      50                 :            : 
+      51                 :            :     /*//////////////////////////////////////////////////////////////
+      52                 :            :                             ACCESS CONTROL
+      53                 :            :     //////////////////////////////////////////////////////////////*/
+      54                 :            : 
+      55                 :            :     /**
+      56                 :            :      * @notice Restricts cap, token and exemption management to MAX_BALANCE_ROLE.
+      57                 :            :      */
+      58                 :         23 :     function _authorizeMaxBalanceManager() internal view virtual override onlyRole(MAX_BALANCE_ROLE) {}
+      59                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.func-sort-c.html new file mode 100644 index 00000000..1d863263 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.func-sort-c.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxBalanceOwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxBalanceOwnable2Step.supportsInterface3
RuleMaxBalanceOwnable2Step._authorizeMaxBalanceManager9
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.func.html new file mode 100644 index 00000000..9090e697 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.func.html @@ -0,0 +1,89 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxBalanceOwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxBalanceOwnable2Step._authorizeMaxBalanceManager9
RuleMaxBalanceOwnable2Step.supportsInterface3
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.gcov.html new file mode 100644 index 00000000..516a3022 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol.gcov.html @@ -0,0 +1,145 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxBalanceOwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:44100.0 %
Date:2026-08-19 15:38:25Functions:22100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+       5                 :            : import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
+       6                 :            : import {Ownable2StepERC165Module} from "../../../modules/Ownable2StepERC165Module.sol";
+       7                 :            : import {RuleTransferValidation} from "../abstract/core/RuleTransferValidation.sol";
+       8                 :            : import {RuleMaxBalanceBase} from "../abstract/base/RuleMaxBalanceBase.sol";
+       9                 :            : 
+      10                 :            : /**
+      11                 :            :  * @title RuleMaxBalanceOwnable2Step
+      12                 :            :  * @notice Ownable2Step variant of RuleMaxBalance.
+      13                 :            :  * @dev WARNING: pair this with a rule that admits one address per investor. The cap counts tokens per
+      14                 :            :  * address, so a holder with several addresses can otherwise exceed it.
+      15                 :            :  */
+      16                 :            : contract RuleMaxBalanceOwnable2Step is RuleMaxBalanceBase, Ownable2Step, Ownable2StepERC165Module {
+      17                 :            :     /*//////////////////////////////////////////////////////////////
+      18                 :            :                              CONSTRUCTOR
+      19                 :            :     //////////////////////////////////////////////////////////////*/
+      20                 :            : 
+      21                 :            :     /**
+      22                 :            :      * @notice Deploys the rule, sets the owner, the observed token and the initial cap.
+      23                 :            :      * @param owner Contract owner.
+      24                 :            :      * @param balanceToken_ Token contract that exposes `balanceOf` (must be a contract).
+      25                 :            :      * @param maxBalance_ Initial maximum balance per non-exempt address.
+      26                 :            :      */
+      27                 :            :     constructor(address owner, address balanceToken_, uint256 maxBalance_)
+      28                 :            :         RuleMaxBalanceBase(balanceToken_, maxBalance_)
+      29                 :            :         Ownable(owner)
+      30                 :            :     {}
+      31                 :            : 
+      32                 :            :     /*//////////////////////////////////////////////////////////////
+      33                 :            :                           PUBLIC FUNCTIONS
+      34                 :            :     //////////////////////////////////////////////////////////////*/
+      35                 :            : 
+      36                 :            :     /**
+      37                 :            :      * @notice Indicates whether this contract supports a given interface.
+      38                 :            :      * @param interfaceId The interface identifier, as specified in ERC-165.
+      39                 :            :      * @return True if the interface is supported.
+      40                 :            :      */
+      41                 :          3 :     function supportsInterface(bytes4 interfaceId)
+      42                 :            :         public
+      43                 :            :         view
+      44                 :            :         virtual
+      45                 :            :         override(RuleTransferValidation, Ownable2StepERC165Module)
+      46                 :            :         returns (bool)
+      47                 :            :     {
+      48                 :          3 :         return Ownable2StepERC165Module.supportsInterface(interfaceId)
+      49                 :          2 :             || RuleTransferValidation.supportsInterface(interfaceId);
+      50                 :            :     }
+      51                 :            : 
+      52                 :            :     /*//////////////////////////////////////////////////////////////
+      53                 :            :                             ACCESS CONTROL
+      54                 :            :     //////////////////////////////////////////////////////////////*/
+      55                 :            : 
+      56                 :            :     /**
+      57                 :            :      * @notice Restricts cap, token and exemption management to the contract owner.
+      58                 :            :      */
+      59                 :          9 :     function _authorizeMaxBalanceManager() internal view virtual override onlyOwner {}
+      60                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func-sort-c.html index 6e43d3fe..452a7909 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleMaxTotalSupply.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupply.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -70,11 +70,11 @@ RuleMaxTotalSupply.supportsInterface - 19 + 79 RuleMaxTotalSupply._authorizeMaxTotalSupplyManager - 260 + 266
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func.html index fc1fbc3a..40747121 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleMaxTotalSupply.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupply.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -70,11 +70,11 @@ RuleMaxTotalSupply._authorizeMaxTotalSupplyManager - 260 + 266 RuleMaxTotalSupply.supportsInterface - 19 + 79
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.gcov.html index 5f224021..6bcc7083 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupply.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleMaxTotalSupply.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupply.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -105,15 +105,15 @@ 34 : : * @param interfaceId The interface identifier, as specified in ERC-165. 35 : : * @return True if the interface is supported. 36 : : */ - 37 : 19 : function supportsInterface(bytes4 interfaceId) + 37 : 79 : function supportsInterface(bytes4 interfaceId) 38 : : public 39 : : view 40 : : virtual 41 : : override(AccessControlEnumerable, RuleTransferValidation) 42 : : returns (bool) 43 : : { - 44 : 19 : return AccessControlEnumerable.supportsInterface(interfaceId) - 45 : 13 : || RuleTransferValidation.supportsInterface(interfaceId); + 44 : 79 : return AccessControlEnumerable.supportsInterface(interfaceId) + 45 : 53 : || RuleTransferValidation.supportsInterface(interfaceId); 46 : : } 47 : : 48 : : /*////////////////////////////////////////////////////////////// @@ -123,7 +123,7 @@ 52 : : /** 53 : : * @notice Restricts maximum total supply management to holders of DEFAULT_ADMIN_ROLE. 54 : : */ - 55 : 260 : function _authorizeMaxTotalSupplyManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 55 : 266 : function _authorizeMaxTotalSupplyManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} 56 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.func-sort-c.html new file mode 100644 index 00000000..86e8af65 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.func-sort-c.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxTotalSupplyERC3643.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxTotalSupplyERC3643._detectTransferRestrictionOnNotify10
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.func.html new file mode 100644 index 00000000..94b7ce34 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.func.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxTotalSupplyERC3643.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxTotalSupplyERC3643._detectTransferRestrictionOnNotify10
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.gcov.html new file mode 100644 index 00000000..1f6eae03 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol.gcov.html @@ -0,0 +1,146 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxTotalSupplyERC3643.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleMaxTotalSupply} from "./RuleMaxTotalSupply.sol";
+       5                 :            : 
+       6                 :            : /**
+       7                 :            :  * @title RuleMaxTotalSupplyERC3643
+       8                 :            :  * @notice {RuleMaxTotalSupply} for **ERC-3643 tokens only**. Identical supply-cap logic; the sole difference is WHEN the
+       9                 :            :  * token reports the mint.
+      10                 :            :  *
+      11                 :            :  * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 /
+      12                 :            :  * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes
+      13                 :            :  * the new tokens. CMTAT calls the rule first and must use plain {RuleMaxTotalSupply}.
+      14                 :            :  *
+      15                 :            :  * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The
+      16                 :            :  * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the configured ceiling; this variant
+      17                 :            :  * on CMTAT ignores the pending amount and weakens enforcement.
+      18                 :            :  *
+      19                 :            :  * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643
+      20                 :            :  * calls `canTransfer` before `_mint`.
+      21                 :            :  */
+      22                 :            : contract RuleMaxTotalSupplyERC3643 is RuleMaxTotalSupply {
+      23                 :            :     /*//////////////////////////////////////////////////////////////
+      24                 :            :                              CONSTRUCTOR
+      25                 :            :     //////////////////////////////////////////////////////////////*/
+      26                 :            : 
+      27                 :            :     /**
+      28                 :            :      * @param admin Address that receives the default admin role.
+      29                 :            :      * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero).
+      30                 :            :      * @param maxTotalSupply_ Initial maximum supply.
+      31                 :            :      */
+      32                 :            :     constructor(address admin, address tokenContract_, uint256 maxTotalSupply_)
+      33                 :            :         RuleMaxTotalSupply(admin, tokenContract_, maxTotalSupply_)
+      34                 :            :     {}
+      35                 :            : 
+      36                 :            :     /*//////////////////////////////////////////////////////////////
+      37                 :            :                         INTERNAL FUNCTIONS
+      38                 :            :     //////////////////////////////////////////////////////////////*/
+      39                 :            : 
+      40                 :            :     /**
+      41                 :            :      * @notice Enforcement for a token that reports the mint after performing it.
+      42                 :            :      * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the
+      43                 :            :      * minted amount, so the comparison reduces to "is the post-mint supply within the ceiling".
+      44                 :            :      * @param from Sender address; the zero address denotes the mint this rule gates.
+      45                 :            :      * @param to Recipient address.
+      46                 :            :      * @return The restriction code the write hook enforces.
+      47                 :            :      */
+      48                 :         10 :     function _detectTransferRestrictionOnNotify(
+      49                 :            :         address from,
+      50                 :            :         address to,
+      51                 :            :         uint256 /* value */
+      52                 :            :     )
+      53                 :            :         internal
+      54                 :            :         view
+      55                 :            :         virtual
+      56                 :            :         override
+      57                 :            :         returns (uint8)
+      58                 :            :     {
+      59                 :         10 :         return _detectTransferRestriction(from, to, 0);
+      60                 :            :     }
+      61                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.func-sort-c.html new file mode 100644 index 00000000..111510bb --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.func-sort-c.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxTotalSupplyERC3643Ownable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxTotalSupplyERC3643Ownable2Step._detectTransferRestrictionOnNotify2
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.func.html new file mode 100644 index 00000000..57bebdbf --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.func.html @@ -0,0 +1,85 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxTotalSupplyERC3643Ownable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleMaxTotalSupplyERC3643Ownable2Step._detectTransferRestrictionOnNotify2
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.gcov.html new file mode 100644 index 00000000..4873de5e --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol.gcov.html @@ -0,0 +1,146 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleMaxTotalSupplyERC3643Ownable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:22100.0 %
Date:2026-08-19 15:38:25Functions:11100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {RuleMaxTotalSupplyOwnable2Step} from "./RuleMaxTotalSupplyOwnable2Step.sol";
+       5                 :            : 
+       6                 :            : /**
+       7                 :            :  * @title RuleMaxTotalSupplyERC3643Ownable2Step
+       8                 :            :  * @notice {RuleMaxTotalSupplyOwnable2Step} for **ERC-3643 tokens only**. Identical supply-cap logic; the sole difference is WHEN the
+       9                 :            :  * token reports the mint.
+      10                 :            :  *
+      11                 :            :  * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 /
+      12                 :            :  * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes
+      13                 :            :  * the new tokens. CMTAT calls the rule first and must use plain {RuleMaxTotalSupplyOwnable2Step}.
+      14                 :            :  *
+      15                 :            :  * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The
+      16                 :            :  * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the configured ceiling; this variant
+      17                 :            :  * on CMTAT ignores the pending amount and weakens enforcement.
+      18                 :            :  *
+      19                 :            :  * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643
+      20                 :            :  * calls `canTransfer` before `_mint`.
+      21                 :            :  */
+      22                 :            : contract RuleMaxTotalSupplyERC3643Ownable2Step is RuleMaxTotalSupplyOwnable2Step {
+      23                 :            :     /*//////////////////////////////////////////////////////////////
+      24                 :            :                              CONSTRUCTOR
+      25                 :            :     //////////////////////////////////////////////////////////////*/
+      26                 :            : 
+      27                 :            :     /**
+      28                 :            :      * @param owner Contract owner.
+      29                 :            :      * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero).
+      30                 :            :      * @param maxTotalSupply_ Initial maximum supply.
+      31                 :            :      */
+      32                 :            :     constructor(address owner, address tokenContract_, uint256 maxTotalSupply_)
+      33                 :            :         RuleMaxTotalSupplyOwnable2Step(owner, tokenContract_, maxTotalSupply_)
+      34                 :            :     {}
+      35                 :            : 
+      36                 :            :     /*//////////////////////////////////////////////////////////////
+      37                 :            :                         INTERNAL FUNCTIONS
+      38                 :            :     //////////////////////////////////////////////////////////////*/
+      39                 :            : 
+      40                 :            :     /**
+      41                 :            :      * @notice Enforcement for a token that reports the mint after performing it.
+      42                 :            :      * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the
+      43                 :            :      * minted amount, so the comparison reduces to "is the post-mint supply within the ceiling".
+      44                 :            :      * @param from Sender address; the zero address denotes the mint this rule gates.
+      45                 :            :      * @param to Recipient address.
+      46                 :            :      * @return The restriction code the write hook enforces.
+      47                 :            :      */
+      48                 :          2 :     function _detectTransferRestrictionOnNotify(
+      49                 :            :         address from,
+      50                 :            :         address to,
+      51                 :            :         uint256 /* value */
+      52                 :            :     )
+      53                 :            :         internal
+      54                 :            :         view
+      55                 :            :         virtual
+      56                 :            :         override
+      57                 :            :         returns (uint8)
+      58                 :            :     {
+      59                 :          2 :         return _detectTransferRestriction(from, to, 0);
+      60                 :            :     }
+      61                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func-sort-c.html index 2a0b0caa..4350a84c 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -68,14 +68,14 @@ Function Name Sort by function name Hit count Sort by hit count - - RuleMaxTotalSupplyOwnable2Step._authorizeMaxTotalSupplyManager - 4 - RuleMaxTotalSupplyOwnable2Step.supportsInterface 5 + + RuleMaxTotalSupplyOwnable2Step._authorizeMaxTotalSupplyManager + 6 +
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func.html index 54992130..7e99b1c5 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -70,7 +70,7 @@ RuleMaxTotalSupplyOwnable2Step._authorizeMaxTotalSupplyManager - 4 + 6 RuleMaxTotalSupplyOwnable2Step.supportsInterface diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.gcov.html index 0d666748..a1b4cb02 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 4 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 2 @@ -125,7 +125,7 @@ 54 : : /** 55 : : * @notice Restricts maximum total supply management to the contract owner. 56 : : */ - 57 : 4 : function _authorizeMaxTotalSupplyManager() internal view virtual override onlyOwner {} + 57 : 6 : function _authorizeMaxTotalSupplyManager() internal view virtual override onlyOwner {} 58 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.func-sort-c.html new file mode 100644 index 00000000..bd770e81 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.func-sort-c.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleReceiverWhitelist.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleReceiverWhitelist.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:66100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleReceiverWhitelist._msgData1
RuleReceiverWhitelist._authorizeAddressListRemove3
RuleReceiverWhitelist.supportsInterface7
RuleReceiverWhitelist._authorizeAddressListAdd22
RuleReceiverWhitelist._msgSender64
RuleReceiverWhitelist._contextSuffixLength66
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.func.html new file mode 100644 index 00000000..a6093034 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.func.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleReceiverWhitelist.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleReceiverWhitelist.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:66100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleReceiverWhitelist._authorizeAddressListAdd22
RuleReceiverWhitelist._authorizeAddressListRemove3
RuleReceiverWhitelist._contextSuffixLength66
RuleReceiverWhitelist._msgData1
RuleReceiverWhitelist._msgSender64
RuleReceiverWhitelist.supportsInterface7
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.gcov.html new file mode 100644 index 00000000..5275f4cd --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelist.sol.gcov.html @@ -0,0 +1,175 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleReceiverWhitelist.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleReceiverWhitelist.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:66100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
+       5                 :            : import {Context} from "@openzeppelin/contracts/utils/Context.sol";
+       6                 :            : import {AccessControlModuleStandalone} from "../../../modules/AccessControlModuleStandalone.sol";
+       7                 :            : import {RuleReceiverWhitelistBase} from "../abstract/base/RuleReceiverWhitelistBase.sol";
+       8                 :            : import {RuleAddressSet} from "../abstract/RuleAddressSet/RuleAddressSet.sol";
+       9                 :            : 
+      10                 :            : /**
+      11                 :            :  * @title RuleReceiverWhitelist
+      12                 :            :  * @notice AccessControlEnumerable deployment variant of receiver whitelist rule.
+      13                 :            :  */
+      14                 :            : contract RuleReceiverWhitelist is RuleReceiverWhitelistBase, AccessControlModuleStandalone {
+      15                 :            :     /*//////////////////////////////////////////////////////////////
+      16                 :            :                              CONSTRUCTOR
+      17                 :            :     //////////////////////////////////////////////////////////////*/
+      18                 :            : 
+      19                 :            :     /**
+      20                 :            :      * @notice Deploys the rule, sets the admin and the meta-transaction forwarder.
+      21                 :            :      * @param admin Address that receives the default admin role.
+      22                 :            :      * @param forwarderIrrevocable Address of the ERC-2771 forwarder for meta-transactions.
+      23                 :            :      */
+      24                 :            :     constructor(address admin, address forwarderIrrevocable)
+      25                 :            :         RuleReceiverWhitelistBase(forwarderIrrevocable)
+      26                 :            :         AccessControlModuleStandalone(admin)
+      27                 :            :     {}
+      28                 :            : 
+      29                 :            :     /*//////////////////////////////////////////////////////////////
+      30                 :            :                           PUBLIC FUNCTIONS
+      31                 :            :     //////////////////////////////////////////////////////////////*/
+      32                 :            : 
+      33                 :            :     /**
+      34                 :            :      * @notice Indicates whether this contract supports a given interface.
+      35                 :            :      * @param interfaceId The interface identifier, as specified in ERC-165.
+      36                 :            :      * @return True if the interface is supported.
+      37                 :            :      */
+      38                 :          7 :     function supportsInterface(bytes4 interfaceId)
+      39                 :            :         public
+      40                 :            :         view
+      41                 :            :         virtual
+      42                 :            :         override(AccessControlEnumerable, RuleReceiverWhitelistBase)
+      43                 :            :         returns (bool)
+      44                 :            :     {
+      45                 :          7 :         return AccessControlEnumerable.supportsInterface(interfaceId)
+      46                 :          6 :             || RuleReceiverWhitelistBase.supportsInterface(interfaceId);
+      47                 :            :     }
+      48                 :            : 
+      49                 :            :     /*//////////////////////////////////////////////////////////////
+      50                 :            :                             ACCESS CONTROL
+      51                 :            :     //////////////////////////////////////////////////////////////*/
+      52                 :            : 
+      53                 :            :     /**
+      54                 :            :      * @notice Restricts adding addresses to the receiver whitelist to holders of ADDRESS_LIST_ADD_ROLE.
+      55                 :            :      */
+      56                 :         22 :     function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {}
+      57                 :            : 
+      58                 :            :     /**
+      59                 :            :      * @notice Restricts removing addresses from the receiver whitelist to holders of ADDRESS_LIST_REMOVE_ROLE.
+      60                 :            :      */
+      61                 :          3 :     function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {}
+      62                 :            : 
+      63                 :            :     /*//////////////////////////////////////////////////////////////
+      64                 :            :                         INTERNAL FUNCTIONS
+      65                 :            :     //////////////////////////////////////////////////////////////*/
+      66                 :            : 
+      67                 :            :     /**
+      68                 :            :      * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context.
+      69                 :            :      * @return sender The address of the message sender.
+      70                 :            :      */
+      71                 :         64 :     function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) {
+      72                 :         64 :         return super._msgSender();
+      73                 :            :     }
+      74                 :            : 
+      75                 :            :     /**
+      76                 :            :      * @notice Returns the message calldata, accounting for meta-transaction (ERC-2771) context.
+      77                 :            :      * @return The message calldata.
+      78                 :            :      */
+      79                 :          1 :     function _msgData() internal view virtual override(Context, RuleAddressSet) returns (bytes calldata) {
+      80                 :          1 :         return super._msgData();
+      81                 :            :     }
+      82                 :            : 
+      83                 :            :     /**
+      84                 :            :      * @notice Returns the length of the context suffix appended by the forwarder.
+      85                 :            :      * @return The context suffix length in bytes.
+      86                 :            :      */
+      87                 :         66 :     function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) {
+      88                 :         66 :         return super._contextSuffixLength();
+      89                 :            :     }
+      90                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.func-sort-c.html new file mode 100644 index 00000000..cf4f49bd --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.func-sort-c.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleReceiverWhitelistOwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:66100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleReceiverWhitelistOwnable2Step._msgData1
RuleReceiverWhitelistOwnable2Step._authorizeAddressListAdd2
RuleReceiverWhitelistOwnable2Step._authorizeAddressListRemove2
RuleReceiverWhitelistOwnable2Step.supportsInterface5
RuleReceiverWhitelistOwnable2Step._msgSender10
RuleReceiverWhitelistOwnable2Step._contextSuffixLength12
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.func.html new file mode 100644 index 00000000..a362b4f5 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.func.html @@ -0,0 +1,105 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleReceiverWhitelistOwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:66100.0 %
Branches:00-
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
RuleReceiverWhitelistOwnable2Step._authorizeAddressListAdd2
RuleReceiverWhitelistOwnable2Step._authorizeAddressListRemove2
RuleReceiverWhitelistOwnable2Step._contextSuffixLength12
RuleReceiverWhitelistOwnable2Step._msgData1
RuleReceiverWhitelistOwnable2Step._msgSender10
RuleReceiverWhitelistOwnable2Step.supportsInterface5
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.gcov.html new file mode 100644 index 00000000..5a1c3d52 --- /dev/null +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol.gcov.html @@ -0,0 +1,176 @@ + + + + + + + LCOV - lcov2.info - src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/rules/validation/deployment - RuleReceiverWhitelistOwnable2Step.sol (source / functions)HitTotalCoverage
Test:lcov2.infoLines:1111100.0 %
Date:2026-08-19 15:38:25Functions:66100.0 %
Branches:00-
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : // SPDX-License-Identifier: MPL-2.0
+       2                 :            : pragma solidity ^0.8.20;
+       3                 :            : 
+       4                 :            : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+       5                 :            : import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
+       6                 :            : import {Context} from "@openzeppelin/contracts/utils/Context.sol";
+       7                 :            : import {Ownable2StepERC165Module} from "../../../modules/Ownable2StepERC165Module.sol";
+       8                 :            : import {RuleReceiverWhitelistBase} from "../abstract/base/RuleReceiverWhitelistBase.sol";
+       9                 :            : import {RuleAddressSet} from "../abstract/RuleAddressSet/RuleAddressSet.sol";
+      10                 :            : 
+      11                 :            : /**
+      12                 :            :  * @title RuleReceiverWhitelistOwnable2Step
+      13                 :            :  * @notice Ownable2Step deployment variant of receiver whitelist rule.
+      14                 :            :  */
+      15                 :            : contract RuleReceiverWhitelistOwnable2Step is RuleReceiverWhitelistBase, Ownable2Step, Ownable2StepERC165Module {
+      16                 :            :     /*//////////////////////////////////////////////////////////////
+      17                 :            :                              CONSTRUCTOR
+      18                 :            :     //////////////////////////////////////////////////////////////*/
+      19                 :            : 
+      20                 :            :     /**
+      21                 :            :      * @notice Deploys the rule, sets the owner and the meta-transaction forwarder.
+      22                 :            :      * @param owner Contract owner.
+      23                 :            :      * @param forwarderIrrevocable Address of the ERC-2771 forwarder for meta-transactions.
+      24                 :            :      */
+      25                 :            :     constructor(address owner, address forwarderIrrevocable)
+      26                 :            :         RuleReceiverWhitelistBase(forwarderIrrevocable)
+      27                 :            :         Ownable(owner)
+      28                 :            :     {}
+      29                 :            : 
+      30                 :            :     /*//////////////////////////////////////////////////////////////
+      31                 :            :                           PUBLIC FUNCTIONS
+      32                 :            :     //////////////////////////////////////////////////////////////*/
+      33                 :            : 
+      34                 :            :     /**
+      35                 :            :      * @notice Indicates whether this contract supports a given interface.
+      36                 :            :      * @param interfaceId The interface identifier, as specified in ERC-165.
+      37                 :            :      * @return True if the interface is supported.
+      38                 :            :      */
+      39                 :          5 :     function supportsInterface(bytes4 interfaceId)
+      40                 :            :         public
+      41                 :            :         view
+      42                 :            :         virtual
+      43                 :            :         override(RuleReceiverWhitelistBase, Ownable2StepERC165Module)
+      44                 :            :         returns (bool)
+      45                 :            :     {
+      46                 :          5 :         return Ownable2StepERC165Module.supportsInterface(interfaceId)
+      47                 :          2 :             || RuleReceiverWhitelistBase.supportsInterface(interfaceId);
+      48                 :            :     }
+      49                 :            : 
+      50                 :            :     /*//////////////////////////////////////////////////////////////
+      51                 :            :                             ACCESS CONTROL
+      52                 :            :     //////////////////////////////////////////////////////////////*/
+      53                 :            : 
+      54                 :            :     /**
+      55                 :            :      * @notice Restricts adding addresses to the receiver whitelist to the contract owner.
+      56                 :            :      */
+      57                 :          2 :     function _authorizeAddressListAdd() internal view virtual override onlyOwner {}
+      58                 :            : 
+      59                 :            :     /**
+      60                 :            :      * @notice Restricts removing addresses from the receiver whitelist to the contract owner.
+      61                 :            :      */
+      62                 :          2 :     function _authorizeAddressListRemove() internal view virtual override onlyOwner {}
+      63                 :            : 
+      64                 :            :     /*//////////////////////////////////////////////////////////////
+      65                 :            :                         INTERNAL FUNCTIONS
+      66                 :            :     //////////////////////////////////////////////////////////////*/
+      67                 :            : 
+      68                 :            :     /**
+      69                 :            :      * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context.
+      70                 :            :      * @return sender The address of the message sender.
+      71                 :            :      */
+      72                 :         10 :     function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) {
+      73                 :         10 :         return super._msgSender();
+      74                 :            :     }
+      75                 :            : 
+      76                 :            :     /**
+      77                 :            :      * @notice Returns the message calldata, accounting for meta-transaction (ERC-2771) context.
+      78                 :            :      * @return The message calldata.
+      79                 :            :      */
+      80                 :          1 :     function _msgData() internal view virtual override(Context, RuleAddressSet) returns (bytes calldata) {
+      81                 :          1 :         return super._msgData();
+      82                 :            :     }
+      83                 :            : 
+      84                 :            :     /**
+      85                 :            :      * @notice Returns the length of the context suffix appended by the forwarder.
+      86                 :            :      * @return The context suffix length in bytes.
+      87                 :            :      */
+      88                 :         12 :     function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) {
+      89                 :         12 :         return super._contextSuffixLength();
+      90                 :            :     }
+      91                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func-sort-c.html index 4d187708..7ac06ab4 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSanctionsList.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSanctionsList.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 10 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 @@ -76,17 +76,17 @@ RuleSanctionsList._authorizeSanctionListManager 18 - - RuleSanctionsList.supportsInterface - 58 - RuleSanctionsList._msgSender - 60 + 93 RuleSanctionsList._contextSuffixLength - 61 + 94 + + + RuleSanctionsList.supportsInterface + 115
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func.html index 69aa75b1..748f3cec 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSanctionsList.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSanctionsList.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 10 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 @@ -74,7 +74,7 @@ RuleSanctionsList._contextSuffixLength - 61 + 94 RuleSanctionsList._msgData @@ -82,11 +82,11 @@ RuleSanctionsList._msgSender - 60 + 93 RuleSanctionsList.supportsInterface - 58 + 115
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.gcov.html index 82be194b..e1589037 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsList.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSanctionsList.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleSanctionsList.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 10 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 @@ -108,15 +108,15 @@ 37 : : * @param interfaceId The interface identifier, as specified in ERC-165. 38 : : * @return True if the interface is supported. 39 : : */ - 40 : 58 : function supportsInterface(bytes4 interfaceId) + 40 : 115 : function supportsInterface(bytes4 interfaceId) 41 : : public 42 : : view 43 : : virtual 44 : : override(AccessControlEnumerable, RuleTransferValidation) 45 : : returns (bool) 46 : : { - 47 : 58 : return AccessControlEnumerable.supportsInterface(interfaceId) - 48 : 39 : || RuleTransferValidation.supportsInterface(interfaceId); + 47 : 115 : return AccessControlEnumerable.supportsInterface(interfaceId) + 48 : 77 : || RuleTransferValidation.supportsInterface(interfaceId); 49 : : } 50 : : 51 : : /*////////////////////////////////////////////////////////////// @@ -136,8 +136,8 @@ 65 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. 66 : : * @return sender The address of the message sender. 67 : : */ - 68 : 60 : function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) { - 69 : 60 : return ERC2771Context._msgSender(); + 68 : 93 : function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) { + 69 : 93 : return ERC2771Context._msgSender(); 70 : : } 71 : : 72 : : /** @@ -152,8 +152,8 @@ 81 : : * @notice Returns the length of the context suffix appended by the forwarder. 82 : : * @return The context suffix length in bytes. 83 : : */ - 84 : 61 : function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) { - 85 : 61 : return ERC2771Context._contextSuffixLength(); + 84 : 94 : function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) { + 85 : 94 : return ERC2771Context._contextSuffixLength(); 86 : : } 87 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func-sort-c.html index 59c428d4..da2c652f 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 10 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func.html index ef355213..0ab9816f 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 10 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.gcov.html index 73388d39..b00f8191 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 10 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 5 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func-sort-c.html index 99ca6420..00305d8b 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSpenderWhitelist.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSpenderWhitelist.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 @@ -77,20 +77,20 @@ 2 - RuleSpenderWhitelist.supportsInterface - 6 + RuleSpenderWhitelist._authorizeAddressListAdd + 9 - RuleSpenderWhitelist._authorizeAddressListAdd - 7 + RuleSpenderWhitelist.supportsInterface + 14 RuleSpenderWhitelist._msgSender - 38 + 46 RuleSpenderWhitelist._contextSuffixLength - 40 + 48
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func.html index 352ef625..24462258 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSpenderWhitelist.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSpenderWhitelist.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 @@ -70,7 +70,7 @@ RuleSpenderWhitelist._authorizeAddressListAdd - 7 + 9 RuleSpenderWhitelist._authorizeAddressListRemove @@ -78,7 +78,7 @@ RuleSpenderWhitelist._contextSuffixLength - 40 + 48 RuleSpenderWhitelist._msgData @@ -86,11 +86,11 @@ RuleSpenderWhitelist._msgSender - 38 + 46 RuleSpenderWhitelist.supportsInterface - 6 + 14
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.gcov.html index 63bbc743..04f23e14 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelist.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSpenderWhitelist.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleSpenderWhitelist.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 @@ -106,15 +106,15 @@ 35 : : * @param interfaceId The interface identifier, as specified in ERC-165. 36 : : * @return True if the interface is supported. 37 : : */ - 38 : 6 : function supportsInterface(bytes4 interfaceId) + 38 : 14 : function supportsInterface(bytes4 interfaceId) 39 : : public 40 : : view 41 : : virtual 42 : : override(AccessControlEnumerable, RuleSpenderWhitelistBase) 43 : : returns (bool) 44 : : { - 45 : 6 : return AccessControlEnumerable.supportsInterface(interfaceId) - 46 : 5 : || RuleSpenderWhitelistBase.supportsInterface(interfaceId); + 45 : 14 : return AccessControlEnumerable.supportsInterface(interfaceId) + 46 : 11 : || RuleSpenderWhitelistBase.supportsInterface(interfaceId); 47 : : } 48 : : 49 : : /*////////////////////////////////////////////////////////////// @@ -124,7 +124,7 @@ 53 : : /** 54 : : * @notice Restricts adding addresses to the spender whitelist to holders of ADDRESS_LIST_ADD_ROLE. 55 : : */ - 56 : 7 : function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + 56 : 9 : function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} 57 : : 58 : : /** 59 : : * @notice Restricts removing addresses from the spender whitelist to holders of ADDRESS_LIST_REMOVE_ROLE. @@ -139,8 +139,8 @@ 68 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. 69 : : * @return sender The address of the message sender. 70 : : */ - 71 : 38 : function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) { - 72 : 38 : return super._msgSender(); + 71 : 46 : function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) { + 72 : 46 : return super._msgSender(); 73 : : } 74 : : 75 : : /** @@ -155,8 +155,8 @@ 84 : : * @notice Returns the length of the context suffix appended by the forwarder. 85 : : * @return The context suffix length in bytes. 86 : : */ - 87 : 40 : function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) { - 88 : 40 : return super._contextSuffixLength(); + 87 : 48 : function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) { + 88 : 48 : return super._contextSuffixLength(); 89 : : } 90 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func-sort-c.html index 743728ea..1ea09cdb 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func.html index f0afa4c0..5e139b0a 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.gcov.html index b97e62e6..10846141 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 11 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 6 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func-sort-c.html index 6578ddea..5ad512c7 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelist.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelist.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 @@ -69,36 +69,36 @@ Hit count Sort by hit count - RuleWhitelist._authorizeCheckSpenderManager + RuleWhitelist._msgData 1 - RuleWhitelist._msgData - 1 + RuleWhitelist._authorizeCheckSpenderManager + 2 RuleWhitelist._authorizeMintBurnManager - 10 + 30 - RuleWhitelist.supportsInterface - 47 + RuleWhitelist._authorizeAddressListRemove + 265 - RuleWhitelist._authorizeAddressListRemove - 263 + RuleWhitelist.supportsInterface + 703 RuleWhitelist._authorizeAddressListAdd - 367 + 881 RuleWhitelist._msgSender - 827 + 1402 RuleWhitelist._contextSuffixLength - 828 + 1403
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func.html index 43bdefe6..30e3df89 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelist.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelist.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 @@ -70,23 +70,23 @@ RuleWhitelist._authorizeAddressListAdd - 367 + 881 RuleWhitelist._authorizeAddressListRemove - 263 + 265 RuleWhitelist._authorizeCheckSpenderManager - 1 + 2 RuleWhitelist._authorizeMintBurnManager - 10 + 30 RuleWhitelist._contextSuffixLength - 828 + 1403 RuleWhitelist._msgData @@ -94,11 +94,11 @@ RuleWhitelist._msgSender - 827 + 1402 RuleWhitelist.supportsInterface - 47 + 703
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.gcov.html index 32857fee..0c07ca35 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelist.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelist.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelist.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 @@ -115,15 +115,15 @@ 44 : : * @param interfaceId The interface identifier, as specified in ERC-165. 45 : : * @return supported True if the interface is supported. 46 : : */ - 47 : 47 : function supportsInterface(bytes4 interfaceId) + 47 : 703 : function supportsInterface(bytes4 interfaceId) 48 : : public 49 : : view 50 : : virtual 51 : : override(AccessControlEnumerable, RuleWhitelistBase) 52 : : returns (bool) 53 : : { - 54 : 47 : return AccessControlEnumerable.supportsInterface(interfaceId) - 55 : 32 : || RuleWhitelistBase.supportsInterface(interfaceId); + 54 : 703 : return AccessControlEnumerable.supportsInterface(interfaceId) + 55 : 470 : || RuleWhitelistBase.supportsInterface(interfaceId); 56 : : } 57 : : 58 : : /*////////////////////////////////////////////////////////////// @@ -133,22 +133,22 @@ 62 : : /** 63 : : * @notice Restricts toggling the spender-check setting to holders of DEFAULT_ADMIN_ROLE. 64 : : */ - 65 : 1 : function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 65 : 2 : function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} 66 : : 67 : : /** 68 : : * @notice Restricts toggling `allowMint` / `allowBurn` to holders of DEFAULT_ADMIN_ROLE. 69 : : */ - 70 : 10 : function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 70 : 30 : function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} 71 : : 72 : : /** 73 : : * @notice Restricts adding addresses to the whitelist to holders of ADDRESS_LIST_ADD_ROLE. 74 : : */ - 75 : 367 : function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + 75 : 881 : function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} 76 : : 77 : : /** 78 : : * @notice Restricts removing addresses from the whitelist to holders of ADDRESS_LIST_REMOVE_ROLE. 79 : : */ - 80 : 263 : function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + 80 : 265 : function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} 81 : : 82 : : /*////////////////////////////////////////////////////////////// 83 : : INTERNAL FUNCTIONS @@ -158,8 +158,8 @@ 87 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. 88 : : * @return sender The address of the message sender. 89 : : */ - 90 : 827 : function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) { - 91 : 827 : return super._msgSender(); + 90 : 1402 : function _msgSender() internal view virtual override(Context, RuleAddressSet) returns (address sender) { + 91 : 1402 : return super._msgSender(); 92 : : } 93 : : 94 : : /** @@ -174,8 +174,8 @@ 103 : : * @notice Returns the length of the context suffix appended by the forwarder. 104 : : * @return The context suffix length in bytes. 105 : : */ - 106 : 828 : function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) { - 107 : 828 : return super._contextSuffixLength(); + 106 : 1403 : function _contextSuffixLength() internal view virtual override(Context, RuleAddressSet) returns (uint256) { + 107 : 1403 : return super._contextSuffixLength(); 108 : : } 109 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func-sort-c.html index a8974435..0024672e 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func.html index dff580cf..654c7cbe 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.gcov.html index c8655d78..6f142884 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func-sort-c.html index fd0b1f54..92225dac 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistWrapper.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistWrapper.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 19 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 11 @@ -73,44 +73,44 @@ 1 - RuleWhitelistWrapper._revokeRole + RuleWhitelistWrapper._revokeRole 1 - RuleWhitelistWrapper._authorizeCheckSpenderManager + RuleWhitelistWrapper._authorizeCheckSpenderManager 2 - RuleWhitelistWrapper._onlyRulesLimitManager + RuleWhitelistWrapper._onlyRulesLimitManager 2 - RuleWhitelistWrapper._authorizeMintBurnManager + RuleWhitelistWrapper._authorizeMintBurnManager 4 RuleWhitelistWrapper.supportsInterface - 47 + 50 - RuleWhitelistWrapper._grantRole - 49 + RuleWhitelistWrapper._grantRole + 56 RuleWhitelistWrapper.hasRole - 49 + 56 - RuleWhitelistWrapper._onlyRulesManager - 98 + RuleWhitelistWrapper._onlyRulesManager + 105 RuleWhitelistWrapper._msgSender - 158 + 172 RuleWhitelistWrapper._contextSuffixLength - 159 + 173
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func.html index 9da6f757..4d39b329 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistWrapper.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistWrapper.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 19 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 11 @@ -69,20 +69,20 @@ Hit count Sort by hit count - RuleWhitelistWrapper._authorizeCheckSpenderManager + RuleWhitelistWrapper._authorizeCheckSpenderManager 2 - RuleWhitelistWrapper._authorizeMintBurnManager + RuleWhitelistWrapper._authorizeMintBurnManager 4 RuleWhitelistWrapper._contextSuffixLength - 159 + 173 - RuleWhitelistWrapper._grantRole - 49 + RuleWhitelistWrapper._grantRole + 56 RuleWhitelistWrapper._msgData @@ -90,27 +90,27 @@ RuleWhitelistWrapper._msgSender - 158 + 172 - RuleWhitelistWrapper._onlyRulesLimitManager + RuleWhitelistWrapper._onlyRulesLimitManager 2 - RuleWhitelistWrapper._onlyRulesManager - 98 + RuleWhitelistWrapper._onlyRulesManager + 105 - RuleWhitelistWrapper._revokeRole + RuleWhitelistWrapper._revokeRole 1 RuleWhitelistWrapper.hasRole - 49 + 56 RuleWhitelistWrapper.supportsInterface - 47 + 50
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.gcov.html index c3d807a5..279d50f3 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapper.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistWrapper.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistWrapper.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 19 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 11 @@ -115,8 +115,8 @@ 44 : : * @param account Address being checked for the role. 45 : : * @return True if `account` holds `role`. 46 : : */ - 47 : 49 : function hasRole(bytes32 role, address account) public view virtual override returns (bool) { - 48 : 159 : return AccessControlModuleStandalone.hasRole(role, account); + 47 : 56 : function hasRole(bytes32 role, address account) public view virtual override returns (bool) { + 48 : 173 : return AccessControlModuleStandalone.hasRole(role, account); 49 : : } 50 : : 51 : : /** @@ -124,72 +124,72 @@ 53 : : * @param interfaceId The interface identifier, as specified in ERC-165. 54 : : * @return True if the interface is supported. 55 : : */ - 56 : 47 : function supportsInterface(bytes4 interfaceId) + 56 : 50 : function supportsInterface(bytes4 interfaceId) 57 : : public 58 : : view 59 : : virtual 60 : : override(AccessControlEnumerable, RuleWhitelistWrapperBase) 61 : : returns (bool) 62 : : { - 63 : 47 : return RuleWhitelistWrapperBase.supportsInterface(interfaceId) - 64 : 32 : || AccessControlEnumerable.supportsInterface(interfaceId); + 63 : 50 : return RuleWhitelistWrapperBase.supportsInterface(interfaceId) + 64 : 35 : || AccessControlEnumerable.supportsInterface(interfaceId); 65 : : } 66 : : 67 : : /*////////////////////////////////////////////////////////////// - 68 : : ACCESS CONTROL + 68 : : INTERNAL FUNCTIONS 69 : : //////////////////////////////////////////////////////////////*/ 70 : : 71 : : /** - 72 : : * @notice Restricts toggling the spender-check setting to holders of DEFAULT_ADMIN_ROLE. - 73 : : */ - 74 : 2 : function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} - 75 : : - 76 : : /** - 77 : : * @notice Restricts toggling `allowMint` / `allowBurn` to holders of DEFAULT_ADMIN_ROLE. - 78 : : */ - 79 : 4 : function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 72 : : * @notice Grants `role` to `account`, keeping role enumeration in sync. + 73 : : * @param role Role identifier to grant. + 74 : : * @param account Address receiving the role. + 75 : : * @return True if the role was newly granted. + 76 : : */ + 77 : 56 : function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { + 78 : 56 : return AccessControlEnumerable._grantRole(role, account); + 79 : : } 80 : : 81 : : /** - 82 : : * @notice Restricts rules management to holders of RULES_MANAGEMENT_ROLE. - 83 : : * @dev Restrict rules management to the dedicated role. - 84 : : */ - 85 : 98 : function _onlyRulesManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} - 86 : : - 87 : : /** - 88 : : * @notice Restricts rules-limit management to holders of RULES_MANAGEMENT_ROLE. - 89 : : */ - 90 : 2 : function _onlyRulesLimitManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} - 91 : : - 92 : : /*////////////////////////////////////////////////////////////// - 93 : : INTERNAL FUNCTIONS - 94 : : //////////////////////////////////////////////////////////////*/ - 95 : : - 96 : : /** - 97 : : * @notice Grants `role` to `account`, keeping role enumeration in sync. - 98 : : * @param role Role identifier to grant. - 99 : : * @param account Address receiving the role. - 100 : : * @return True if the role was newly granted. - 101 : : */ - 102 : 49 : function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { - 103 : 49 : return AccessControlEnumerable._grantRole(role, account); - 104 : : } - 105 : : - 106 : : /** - 107 : : * @notice Revokes `role` from `account`, keeping role enumeration in sync. - 108 : : * @param role Role identifier to revoke. - 109 : : * @param account Address losing the role. - 110 : : * @return True if the role was previously held and is now revoked. - 111 : : */ - 112 : 1 : function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { - 113 : 1 : return AccessControlEnumerable._revokeRole(role, account); - 114 : : } + 82 : : * @notice Revokes `role` from `account`, keeping role enumeration in sync. + 83 : : * @param role Role identifier to revoke. + 84 : : * @param account Address losing the role. + 85 : : * @return True if the role was previously held and is now revoked. + 86 : : */ + 87 : 1 : function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { + 88 : 1 : return AccessControlEnumerable._revokeRole(role, account); + 89 : : } + 90 : : + 91 : : /*////////////////////////////////////////////////////////////// + 92 : : ACCESS CONTROL + 93 : : //////////////////////////////////////////////////////////////*/ + 94 : : + 95 : : /** + 96 : : * @notice Restricts toggling the spender-check setting to holders of DEFAULT_ADMIN_ROLE. + 97 : : */ + 98 : 2 : function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 99 : : + 100 : : /** + 101 : : * @notice Restricts toggling `allowMint` / `allowBurn` to holders of DEFAULT_ADMIN_ROLE. + 102 : : */ + 103 : 4 : function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + 104 : : + 105 : : /** + 106 : : * @notice Restricts rules management to holders of RULES_MANAGEMENT_ROLE. + 107 : : * @dev Restrict rules management to the dedicated role. + 108 : : */ + 109 : 105 : function _onlyRulesManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + 110 : : + 111 : : /** + 112 : : * @notice Restricts rules-limit management to holders of RULES_MANAGEMENT_ROLE. + 113 : : */ + 114 : 2 : function _onlyRulesLimitManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} 115 : : 116 : : /** 117 : : * @notice Returns the message sender, accounting for meta-transaction (ERC-2771) context. 118 : : * @return sender The address of the message sender. 119 : : */ - 120 : 158 : function _msgSender() internal view virtual override(RuleWhitelistWrapperBase, Context) returns (address sender) { - 121 : 158 : return RuleWhitelistWrapperBase._msgSender(); + 120 : 172 : function _msgSender() internal view virtual override(RuleWhitelistWrapperBase, Context) returns (address sender) { + 121 : 172 : return RuleWhitelistWrapperBase._msgSender(); 122 : : } 123 : : 124 : : /** @@ -204,14 +204,14 @@ 133 : : * @notice Returns the length of the context suffix appended by the forwarder. 134 : : * @return The context suffix length in bytes. 135 : : */ - 136 : 159 : function _contextSuffixLength() + 136 : 173 : function _contextSuffixLength() 137 : : internal 138 : : view 139 : : virtual 140 : : override(RuleWhitelistWrapperBase, Context) 141 : : returns (uint256) 142 : : { - 143 : 159 : return RuleWhitelistWrapperBase._contextSuffixLength(); + 143 : 173 : return RuleWhitelistWrapperBase._contextSuffixLength(); 144 : : } 145 : : } diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func-sort-c.html index 4a428f96..79673283 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func-sort-c.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func.html index 3f876eaf..fa20bf7b 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.func.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol - functions + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol - functions @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.gcov.html index 7bca084f..aab4f569 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol.gcov.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol + LCOV - lcov2.info - src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol @@ -28,7 +28,7 @@ Test: - lcov.info + lcov2.info Lines: 13 @@ -37,7 +37,7 @@ Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: 8 diff --git a/doc/coverage/coverage/src/rules/validation/deployment/index-sort-b.html b/doc/coverage/coverage/src/rules/validation/deployment/index-sort-b.html index b9275635..2e0611e7 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/index-sort-b.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/index-sort-b.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment + LCOV - lcov2.info - src/rules/validation/deployment @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 164 - 164 + 210 + 210 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 95 - 95 + 119 + 119 100.0 % @@ -81,6 +81,18 @@ Functions Sort by function coverage Branches Sort by branch coverage + + RuleWhitelist.sol + +
100.0%
+ + 100.0 % + 13 / 13 + 100.0 % + 8 / 8 + - + 0 / 0 + RuleWhitelistWrapperOwnable2Step.sol @@ -93,6 +105,18 @@ - 0 / 0 + + RuleSpenderWhitelistOwnable2Step.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + RuleSanctionsList.sol @@ -106,38 +130,74 @@ 0 / 0 - RuleMaxTotalSupply.sol + RuleERC2980Ownable2Step.sol
100.0%
100.0 % - 4 / 4 + 13 / 13 + 100.0 % + 9 / 9 + - + 0 / 0 + + + RuleMaxTotalSupplyERC3643Ownable2Step.sol + +
100.0%
+ 100.0 % 2 / 2 + 100.0 % + 1 / 1 - 0 / 0 - RuleERC2980.sol + RuleReceiverWhitelistOwnable2Step.sol
100.0%
100.0 % - 13 / 13 + 11 / 11 100.0 % - 9 / 9 + 6 / 6 - 0 / 0 - RuleWhitelist.sol + RuleChainlinkPoRERC3643.sol
100.0%
100.0 % - 13 / 13 + 2 / 2 100.0 % - 8 / 8 + 1 / 1 + - + 0 / 0 + + + RuleReceiverWhitelist.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + + + RuleBlacklistOwnable2Step.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 - 0 / 0 @@ -154,7 +214,7 @@ 0 / 0 - RuleIdentityRegistryOwnable2Step.sol + RuleIdentityRegistry.sol
100.0%
@@ -166,43 +226,43 @@ 0 / 0 - RuleBlacklist.sol + RuleChainlinkPoR.sol
100.0%
100.0 % - 11 / 11 + 4 / 4 100.0 % - 6 / 6 + 2 / 2 - 0 / 0 - RuleSanctionsListOwnable2Step.sol + RuleIdentityRegistryOwnable2Step.sol
100.0%
100.0 % - 10 / 10 + 4 / 4 100.0 % - 5 / 5 + 2 / 2 - 0 / 0 - RuleIdentityRegistry.sol + RuleMaxTotalSupplyERC3643.sol
100.0%
100.0 % - 4 / 4 - 100.0 % 2 / 2 + 100.0 % + 1 / 1 - 0 / 0 - RuleSpenderWhitelistOwnable2Step.sol + RuleSpenderWhitelist.sol
100.0%
@@ -214,14 +274,26 @@ 0 / 0 - RuleERC2980Ownable2Step.sol + RuleSanctionsListOwnable2Step.sol
100.0%
100.0 % - 13 / 13 + 10 / 10 100.0 % - 9 / 9 + 5 / 5 + - + 0 / 0 + + + RuleMaxBalanceOwnable2Step.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 - 0 / 0 @@ -250,19 +322,19 @@ 0 / 0 - RuleSpenderWhitelist.sol + RuleMaxBalance.sol
100.0%
100.0 % - 11 / 11 + 4 / 4 100.0 % - 6 / 6 + 2 / 2 - 0 / 0 - RuleBlacklistOwnable2Step.sol + RuleBlacklist.sol
100.0%
@@ -273,6 +345,54 @@ - 0 / 0 + + RuleERC2980.sol + +
100.0%
+ + 100.0 % + 13 / 13 + 100.0 % + 9 / 9 + - + 0 / 0 + + + RuleMaxTotalSupply.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoROwnable2Step.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoRERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 +
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/index-sort-f.html b/doc/coverage/coverage/src/rules/validation/deployment/index-sort-f.html index 147d780a..2115a169 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/index-sort-f.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/index-sort-f.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment + LCOV - lcov2.info - src/rules/validation/deployment @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 164 - 164 + 210 + 210 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 95 - 95 + 119 + 119 100.0 % @@ -82,7 +82,67 @@ Branches Sort by branch coverage - RuleMaxTotalSupply.sol + RuleMaxTotalSupplyERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleChainlinkPoRERC3643.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleMaxTotalSupplyERC3643.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleChainlinkPoRERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleIdentityRegistry.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoR.sol
100.0%
@@ -106,7 +166,7 @@ 0 / 0 - RuleIdentityRegistry.sol + RuleMaxBalanceOwnable2Step.sol
100.0%
@@ -129,6 +189,42 @@ - 0 / 0 + + RuleMaxBalance.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleMaxTotalSupply.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoROwnable2Step.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + RuleSanctionsList.sol @@ -154,7 +250,7 @@ 0 / 0 - RuleBlacklist.sol + RuleSpenderWhitelistOwnable2Step.sol
100.0%
@@ -166,7 +262,7 @@ 0 / 0 - RuleSpenderWhitelistOwnable2Step.sol + RuleReceiverWhitelistOwnable2Step.sol
100.0%
@@ -178,7 +274,7 @@ 0 / 0 - RuleSpenderWhitelist.sol + RuleReceiverWhitelist.sol
100.0%
@@ -202,7 +298,31 @@ 0 / 0 - RuleWhitelistWrapperOwnable2Step.sol + RuleSpenderWhitelist.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + + + RuleBlacklist.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + + + RuleWhitelist.sol
100.0%
@@ -214,7 +334,7 @@ 0 / 0 - RuleWhitelist.sol + RuleWhitelistWrapperOwnable2Step.sol
100.0%
@@ -238,7 +358,7 @@ 0 / 0 - RuleERC2980.sol + RuleERC2980Ownable2Step.sol
100.0%
@@ -250,7 +370,7 @@ 0 / 0 - RuleERC2980Ownable2Step.sol + RuleERC2980.sol
100.0%
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/index-sort-l.html b/doc/coverage/coverage/src/rules/validation/deployment/index-sort-l.html index fb117356..7a48c404 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/index-sort-l.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/index-sort-l.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment + LCOV - lcov2.info - src/rules/validation/deployment @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 164 - 164 + 210 + 210 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 95 - 95 + 119 + 119 100.0 % @@ -82,7 +82,67 @@ Branches Sort by branch coverage - RuleMaxTotalSupply.sol + RuleMaxTotalSupplyERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleChainlinkPoRERC3643.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleMaxTotalSupplyERC3643.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleChainlinkPoRERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleIdentityRegistry.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoR.sol
100.0%
@@ -106,7 +166,7 @@ 0 / 0 - RuleIdentityRegistry.sol + RuleMaxBalanceOwnable2Step.sol
100.0%
@@ -129,6 +189,42 @@ - 0 / 0 + + RuleMaxBalance.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleMaxTotalSupply.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoROwnable2Step.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + RuleSanctionsList.sol @@ -154,7 +250,7 @@ 0 / 0 - RuleBlacklist.sol + RuleSpenderWhitelistOwnable2Step.sol
100.0%
@@ -166,7 +262,7 @@ 0 / 0 - RuleSpenderWhitelistOwnable2Step.sol + RuleReceiverWhitelistOwnable2Step.sol
100.0%
@@ -178,7 +274,7 @@ 0 / 0 - RuleSpenderWhitelist.sol + RuleReceiverWhitelist.sol
100.0%
@@ -202,7 +298,31 @@ 0 / 0 - RuleWhitelistWrapperOwnable2Step.sol + RuleSpenderWhitelist.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + + + RuleBlacklist.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + + + RuleWhitelist.sol
100.0%
@@ -214,26 +334,26 @@ 0 / 0 - RuleERC2980.sol + RuleWhitelistWrapperOwnable2Step.sol
100.0%
100.0 % 13 / 13 100.0 % - 9 / 9 + 8 / 8 - 0 / 0 - RuleWhitelist.sol + RuleERC2980Ownable2Step.sol
100.0%
100.0 % 13 / 13 100.0 % - 8 / 8 + 9 / 9 - 0 / 0 @@ -250,7 +370,7 @@ 0 / 0 - RuleERC2980Ownable2Step.sol + RuleERC2980.sol
100.0%
diff --git a/doc/coverage/coverage/src/rules/validation/deployment/index.html b/doc/coverage/coverage/src/rules/validation/deployment/index.html index eb083c3d..2bc1c658 100644 --- a/doc/coverage/coverage/src/rules/validation/deployment/index.html +++ b/doc/coverage/coverage/src/rules/validation/deployment/index.html @@ -4,7 +4,7 @@ - LCOV - lcov.info - src/rules/validation/deployment + LCOV - lcov2.info - src/rules/validation/deployment @@ -28,20 +28,20 @@ Test: - lcov.info + lcov2.info Lines: - 164 - 164 + 210 + 210 100.0 % Date: - 2026-07-14 13:44:06 + 2026-08-19 15:38:25 Functions: - 95 - 95 + 119 + 119 100.0 % @@ -105,6 +105,54 @@ - 0 / 0 + + RuleChainlinkPoR.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleChainlinkPoRERC3643.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleChainlinkPoRERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleChainlinkPoROwnable2Step.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + RuleERC2980.sol @@ -153,6 +201,30 @@ - 0 / 0 + + RuleMaxBalance.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + + + RuleMaxBalanceOwnable2Step.sol + +
100.0%
+ + 100.0 % + 4 / 4 + 100.0 % + 2 / 2 + - + 0 / 0 + RuleMaxTotalSupply.sol @@ -165,6 +237,30 @@ - 0 / 0 + + RuleMaxTotalSupplyERC3643.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + RuleMaxTotalSupplyERC3643Ownable2Step.sol + +
100.0%
+ + 100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + RuleMaxTotalSupplyOwnable2Step.sol @@ -177,6 +273,30 @@ - 0 / 0 + + RuleReceiverWhitelist.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + + + RuleReceiverWhitelistOwnable2Step.sol + +
100.0%
+ + 100.0 % + 11 / 11 + 100.0 % + 6 / 6 + - + 0 / 0 + RuleSanctionsList.sol diff --git a/doc/coverage/lcov.info b/doc/coverage/lcov.info index 4b819914..fb45efa5 100644 --- a/doc/coverage/lcov.info +++ b/doc/coverage/lcov.info @@ -1,708 +1,20 @@ TN: -SF:script/DeployCMTATWithBlacklist.s.sol -DA:11,1 -FN:11,DeployCMTATWithBlacklist.deploy -FNDA:1,DeployCMTATWithBlacklist.deploy -DA:12,1 -DA:13,1 -DA:14,1 -DA:15,1 -DA:22,1 -DA:24,1 -DA:25,1 -DA:27,1 -DA:29,1 -BRDA:29,0,0,1 -DA:30,1 -DA:31,1 -DA:35,0 -FN:35,DeployCMTATWithBlacklist.run -FNDA:0,DeployCMTATWithBlacklist.run -DA:36,0 -DA:37,0 -DA:38,0 -FNF:2 -FNH:1 -LF:16 -LH:12 -BRF:1 -BRH:1 -end_of_record -TN: -SF:script/DeployCMTATWithBlacklistAndSanctionsList.s.sol -DA:27,18 -FN:27,DeployCMTATWithBlacklistAndSanctionsList.deploy -FNDA:18,DeployCMTATWithBlacklistAndSanctionsList.deploy -DA:36,18 -DA:37,18 -DA:38,18 -DA:39,18 -DA:46,18 -DA:49,18 -DA:52,18 -DA:53,18 -DA:57,18 -DA:60,18 -DA:61,18 -DA:64,18 -DA:67,18 -BRDA:67,0,0,18 -DA:68,18 -DA:69,18 -DA:70,18 -DA:71,18 -DA:75,0 -FN:75,DeployCMTATWithBlacklistAndSanctionsList.run -FNDA:0,DeployCMTATWithBlacklistAndSanctionsList.run -DA:84,0 -DA:87,0 -DA:89,0 -FNF:2 -FNH:1 -LF:22 -LH:18 -BRF:1 -BRH:1 -end_of_record -TN: -SF:script/DeployCMTATWithWhitelist.s.sol -DA:11,1 -FN:11,DeployCMTATWithWhitelist.deploy -FNDA:1,DeployCMTATWithWhitelist.deploy -DA:15,1 -DA:16,1 -DA:17,1 -DA:18,1 -DA:25,1 -DA:27,1 -DA:28,1 -DA:30,1 -DA:32,1 -BRDA:32,0,0,1 -DA:33,1 -DA:34,1 -DA:38,0 -FN:38,DeployCMTATWithWhitelist.run -FNDA:0,DeployCMTATWithWhitelist.run -DA:39,0 -DA:40,0 -DA:41,0 -FNF:2 -FNH:1 -LF:16 -LH:12 -BRF:1 -BRH:1 -end_of_record -TN: -SF:src/mocks/AggregatorV3Mock.sol -DA:24,601 -FN:24,AggregatorV3Mock.constructor -FNDA:601,AggregatorV3Mock.constructor -DA:25,601 -DA:26,601 -DA:27,601 -DA:28,601 -DA:39,264 -FN:39,AggregatorV3Mock.setAnswer -FNDA:264,AggregatorV3Mock.setAnswer -DA:40,264 -DA:41,264 -DA:42,264 -DA:49,1 -FN:49,AggregatorV3Mock.setUpdatedAt -FNDA:1,AggregatorV3Mock.setUpdatedAt -DA:50,1 -DA:57,7 -FN:57,AggregatorV3Mock.setDecimals -FNDA:7,AggregatorV3Mock.setDecimals -DA:58,7 -DA:65,2 -FN:65,AggregatorV3Mock.setRevertOnDecimals -FNDA:2,AggregatorV3Mock.setRevertOnDecimals -DA:66,2 -DA:73,2 -FN:73,AggregatorV3Mock.setRevertOnLatestRoundData -FNDA:2,AggregatorV3Mock.setRevertOnLatestRoundData -DA:74,2 -DA:80,1861 -FN:80,AggregatorV3Mock.decimals -FNDA:1861,AggregatorV3Mock.decimals -DA:81,1861 -BRDA:81,0,0,3 -BRDA:81,0,1,1858 -DA:82,1858 -DA:88,1 -FN:88,AggregatorV3Mock.description -FNDA:1,AggregatorV3Mock.description -DA:89,1 -DA:95,1 -FN:95,AggregatorV3Mock.version -FNDA:1,AggregatorV3Mock.version -DA:96,1 -DA:102,1 -FN:102,AggregatorV3Mock.getRoundData -FNDA:1,AggregatorV3Mock.getRoundData -DA:108,1 -DA:114,1247 -FN:114,AggregatorV3Mock.latestRoundData -FNDA:1247,AggregatorV3Mock.latestRoundData -DA:120,1247 -BRDA:120,1,0,2 -BRDA:120,1,1,1245 -DA:121,1245 -FNF:11 -FNH:11 -LF:29 -LH:29 -BRF:4 -BRH:4 -end_of_record -TN: -SF:src/mocks/ERC3643TokenMock.sol -DA:67,27 -FN:67,ERC3643TokenMock.constructor -FNDA:27,ERC3643TokenMock.constructor -DA:68,27 -DA:69,27 -DA:73,25 -FN:73,ERC3643TokenMock.onlyAgent -FNDA:25,ERC3643TokenMock.onlyAgent -DA:74,25 -BRDA:74,0,0,- -BRDA:74,0,1,6 -DA:86,0 -FN:86,ERC3643TokenMock.setIdentityRegistry -FNDA:0,ERC3643TokenMock.setIdentityRegistry -DA:87,0 -DA:97,13 -FN:97,ERC3643TokenMock.setCompliance -FNDA:13,ERC3643TokenMock.setCompliance -DA:98,13 -BRDA:98,1,0,- -DA:99,0 -DA:101,13 -DA:102,13 -DA:110,0 -FN:110,ERC3643TokenMock.setAgent -FNDA:0,ERC3643TokenMock.setAgent -DA:111,0 -DA:122,7 -FN:122,ERC3643TokenMock.transfer -FNDA:7,ERC3643TokenMock.transfer -DA:123,7 -BRDA:123,2,0,- -BRDA:123,2,1,7 -DA:124,7 -BRDA:124,3,0,3 -DA:125,3 -DA:126,3 -DA:127,3 -DA:129,4 -DA:140,4 -FN:140,ERC3643TokenMock.transferFrom -FNDA:4,ERC3643TokenMock.transferFrom -DA:141,4 -BRDA:141,4,0,- -BRDA:141,4,1,4 -DA:142,4 -BRDA:142,5,0,2 -DA:143,2 -DA:144,2 -DA:145,2 -DA:147,2 -DA:158,4 -FN:158,ERC3643TokenMock.forcedTransfer -FNDA:4,ERC3643TokenMock.forcedTransfer -DA:159,6 -BRDA:159,6,0,- -BRDA:159,6,1,6 -DA:163,6 -BRDA:163,7,0,5 -DA:164,5 -DA:165,5 -DA:166,4 -DA:168,1 -DA:177,25 -FN:177,ERC3643TokenMock.mint -FNDA:25,ERC3643TokenMock.mint -DA:178,25 -BRDA:178,8,0,2 -BRDA:178,8,1,23 -DA:179,23 -BRDA:179,9,0,1 -BRDA:179,9,1,22 -DA:180,22 -DA:181,22 -DA:182,22 -DA:183,22 -BRDA:183,10,0,10 -DA:184,10 -DA:194,3 -FN:194,ERC3643TokenMock.burn -FNDA:3,ERC3643TokenMock.burn -DA:195,3 -BRDA:195,11,0,- -BRDA:195,11,1,3 -DA:196,3 -DA:197,3 -DA:198,3 -DA:199,3 -BRDA:199,12,0,2 -DA:200,2 -DA:219,4 -FN:219,ERC3643TokenMock.recoveryAddress -FNDA:4,ERC3643TokenMock.recoveryAddress -DA:224,4 -BRDA:224,13,0,- -BRDA:224,13,1,4 -DA:226,4 -DA:227,4 -BRDA:227,14,0,3 -DA:228,3 -DA:229,3 -DA:232,2 -DA:233,2 -DA:234,2 -DA:235,2 -DA:237,1 -DA:251,31 -FN:251,ERC3643TokenMock._canTransfer -FNDA:31,ERC3643TokenMock._canTransfer -DA:252,31 -BRDA:252,15,0,15 -DA:253,15 -DA:255,16 -DA:264,10 -FN:264,ERC3643TokenMock._complianceTransferred -FNDA:10,ERC3643TokenMock._complianceTransferred -DA:265,10 -BRDA:265,16,0,4 -DA:266,4 -DA:276,10 -FN:276,ERC3643TokenMock._transfer -FNDA:10,ERC3643TokenMock._transfer -DA:277,10 -DA:278,10 -DA:279,10 -FNF:14 -FNH:12 -LF:72 -LH:67 -BRF:25 -BRH:18 -end_of_record -TN: -SF:src/mocks/IAddressListInterfaceIdHelper.sol -DA:88,1 -FN:88,IAddressListInterfaceIdHelper.getIAddressListInterfaceId -FNDA:1,IAddressListInterfaceIdHelper.getIAddressListInterfaceId -DA:89,1 -DA:96,0 -FN:96,IAddressListInterfaceIdHelper.getIAddressListAllFunctionsInterfaceId -FNDA:0,IAddressListInterfaceIdHelper.getIAddressListAllFunctionsInterfaceId -DA:97,0 -DA:104,0 -FN:104,IAddressListInterfaceIdHelper.getAddressListInterfaceIdConstant -FNDA:0,IAddressListInterfaceIdHelper.getAddressListInterfaceIdConstant -DA:105,0 -DA:112,1 -FN:112,IAddressListInterfaceIdHelper.getIIdentityRegistryContainsInterfaceId -FNDA:1,IAddressListInterfaceIdHelper.getIIdentityRegistryContainsInterfaceId -DA:113,1 -FNF:4 -FNH:2 -LF:8 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/IdentityRegistryMock.sol -DA:25,35 -FN:25,IdentityRegistryMock.setVerified -FNDA:35,IdentityRegistryMock.setVerified -DA:26,35 -DA:34,60 -FN:34,IdentityRegistryMock.isVerified -FNDA:60,IdentityRegistryMock.isVerified -DA:35,60 -FNF:2 -FNH:2 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/MockERC20TransferFromFalse.sol -DA:25,1 -FN:25,MockERC20TransferFromFalse.setAllowance -FNDA:1,MockERC20TransferFromFalse.setAllowance -DA:26,1 -DA:35,1 -FN:35,MockERC20TransferFromFalse.allowance -FNDA:1,MockERC20TransferFromFalse.allowance -DA:36,1 -DA:43,1 -FN:43,MockERC20TransferFromFalse.transferFrom -FNDA:1,MockERC20TransferFromFalse.transferFrom -DA:44,1 -FNF:3 -FNH:3 -LF:6 -LH:6 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/MockERC20WithTransferContext.sol -DA:37,19 -FN:37,MockERC20WithTransferContext.setRule -FNDA:19,MockERC20WithTransferContext.setRule -DA:38,19 -DA:46,19 -FN:46,MockERC20WithTransferContext.mint -FNDA:19,MockERC20WithTransferContext.mint -DA:47,19 -DA:58,3 -FN:58,MockERC20WithTransferContext.transferWithContext -FNDA:3,MockERC20WithTransferContext.transferWithContext -DA:62,3 -DA:63,2 -BRDA:63,0,0,2 -BRDA:63,0,1,1 -DA:64,2 -DA:66,1 -DA:68,3 -DA:80,3 -FN:80,MockERC20WithTransferContext.transferFromWithContext -FNDA:3,MockERC20WithTransferContext.transferFromWithContext -DA:84,3 -DA:85,3 -DA:86,3 -DA:88,2 -BRDA:88,1,0,2 -BRDA:88,1,1,1 -DA:89,2 -DA:91,1 -DA:93,3 -DA:103,3 -FN:103,MockERC20WithTransferContext.transfer -FNDA:3,MockERC20WithTransferContext.transfer -DA:104,3 -DA:105,3 -DA:106,2 -DA:112,5 -FN:112,MockERC20WithTransferContext.transferFrom -FNDA:5,MockERC20WithTransferContext.transferFrom -DA:113,5 -DA:114,5 -DA:115,5 -DA:116,4 -DA:131,12 -FN:131,MockERC20WithTransferContext._notifyFungible -FNDA:12,MockERC20WithTransferContext._notifyFungible -DA:132,12 -BRDA:132,2,0,12 -DA:133,12 -DA:136,12 -DA:144,12 -DA:156,2 -FN:156,MockERC20WithTransferContext._notifyMultiToken -FNDA:2,MockERC20WithTransferContext._notifyMultiToken -DA:157,2 -BRDA:157,3,0,2 -DA:158,2 -DA:161,2 -DA:170,2 -FNF:8 -FNH:8 -LF:37 -LH:37 -BRF:6 -BRH:6 -end_of_record -TN: -SF:src/mocks/MockERC721WithTransferContext.sol -DA:37,3 -FN:37,MockERC721WithTransferContext.setRule -FNDA:3,MockERC721WithTransferContext.setRule -DA:38,3 -DA:46,3 -FN:46,MockERC721WithTransferContext.mint -FNDA:3,MockERC721WithTransferContext.mint -DA:47,3 -DA:57,4 -FN:57,MockERC721WithTransferContext.transferFrom -FNDA:4,MockERC721WithTransferContext.transferFrom -DA:58,4 -DA:59,4 -DA:60,4 -DA:75,4 -FN:75,MockERC721WithTransferContext._notifyRule -FNDA:4,MockERC721WithTransferContext._notifyRule -DA:76,4 -BRDA:76,0,0,4 -DA:77,4 -DA:80,4 -DA:89,4 -FNF:4 -FNH:4 -LF:13 -LH:13 -BRF:1 -BRH:1 -end_of_record -TN: -SF:src/mocks/OnchainIdMock.sol -DA:25,3 -FN:25,OnchainIdMock.addWalletKey -FNDA:3,OnchainIdMock.addWalletKey -DA:26,3 -DA:32,4 -FN:32,OnchainIdMock.keyHasPurpose -FNDA:4,OnchainIdMock.keyHasPurpose -DA:33,4 -FNF:2 -FNH:2 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/SanctionListOracle.sol -DA:24,24 -FN:24,SanctionListOracle.addToSanctionsList -FNDA:24,SanctionListOracle.addToSanctionsList -DA:25,24 -DA:32,1 -FN:32,SanctionListOracle.removeFromSanctionsList -FNDA:1,SanctionListOracle.removeFromSanctionsList -DA:33,1 -DA:41,267 -FN:41,SanctionListOracle.isSanctioned -FNDA:267,SanctionListOracle.isSanctioned -DA:42,267 -FNF:3 -FNH:3 -LF:6 -LH:6 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/TotalSupplyDecimalsMock.sol -DA:18,593 -FN:18,TotalSupplyDecimalsMock.constructor -FNDA:593,TotalSupplyDecimalsMock.constructor -DA:19,593 -DA:30,554 -FN:30,TotalSupplyDecimalsMock.setTotalSupply -FNDA:554,TotalSupplyDecimalsMock.setTotalSupply -DA:31,554 -DA:38,2 -FN:38,TotalSupplyDecimalsMock.setRevertOnTotalSupply -FNDA:2,TotalSupplyDecimalsMock.setRevertOnTotalSupply -DA:39,2 -DA:46,1033 -FN:46,TotalSupplyDecimalsMock.totalSupply -FNDA:1033,TotalSupplyDecimalsMock.totalSupply -DA:47,1033 -BRDA:47,0,0,4 -BRDA:47,0,1,1029 -DA:48,1029 -DA:55,594 -FN:55,TotalSupplyDecimalsMock.decimals -FNDA:594,TotalSupplyDecimalsMock.decimals -DA:56,594 -FNF:5 -FNH:5 -LF:11 -LH:11 -BRF:2 -BRH:2 -end_of_record -TN: -SF:src/mocks/TotalSupplyMock.sol -DA:22,781 -FN:22,TotalSupplyMock.setTotalSupply -FNDA:781,TotalSupplyMock.setTotalSupply -DA:23,781 -DA:30,1333 -FN:30,TotalSupplyMock.totalSupply -FNDA:1333,TotalSupplyMock.totalSupply -DA:31,1333 -FNF:2 -FNH:2 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/harness/DeploymentCoverageHarnesses.sol -DA:38,1 -FN:38,RuleBlacklistHarness.exposedMsgDataLength.0 -FNDA:1,RuleBlacklistHarness.exposedMsgDataLength.0 -DA:39,1 -DA:70,1 -FN:70,RuleWhitelistHarness.exposedMsgDataLength.1 -FNDA:1,RuleWhitelistHarness.exposedMsgDataLength.1 -DA:71,1 -DA:102,1 -FN:102,RuleWhitelistWrapperHarness.exposedMsgDataLength.2 -FNDA:1,RuleWhitelistWrapperHarness.exposedMsgDataLength.2 -DA:103,1 -DA:133,1 -FN:133,RuleERC2980Harness.exposedMsgDataLength.3 -FNDA:1,RuleERC2980Harness.exposedMsgDataLength.3 -DA:134,1 -DA:164,1 -FN:164,RuleSanctionsListHarness.exposedMsgDataLength.4 -FNDA:1,RuleSanctionsListHarness.exposedMsgDataLength.4 -DA:165,1 -DA:192,1 -FN:192,RuleBlacklistOwnable2StepHarness.exposedMsgDataLength.5 -FNDA:1,RuleBlacklistOwnable2StepHarness.exposedMsgDataLength.5 -DA:193,1 -DA:224,1 -FN:224,RuleWhitelistOwnable2StepHarness.exposedMsgDataLength.6 -FNDA:1,RuleWhitelistOwnable2StepHarness.exposedMsgDataLength.6 -DA:225,1 -DA:256,1 -FN:256,RuleWhitelistWrapperOwnable2StepHarness.exposedMsgDataLength.7 -FNDA:1,RuleWhitelistWrapperOwnable2StepHarness.exposedMsgDataLength.7 -DA:257,1 -DA:287,1 -FN:287,RuleERC2980Ownable2StepHarness.exposedMsgDataLength.8 -FNDA:1,RuleERC2980Ownable2StepHarness.exposedMsgDataLength.8 -DA:288,1 -FNF:9 -FNH:9 -LF:18 -LH:18 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/harness/RuleReceiverWhitelistHarnesses.sol -DA:32,1 -FN:32,RuleReceiverWhitelistHarness.exposedMsgSender.0 -FNDA:1,RuleReceiverWhitelistHarness.exposedMsgSender.0 -DA:33,1 -DA:40,1 -FN:40,RuleReceiverWhitelistHarness.exposedMsgData.0 -FNDA:1,RuleReceiverWhitelistHarness.exposedMsgData.0 -DA:41,1 -DA:48,1 -FN:48,RuleReceiverWhitelistHarness.exposedContextSuffixLength.0 -FNDA:1,RuleReceiverWhitelistHarness.exposedContextSuffixLength.0 -DA:49,1 -DA:78,1 -FN:78,RuleReceiverWhitelistOwnable2StepHarness.exposedMsgSender.1 -FNDA:1,RuleReceiverWhitelistOwnable2StepHarness.exposedMsgSender.1 -DA:79,1 -DA:86,1 -FN:86,RuleReceiverWhitelistOwnable2StepHarness.exposedMsgData.1 -FNDA:1,RuleReceiverWhitelistOwnable2StepHarness.exposedMsgData.1 -DA:87,1 -DA:94,1 -FN:94,RuleReceiverWhitelistOwnable2StepHarness.exposedContextSuffixLength.1 -FNDA:1,RuleReceiverWhitelistOwnable2StepHarness.exposedContextSuffixLength.1 -DA:95,1 -FNF:6 -FNH:6 -LF:12 -LH:12 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/harness/RuleSanctionsListOwnable2StepHarness.sol -DA:33,1 -FN:33,RuleSanctionsListOwnable2StepHarness.exposedMsgSender -FNDA:1,RuleSanctionsListOwnable2StepHarness.exposedMsgSender -DA:34,1 -DA:41,1 -FN:41,RuleSanctionsListOwnable2StepHarness.exposedMsgData -FNDA:1,RuleSanctionsListOwnable2StepHarness.exposedMsgData -DA:42,1 -DA:49,1 -FN:49,RuleSanctionsListOwnable2StepHarness.exposedContextSuffixLength -FNDA:1,RuleSanctionsListOwnable2StepHarness.exposedContextSuffixLength -DA:50,1 -FNF:3 -FNH:3 -LF:6 -LH:6 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/harness/RuleSpenderWhitelistHarnesses.sol -DA:30,1 -FN:30,RuleSpenderWhitelistHarness.exposedMsgSender.0 -FNDA:1,RuleSpenderWhitelistHarness.exposedMsgSender.0 -DA:31,1 -DA:38,1 -FN:38,RuleSpenderWhitelistHarness.exposedMsgData.0 -FNDA:1,RuleSpenderWhitelistHarness.exposedMsgData.0 -DA:39,1 -DA:46,1 -FN:46,RuleSpenderWhitelistHarness.exposedContextSuffixLength.0 -FNDA:1,RuleSpenderWhitelistHarness.exposedContextSuffixLength.0 -DA:47,1 -DA:76,1 -FN:76,RuleSpenderWhitelistOwnable2StepHarness.exposedMsgSender.1 -FNDA:1,RuleSpenderWhitelistOwnable2StepHarness.exposedMsgSender.1 -DA:77,1 -DA:84,1 -FN:84,RuleSpenderWhitelistOwnable2StepHarness.exposedMsgData.1 -FNDA:1,RuleSpenderWhitelistOwnable2StepHarness.exposedMsgData.1 -DA:85,1 -DA:92,1 -FN:92,RuleSpenderWhitelistOwnable2StepHarness.exposedContextSuffixLength.1 -FNDA:1,RuleSpenderWhitelistOwnable2StepHarness.exposedContextSuffixLength.1 -DA:93,1 -FNF:6 -FNH:6 -LF:12 -LH:12 -BRF:0 -BRH:0 -end_of_record -TN: -SF:src/mocks/harness/RuleWhitelistWrapperHarnessInternal.sol -DA:36,1 -FN:36,RuleWhitelistWrapperHarnessInternal.exposedTransferredSpenderInternal -FNDA:1,RuleWhitelistWrapperHarnessInternal.exposedTransferredSpenderInternal -DA:37,1 -FNF:1 -FNH:1 -LF:2 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: SF:src/modules/AccessControlModuleStandalone.sol -DA:30,2308 +DA:30,2595 FN:30,AccessControlModuleStandalone.constructor -FNDA:2308,AccessControlModuleStandalone.constructor -DA:31,2308 +FNDA:2595,AccessControlModuleStandalone.constructor +DA:31,2595 BRDA:31,0,0,7 -BRDA:31,0,1,2301 -DA:35,2301 -DA:46,2432 +BRDA:31,0,1,2588 +DA:35,2588 +DA:46,2731 FN:46,AccessControlModuleStandalone.hasRole -FNDA:2432,AccessControlModuleStandalone.hasRole -DA:55,22060 -BRDA:55,1,0,4187 -BRDA:55,1,1,17873 -DA:56,4187 -DA:58,17873 +FNDA:2731,AccessControlModuleStandalone.hasRole +DA:55,23039 +BRDA:55,1,0,4834 +BRDA:55,1,1,18205 +DA:56,4834 +DA:58,18205 FNF:2 FNH:2 LF:7 @@ -712,12 +24,12 @@ BRH:4 end_of_record TN: SF:src/modules/Ownable2StepERC165Module.sol -DA:17,78 +DA:17,81 FN:17,Ownable2StepERC165Module.supportsInterface -FNDA:78,Ownable2StepERC165Module.supportsInterface -DA:18,78 -DA:19,66 -DA:20,54 +FNDA:81,Ownable2StepERC165Module.supportsInterface +DA:18,81 +DA:19,69 +DA:20,57 FNF:1 FNH:1 LF:4 @@ -727,10 +39,10 @@ BRH:0 end_of_record TN: SF:src/modules/VersionModule.sol -DA:23,7 +DA:23,20 FN:23,VersionModule.version -FNDA:7,VersionModule.version -DA:24,7 +FNDA:20,VersionModule.version +DA:24,20 FNF:1 FNH:1 LF:2 @@ -740,9 +52,9 @@ BRH:0 end_of_record TN: SF:src/registry/IdentityRegistryWhitelist.sol -DA:34,85 +DA:34,107 FN:34,IdentityRegistryWhitelist._authorizeIdentityRegistrar -FNDA:85,IdentityRegistryWhitelist._authorizeIdentityRegistrar +FNDA:107,IdentityRegistryWhitelist._authorizeIdentityRegistrar FNF:1 FNH:1 LF:1 @@ -752,48 +64,46 @@ BRH:0 end_of_record TN: SF:src/registry/abstract/IdentityRegistryWhitelistBase.sol -DA:69,78 -FN:69,IdentityRegistryWhitelistBase.registerIdentity -FNDA:78,IdentityRegistryWhitelistBase.registerIdentity -DA:81,76 -BRDA:81,0,0,1 -BRDA:81,0,1,75 -DA:82,75 -BRDA:82,1,0,1 -BRDA:82,1,1,74 -DA:83,74 -DA:84,74 -DA:91,7 -FN:91,IdentityRegistryWhitelistBase.deleteIdentity -FNDA:7,IdentityRegistryWhitelistBase.deleteIdentity -DA:92,6 -BRDA:92,2,0,1 -BRDA:92,2,1,5 -DA:93,5 -DA:94,5 -DA:103,3 -FN:103,IdentityRegistryWhitelistBase.registeredIdentityCount +DA:46,96 +FN:46,IdentityRegistryWhitelistBase.registerIdentity +FNDA:96,IdentityRegistryWhitelistBase.registerIdentity +DA:58,93 +BRDA:58,0,0,1 +BRDA:58,0,1,92 +DA:59,92 +BRDA:59,1,0,1 +BRDA:59,1,1,91 +DA:60,91 +DA:67,11 +FN:67,IdentityRegistryWhitelistBase.deleteIdentity +FNDA:11,IdentityRegistryWhitelistBase.deleteIdentity +DA:68,10 +BRDA:68,2,0,1 +BRDA:68,2,1,9 +DA:69,9 +DA:78,3 +FN:78,IdentityRegistryWhitelistBase.registeredIdentityCount FNDA:3,IdentityRegistryWhitelistBase.registeredIdentityCount -DA:104,3 -DA:117,54 -FN:117,IdentityRegistryWhitelistBase.isVerified -FNDA:54,IdentityRegistryWhitelistBase.isVerified -DA:118,54 -DA:127,7 -FN:127,IdentityRegistryWhitelistBase.investorCountry +DA:79,3 +DA:92,84 +FN:92,IdentityRegistryWhitelistBase.isVerified +FNDA:84,IdentityRegistryWhitelistBase.isVerified +DA:93,84 +DA:102,7 +FN:102,IdentityRegistryWhitelistBase.investorCountry FNDA:7,IdentityRegistryWhitelistBase.investorCountry -DA:136,7 -DA:143,78 -FN:143,IdentityRegistryWhitelistBase.onlyIdentityRegistrar -FNDA:78,IdentityRegistryWhitelistBase.onlyIdentityRegistrar -DA:144,78 -DA:152,0 -FN:152,IdentityRegistryWhitelistBase._authorizeIdentityRegistrar +DA:111,7 +DA:118,96 +FN:118,IdentityRegistryWhitelistBase.onlyIdentityRegistrar +FNDA:96,IdentityRegistryWhitelistBase.onlyIdentityRegistrar +DA:119,96 +DA:127,0 +FN:127,IdentityRegistryWhitelistBase._authorizeIdentityRegistrar FNDA:0,IdentityRegistryWhitelistBase._authorizeIdentityRegistrar FNF:7 FNH:6 -LF:18 -LH:17 +LF:16 +LH:15 BRF:6 BRH:6 end_of_record @@ -807,15 +117,15 @@ DA:49,47 DA:50,46 DA:51,30 DA:52,29 -DA:62,58 +DA:62,69 FN:62,RuleConditionalTransferLight._onlyComplianceManager -FNDA:58,RuleConditionalTransferLight._onlyComplianceManager -DA:67,7593 +FNDA:69,RuleConditionalTransferLight._onlyComplianceManager +DA:67,7637 FN:67,RuleConditionalTransferLight._authorizeTransferApproval -FNDA:7593,RuleConditionalTransferLight._authorizeTransferApproval +FNDA:7637,RuleConditionalTransferLight._authorizeTransferApproval DA:72,3 -FN:72,RuleConditionalTransferLight._authorizeComplianceBindingChange -FNDA:3,RuleConditionalTransferLight._authorizeComplianceBindingChange +FN:72,RuleConditionalTransferLight._authorizeTokenBindingChange +FNDA:3,RuleConditionalTransferLight._authorizeTokenBindingChange FNF:4 FNH:4 LF:9 @@ -833,12 +143,12 @@ DA:41,6 DA:42,6 DA:43,4 DA:44,4 -DA:50,38 +DA:50,52 FN:50,RuleConditionalTransferLightMultiToken._onlyComplianceManager -FNDA:38,RuleConditionalTransferLightMultiToken._onlyComplianceManager -DA:55,31 +FNDA:52,RuleConditionalTransferLightMultiToken._onlyComplianceManager +DA:55,43 FN:55,RuleConditionalTransferLightMultiToken._authorizeTransferApproval -FNDA:31,RuleConditionalTransferLightMultiToken._authorizeTransferApproval +FNDA:43,RuleConditionalTransferLightMultiToken._authorizeTransferApproval FNF:3 FNH:3 LF:8 @@ -856,16 +166,16 @@ DA:40,2 DA:41,2 DA:42,2 DA:43,2 -DA:49,0 +DA:49,6 FN:49,RuleConditionalTransferLightMultiTokenOwnable2Step._onlyComplianceManager -FNDA:0,RuleConditionalTransferLightMultiTokenOwnable2Step._onlyComplianceManager -DA:54,0 +FNDA:6,RuleConditionalTransferLightMultiTokenOwnable2Step._onlyComplianceManager +DA:54,2 FN:54,RuleConditionalTransferLightMultiTokenOwnable2Step._authorizeTransferApproval -FNDA:0,RuleConditionalTransferLightMultiTokenOwnable2Step._authorizeTransferApproval +FNDA:2,RuleConditionalTransferLightMultiTokenOwnable2Step._authorizeTransferApproval FNF:3 -FNH:1 +FNH:3 LF:8 -LH:6 +LH:8 BRF:0 BRH:0 end_of_record @@ -879,40 +189,40 @@ DA:47,8 DA:48,7 DA:49,6 DA:50,4 -DA:60,3 +DA:60,5 FN:60,RuleConditionalTransferLightOwnable2Step._onlyComplianceManager -FNDA:3,RuleConditionalTransferLightOwnable2Step._onlyComplianceManager +FNDA:5,RuleConditionalTransferLightOwnable2Step._onlyComplianceManager DA:65,4 FN:65,RuleConditionalTransferLightOwnable2Step._authorizeTransferApproval FNDA:4,RuleConditionalTransferLightOwnable2Step._authorizeTransferApproval -DA:70,0 -FN:70,RuleConditionalTransferLightOwnable2Step._authorizeComplianceBindingChange -FNDA:0,RuleConditionalTransferLightOwnable2Step._authorizeComplianceBindingChange +DA:70,2 +FN:70,RuleConditionalTransferLightOwnable2Step._authorizeTokenBindingChange +FNDA:2,RuleConditionalTransferLightOwnable2Step._authorizeTokenBindingChange FNF:4 -FNH:3 +FNH:4 LF:9 -LH:8 +LH:9 BRF:0 BRH:0 end_of_record TN: SF:src/rules/operation/RuleMintAllowance.sol -DA:38,34 +DA:38,37 FN:38,RuleMintAllowance.supportsInterface -FNDA:34,RuleMintAllowance.supportsInterface -DA:47,34 -DA:48,33 -DA:49,32 -DA:50,21 -DA:60,312 +FNDA:37,RuleMintAllowance.supportsInterface +DA:47,37 +DA:48,36 +DA:49,35 +DA:50,23 +DA:60,313 FN:60,RuleMintAllowance._onlyComplianceManager -FNDA:312,RuleMintAllowance._onlyComplianceManager -DA:65,10087 +FNDA:313,RuleMintAllowance._onlyComplianceManager +DA:65,10089 FN:65,RuleMintAllowance._authorizeSetMintAllowance -FNDA:10087,RuleMintAllowance._authorizeSetMintAllowance +FNDA:10089,RuleMintAllowance._authorizeSetMintAllowance DA:70,4 -FN:70,RuleMintAllowance._authorizeComplianceBindingChange -FNDA:4,RuleMintAllowance._authorizeComplianceBindingChange +FN:70,RuleMintAllowance._authorizeTokenBindingChange +FNDA:4,RuleMintAllowance._authorizeTokenBindingChange FNF:4 FNH:4 LF:8 @@ -936,8 +246,8 @@ DA:62,6 FN:62,RuleMintAllowanceOwnable2Step._authorizeSetMintAllowance FNDA:6,RuleMintAllowanceOwnable2Step._authorizeSetMintAllowance DA:67,2 -FN:67,RuleMintAllowanceOwnable2Step._authorizeComplianceBindingChange -FNDA:2,RuleMintAllowanceOwnable2Step._authorizeComplianceBindingChange +FN:67,RuleMintAllowanceOwnable2Step._authorizeTokenBindingChange +FNDA:2,RuleMintAllowanceOwnable2Step._authorizeTokenBindingChange FNF:4 FNH:4 LF:8 @@ -951,203 +261,208 @@ DA:22,4 FN:22,RuleConditionalTransferLightApprovalBase.onlyTransferApprover FNDA:4,RuleConditionalTransferLightApprovalBase.onlyTransferApprover DA:23,4 -DA:27,3 +DA:27,6 FN:27,RuleConditionalTransferLightApprovalBase.onlyTransferExecutor -FNDA:3,RuleConditionalTransferLightApprovalBase.onlyTransferExecutor -DA:28,3 -DA:40,3 +FNDA:6,RuleConditionalTransferLightApprovalBase.onlyTransferExecutor +DA:28,6 +DA:40,6 FN:40,RuleConditionalTransferLightApprovalBase.transferred -FNDA:3,RuleConditionalTransferLightApprovalBase.transferred -DA:41,3 -DA:54,6211 +FNDA:6,RuleConditionalTransferLightApprovalBase.transferred +DA:41,6 +DA:54,6242 FN:54,RuleConditionalTransferLightApprovalBase.approveTransfer -FNDA:6211,RuleConditionalTransferLightApprovalBase.approveTransfer -DA:55,6214 -DA:56,6214 -DA:57,6214 -DA:66,1371 -FN:66,RuleConditionalTransferLightApprovalBase.cancelTransferApproval -FNDA:1371,RuleConditionalTransferLightApprovalBase.cancelTransferApproval -DA:67,1370 -DA:68,1370 -DA:69,1370 -BRDA:69,0,0,1 -BRDA:69,0,1,1369 -DA:70,1369 -DA:71,1369 -DA:86,4 -FN:86,RuleConditionalTransferLightApprovalBase.resetApproval +FNDA:6242,RuleConditionalTransferLightApprovalBase.approveTransfer +DA:55,6249 +DA:56,6249 +DA:57,6249 +DA:58,6249 +DA:67,1376 +FN:67,RuleConditionalTransferLightApprovalBase.cancelTransferApproval +FNDA:1376,RuleConditionalTransferLightApprovalBase.cancelTransferApproval +DA:68,1375 +DA:69,1375 +DA:70,1375 +BRDA:70,0,0,1 +BRDA:70,0,1,1374 +DA:71,1374 +DA:72,1374 +DA:87,4 +FN:87,RuleConditionalTransferLightApprovalBase.resetApproval FNDA:4,RuleConditionalTransferLightApprovalBase.resetApproval -DA:92,3 DA:93,3 DA:94,3 -BRDA:94,1,0,1 -BRDA:94,1,1,2 -DA:95,2 +DA:95,3 +BRDA:95,1,0,1 +BRDA:95,1,1,2 DA:96,2 -DA:106,9207 -FN:106,RuleConditionalTransferLightApprovalBase.approvedCount -FNDA:9207,RuleConditionalTransferLightApprovalBase.approvedCount -DA:107,9207 -DA:108,9207 -DA:119,3 -FN:119,RuleConditionalTransferLightApprovalBase._transferredFromContext -FNDA:3,RuleConditionalTransferLightApprovalBase._transferredFromContext -DA:120,3 -DA:130,6258 -FN:130,RuleConditionalTransferLightApprovalBase._transferred -FNDA:6258,RuleConditionalTransferLightApprovalBase._transferred -DA:131,6258 -BRDA:131,2,0,6258 -DA:132,6258 -DA:134,2217 -DA:135,2217 -DA:137,2217 -BRDA:137,3,0,5 -BRDA:137,3,1,2212 -DA:139,2212 -DA:140,2212 -DA:150,19020 -FN:150,RuleConditionalTransferLightApprovalBase._transferHash -FNDA:19020,RuleConditionalTransferLightApprovalBase._transferHash -DA:153,19020 -DA:154,19020 -DA:155,19020 -DA:156,19020 -DA:157,19020 -DA:164,0 -FN:164,RuleConditionalTransferLightApprovalBase._authorizeTransferApproval +DA:97,2 +DA:107,9216 +FN:107,RuleConditionalTransferLightApprovalBase.approvedCount +FNDA:9216,RuleConditionalTransferLightApprovalBase.approvedCount +DA:108,9231 +DA:109,9231 +DA:120,6 +FN:120,RuleConditionalTransferLightApprovalBase._transferredFromContext +FNDA:6,RuleConditionalTransferLightApprovalBase._transferredFromContext +DA:121,6 +DA:131,6291 +FN:131,RuleConditionalTransferLightApprovalBase._transferred +FNDA:6291,RuleConditionalTransferLightApprovalBase._transferred +DA:132,6291 +BRDA:132,2,0,6291 +DA:133,6291 +DA:135,2246 +DA:136,2246 +DA:138,2246 +BRDA:138,3,0,5 +BRDA:138,3,1,2241 +DA:140,2241 +DA:141,2241 +DA:163,19113 +FN:163,RuleConditionalTransferLightApprovalBase._transferHash +FNDA:19113,RuleConditionalTransferLightApprovalBase._transferHash +DA:168,19113 +DA:169,19113 +DA:170,19113 +DA:171,19113 +DA:172,19113 +DA:179,0 +FN:179,RuleConditionalTransferLightApprovalBase._authorizeTransferApproval FNDA:0,RuleConditionalTransferLightApprovalBase._authorizeTransferApproval -DA:169,0 -FN:169,RuleConditionalTransferLightApprovalBase._authorizeTransferExecution +DA:184,0 +FN:184,RuleConditionalTransferLightApprovalBase._authorizeTransferExecution FNDA:0,RuleConditionalTransferLightApprovalBase._authorizeTransferExecution FNF:12 FNH:10 -LF:43 -LH:41 +LF:44 +LH:42 BRF:7 BRH:7 end_of_record TN: SF:src/rules/operation/abstract/RuleConditionalTransferLightBase.sol -DA:59,1 -FN:59,RuleConditionalTransferLightBase.created +DA:61,1 +FN:61,RuleConditionalTransferLightBase.created FNDA:1,RuleConditionalTransferLightBase.created -DA:60,1 -DA:68,1 -FN:68,RuleConditionalTransferLightBase.destroyed +DA:62,1 +DA:70,1 +FN:70,RuleConditionalTransferLightBase.destroyed FNDA:1,RuleConditionalTransferLightBase.destroyed -DA:69,1 -DA:75,1 -FN:75,RuleConditionalTransferLightBase.canReturnTransferRestrictionCode +DA:71,1 +DA:77,1 +FN:77,RuleConditionalTransferLightBase.canReturnTransferRestrictionCode FNDA:1,RuleConditionalTransferLightBase.canReturnTransferRestrictionCode -DA:76,1 -DA:82,2 -FN:82,RuleConditionalTransferLightBase.messageForTransferRestriction +DA:78,1 +DA:84,2 +FN:84,RuleConditionalTransferLightBase.messageForTransferRestriction FNDA:2,RuleConditionalTransferLightBase.messageForTransferRestriction -DA:88,2 -BRDA:88,0,0,1 -DA:89,1 +DA:90,2 +BRDA:90,0,0,1 DA:91,1 -DA:113,6 -FN:113,RuleConditionalTransferLightBase.approveAndTransferIfAllowed -FNDA:6,RuleConditionalTransferLightBase.approveAndTransferIfAllowed -DA:118,6 -DA:119,6 -BRDA:119,1,0,1 -BRDA:119,1,1,5 -DA:121,5 -DA:123,5 -DA:124,4 -BRDA:124,2,0,1 -BRDA:124,2,1,3 -DA:126,3 -DA:127,2 -DA:133,6252 -FN:133,RuleConditionalTransferLightBase.transferred.0 -FNDA:6252,RuleConditionalTransferLightBase.transferred.0 -DA:138,6247 -DA:144,7 -FN:144,RuleConditionalTransferLightBase.transferred.1 +DA:93,1 +DA:115,10 +FN:115,RuleConditionalTransferLightBase.approveAndTransferIfAllowed +FNDA:10,RuleConditionalTransferLightBase.approveAndTransferIfAllowed +DA:121,10 +DA:122,10 +BRDA:122,1,0,1 +BRDA:122,1,1,9 +DA:124,9 +DA:125,9 +DA:127,9 +DA:128,8 +BRDA:128,2,0,1 +BRDA:128,2,1,7 +DA:130,7 +DA:136,6 +BRDA:136,3,0,1 +BRDA:136,3,1,5 +DA:140,5 +DA:146,6283 +FN:146,RuleConditionalTransferLightBase.transferred.0 +FNDA:6283,RuleConditionalTransferLightBase.transferred.0 +DA:152,6277 +DA:158,7 +FN:158,RuleConditionalTransferLightBase.transferred.1 FNDA:7,RuleConditionalTransferLightBase.transferred.1 -DA:155,6 -DA:177,45 -FN:177,RuleConditionalTransferLightBase.bindToken -FNDA:45,RuleConditionalTransferLightBase.bindToken -DA:178,44 -BRDA:178,3,0,1 -BRDA:178,3,1,43 -DA:179,43 -DA:208,13 -FN:208,RuleConditionalTransferLightBase.bindRuleEngine +DA:170,6 +DA:192,58 +FN:192,RuleConditionalTransferLightBase.bindToken +FNDA:58,RuleConditionalTransferLightBase.bindToken +DA:198,57 +BRDA:198,4,0,1 +BRDA:198,4,1,56 +DA:199,56 +DA:218,13 +FN:218,RuleConditionalTransferLightBase.bindRuleEngine FNDA:13,RuleConditionalTransferLightBase.bindRuleEngine -DA:209,12 -BRDA:209,4,0,1 -BRDA:209,4,1,11 -DA:210,11 -BRDA:210,5,0,1 -BRDA:210,5,1,10 -DA:211,10 -DA:212,10 -DA:219,3 -FN:219,RuleConditionalTransferLightBase.unbindRuleEngine +DA:219,12 +BRDA:219,5,0,1 +BRDA:219,5,1,11 +DA:220,11 +BRDA:220,6,0,1 +BRDA:220,6,1,10 +DA:221,10 +DA:222,10 +DA:229,3 +FN:229,RuleConditionalTransferLightBase.unbindRuleEngine FNDA:3,RuleConditionalTransferLightBase.unbindRuleEngine -DA:220,2 -DA:221,2 -BRDA:221,6,0,1 -BRDA:221,6,1,1 -DA:222,1 -DA:223,1 -DA:231,8 -FN:231,RuleConditionalTransferLightBase.isTransferExecutor +DA:230,2 +DA:231,2 +BRDA:231,7,0,1 +BRDA:231,7,1,1 +DA:232,1 +DA:233,1 +DA:241,8 +FN:241,RuleConditionalTransferLightBase.isTransferExecutor FNDA:8,RuleConditionalTransferLightBase.isTransferExecutor -DA:232,6270 -DA:238,7 -FN:238,RuleConditionalTransferLightBase.detectTransferRestriction +DA:242,6301 +DA:248,7 +FN:248,RuleConditionalTransferLightBase.detectTransferRestriction FNDA:7,RuleConditionalTransferLightBase.detectTransferRestriction -DA:244,13 -BRDA:244,7,0,4 -DA:245,4 -DA:247,9 -DA:248,9 -BRDA:248,8,0,6 -DA:249,6 -DA:251,3 -DA:257,1 -FN:257,RuleConditionalTransferLightBase.detectTransferRestrictionFrom +DA:254,13 +BRDA:254,8,0,4 +DA:255,4 +DA:257,9 +DA:258,9 +BRDA:258,9,0,6 +DA:259,6 +DA:261,3 +DA:267,1 +FN:267,RuleConditionalTransferLightBase.detectTransferRestrictionFrom FNDA:1,RuleConditionalTransferLightBase.detectTransferRestrictionFrom -DA:269,2 -DA:275,4 -FN:275,RuleConditionalTransferLightBase.canTransfer +DA:279,2 +DA:285,4 +FN:285,RuleConditionalTransferLightBase.canTransfer FNDA:4,RuleConditionalTransferLightBase.canTransfer -DA:281,4 -DA:287,1 -FN:287,RuleConditionalTransferLightBase.canTransferFrom +DA:291,4 +DA:297,1 +FN:297,RuleConditionalTransferLightBase.canTransferFrom FNDA:1,RuleConditionalTransferLightBase.canTransferFrom -DA:293,1 -DA:306,6262 -FN:306,RuleConditionalTransferLightBase._authorizeTransferExecution -FNDA:6262,RuleConditionalTransferLightBase._authorizeTransferExecution -DA:307,6262 -BRDA:307,9,0,6 -BRDA:307,9,1,6256 +DA:303,1 +DA:316,6293 +FN:316,RuleConditionalTransferLightBase._authorizeTransferExecution +FNDA:6293,RuleConditionalTransferLightBase._authorizeTransferExecution +DA:317,6293 +BRDA:317,10,0,6 +BRDA:317,10,1,6287 FNF:16 FNH:16 -LF:52 -LH:52 -BRF:17 -BRH:17 +LF:54 +LH:54 +BRF:19 +BRH:19 end_of_record TN: SF:src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol -DA:34,24 +DA:34,33 FN:34,RuleConditionalTransferLightMultiTokenBase.onlyTransferApprover -FNDA:24,RuleConditionalTransferLightMultiTokenBase.onlyTransferApprover -DA:35,24 -DA:39,3 +FNDA:33,RuleConditionalTransferLightMultiTokenBase.onlyTransferApprover +DA:35,33 +DA:39,4 FN:39,RuleConditionalTransferLightMultiTokenBase.onlyTransferExecutor -FNDA:3,RuleConditionalTransferLightMultiTokenBase.onlyTransferExecutor -DA:40,3 +FNDA:4,RuleConditionalTransferLightMultiTokenBase.onlyTransferExecutor +DA:40,4 DA:49,2 FN:49,RuleConditionalTransferLightMultiTokenBase.created FNDA:2,RuleConditionalTransferLightMultiTokenBase.created @@ -1156,10 +471,10 @@ DA:58,2 FN:58,RuleConditionalTransferLightMultiTokenBase.destroyed FNDA:2,RuleConditionalTransferLightMultiTokenBase.destroyed DA:59,1 -DA:66,3 +DA:66,4 FN:66,RuleConditionalTransferLightMultiTokenBase.transferred.0 -FNDA:3,RuleConditionalTransferLightMultiTokenBase.transferred.0 -DA:67,3 +FNDA:4,RuleConditionalTransferLightMultiTokenBase.transferred.0 +DA:67,4 DA:73,2 FN:73,RuleConditionalTransferLightMultiTokenBase.canReturnTransferRestrictionCode FNDA:2,RuleConditionalTransferLightMultiTokenBase.canReturnTransferRestrictionCode @@ -1171,260 +486,261 @@ DA:86,2 BRDA:86,0,0,1 DA:87,1 DA:89,1 -DA:99,24 +DA:99,33 FN:99,RuleConditionalTransferLightMultiTokenBase.approveTransfer -FNDA:24,RuleConditionalTransferLightMultiTokenBase.approveTransfer -DA:100,24 -DA:110,3 -FN:110,RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval -FNDA:3,RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval -DA:114,2 -DA:126,1 -FN:126,RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed -FNDA:1,RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed -DA:131,1 -BRDA:131,1,0,- -BRDA:131,1,1,1 -DA:133,1 -DA:135,1 -DA:136,1 -BRDA:136,2,0,- -BRDA:136,2,1,1 -DA:140,1 -DA:141,1 -DA:147,4 -FN:147,RuleConditionalTransferLightMultiTokenBase.transferred.1 -FNDA:4,RuleConditionalTransferLightMultiTokenBase.transferred.1 -DA:152,4 -DA:158,6 -FN:158,RuleConditionalTransferLightMultiTokenBase.transferred.2 +FNDA:33,RuleConditionalTransferLightMultiTokenBase.approveTransfer +DA:104,32 +DA:114,4 +FN:114,RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval +FNDA:4,RuleConditionalTransferLightMultiTokenBase.cancelTransferApproval +DA:119,3 +DA:131,5 +FN:131,RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed +FNDA:5,RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed +DA:137,5 +BRDA:137,1,0,1 +BRDA:137,1,1,4 +DA:139,4 +DA:140,4 +DA:142,4 +DA:143,4 +BRDA:143,2,0,1 +BRDA:143,2,1,3 +DA:147,3 +DA:152,3 +BRDA:152,3,0,1 +BRDA:152,3,1,2 +DA:156,2 +DA:162,5 +FN:162,RuleConditionalTransferLightMultiTokenBase.transferred.1 +FNDA:5,RuleConditionalTransferLightMultiTokenBase.transferred.1 +DA:168,4 +DA:174,6 +FN:174,RuleConditionalTransferLightMultiTokenBase.transferred.2 FNDA:6,RuleConditionalTransferLightMultiTokenBase.transferred.2 -DA:169,6 -DA:187,3 -FN:187,RuleConditionalTransferLightMultiTokenBase.resetApproval +DA:186,6 +DA:204,3 +FN:204,RuleConditionalTransferLightMultiTokenBase.resetApproval FNDA:3,RuleConditionalTransferLightMultiTokenBase.resetApproval -DA:193,2 -DA:194,2 -DA:195,2 -BRDA:195,3,0,1 -BRDA:195,3,1,1 -DA:196,1 -DA:197,1 -DA:208,16 -FN:208,RuleConditionalTransferLightMultiTokenBase.approvedCount -FNDA:16,RuleConditionalTransferLightMultiTokenBase.approvedCount -DA:209,16 -DA:210,16 -DA:222,265 -FN:222,RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction +DA:210,2 +DA:211,2 +DA:212,2 +BRDA:212,4,0,1 +BRDA:212,4,1,1 +DA:213,1 +DA:214,1 +DA:225,24 +FN:225,RuleConditionalTransferLightMultiTokenBase.approvedCount +FNDA:24,RuleConditionalTransferLightMultiTokenBase.approvedCount +DA:226,31 +DA:227,31 +DA:239,265 +FN:239,RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction FNDA:265,RuleConditionalTransferLightMultiTokenBase.detectTransferRestriction -DA:228,270 -DA:242,263 -FN:242,RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken +DA:245,270 +DA:259,263 +FN:259,RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken FNDA:263,RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionForToken -DA:248,263 -DA:261,5 -FN:261,RuleConditionalTransferLightMultiTokenBase.canTransferForToken +DA:265,263 +DA:278,5 +FN:278,RuleConditionalTransferLightMultiTokenBase.canTransferForToken FNDA:5,RuleConditionalTransferLightMultiTokenBase.canTransferForToken -DA:267,5 -DA:274,2 -FN:274,RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom +DA:284,5 +DA:291,2 +FN:291,RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom FNDA:2,RuleConditionalTransferLightMultiTokenBase.detectTransferRestrictionFrom -DA:286,4 -DA:294,1 -FN:294,RuleConditionalTransferLightMultiTokenBase.canTransfer +DA:303,4 +DA:311,1 +FN:311,RuleConditionalTransferLightMultiTokenBase.canTransfer FNDA:1,RuleConditionalTransferLightMultiTokenBase.canTransfer -DA:300,1 -DA:306,2 -FN:306,RuleConditionalTransferLightMultiTokenBase.canTransferFrom +DA:317,1 +DA:323,2 +FN:323,RuleConditionalTransferLightMultiTokenBase.canTransferFrom FNDA:2,RuleConditionalTransferLightMultiTokenBase.canTransferFrom -DA:312,2 -DA:327,38 -FN:327,RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange -FNDA:38,RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange -DA:328,38 -DA:338,25 -FN:338,RuleConditionalTransferLightMultiTokenBase._approveTransfer -FNDA:25,RuleConditionalTransferLightMultiTokenBase._approveTransfer -DA:339,25 -BRDA:339,4,0,2 -BRDA:339,4,1,23 -DA:340,23 -DA:341,23 -DA:342,23 -DA:352,2 -FN:352,RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval -FNDA:2,RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval -DA:353,2 -BRDA:353,5,0,- -BRDA:353,5,1,2 -DA:354,2 -DA:355,2 +DA:329,2 +DA:340,36 +FN:340,RuleConditionalTransferLightMultiTokenBase._approveTransfer +FNDA:36,RuleConditionalTransferLightMultiTokenBase._approveTransfer +DA:341,36 +BRDA:341,5,0,2 +BRDA:341,5,1,34 +DA:342,34 +DA:343,34 +DA:344,34 +DA:345,34 +DA:355,3 +FN:355,RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval +FNDA:3,RuleConditionalTransferLightMultiTokenBase._cancelTransferApproval +DA:356,3 +BRDA:356,6,0,1 +BRDA:356,6,1,2 DA:357,2 -BRDA:357,6,0,1 -BRDA:357,6,1,1 -DA:359,1 -DA:360,1 -DA:371,15 -FN:371,RuleConditionalTransferLightMultiTokenBase._transferred -FNDA:15,RuleConditionalTransferLightMultiTokenBase._transferred -DA:372,15 -BRDA:372,7,0,15 -DA:373,15 -DA:376,9 -DA:377,9 -DA:379,9 -BRDA:379,8,0,3 -BRDA:379,8,1,6 -DA:381,6 -DA:382,6 -DA:397,538 -FN:397,RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken +DA:358,2 +DA:360,2 +BRDA:360,7,0,1 +BRDA:360,7,1,1 +DA:362,1 +DA:363,1 +DA:374,16 +FN:374,RuleConditionalTransferLightMultiTokenBase._transferred +FNDA:16,RuleConditionalTransferLightMultiTokenBase._transferred +DA:375,16 +BRDA:375,8,0,16 +DA:376,16 +DA:379,10 +DA:380,10 +DA:382,10 +BRDA:382,9,0,3 +BRDA:382,9,1,7 +DA:384,7 +DA:385,7 +DA:400,538 +FN:400,RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken FNDA:538,RuleConditionalTransferLightMultiTokenBase._detectTransferRestrictionForToken -DA:403,538 -BRDA:403,9,0,2 -DA:404,2 -DA:407,536 -BRDA:407,10,0,7 -DA:408,7 -DA:411,529 -BRDA:411,11,0,519 -DA:412,519 -DA:415,10 -DA:421,13 -FN:421,RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution -FNDA:13,RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution -DA:422,13 -BRDA:422,12,0,- -BRDA:422,12,1,13 -DA:431,0 -FN:431,RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval +DA:406,538 +BRDA:406,10,0,2 +DA:407,2 +DA:410,536 +BRDA:410,11,0,7 +DA:411,7 +DA:414,529 +BRDA:414,12,0,519 +DA:415,519 +DA:418,10 +DA:424,15 +FN:424,RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution +FNDA:15,RuleConditionalTransferLightMultiTokenBase._authorizeTransferExecution +DA:425,15 +BRDA:425,13,0,1 +BRDA:425,13,1,14 +DA:434,0 +FN:434,RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval FNDA:0,RuleConditionalTransferLightMultiTokenBase._authorizeTransferApproval -DA:441,581 -FN:441,RuleConditionalTransferLightMultiTokenBase._transferHash -FNDA:581,RuleConditionalTransferLightMultiTokenBase._transferHash -DA:448,581 -DA:449,581 -DA:450,581 -DA:451,581 -DA:452,581 -DA:453,581 -FNF:28 -FNH:27 -LF:92 -LH:91 -BRF:21 -BRH:17 +DA:450,608 +FN:450,RuleConditionalTransferLightMultiTokenBase._transferHash +FNDA:608,RuleConditionalTransferLightMultiTokenBase._transferHash +DA:457,608 +DA:458,608 +DA:459,608 +DA:460,608 +DA:461,608 +DA:462,608 +FNF:27 +FNH:26 +LF:93 +LH:92 +BRF:23 +BRH:23 end_of_record TN: SF:src/rules/operation/abstract/RuleMintAllowanceBase.sol -DA:51,4 -FN:51,RuleMintAllowanceBase.onlyAllowanceOperator +DA:53,4 +FN:53,RuleMintAllowanceBase.onlyAllowanceOperator FNDA:4,RuleMintAllowanceBase.onlyAllowanceOperator -DA:52,4 -DA:63,1 -FN:63,RuleMintAllowanceBase.created +DA:54,4 +DA:65,1 +FN:65,RuleMintAllowanceBase.created FNDA:1,RuleMintAllowanceBase.created -DA:68,1 -FN:68,RuleMintAllowanceBase.destroyed +DA:70,1 +FN:70,RuleMintAllowanceBase.destroyed FNDA:1,RuleMintAllowanceBase.destroyed -DA:73,2 -FN:73,RuleMintAllowanceBase.canReturnTransferRestrictionCode +DA:75,2 +FN:75,RuleMintAllowanceBase.canReturnTransferRestrictionCode FNDA:2,RuleMintAllowanceBase.canReturnTransferRestrictionCode -DA:74,2 -DA:86,3753 -FN:86,RuleMintAllowanceBase.setMintAllowance +DA:76,2 +DA:88,3753 +FN:88,RuleMintAllowanceBase.setMintAllowance FNDA:3753,RuleMintAllowanceBase.setMintAllowance -DA:87,3751 -DA:95,3241 -FN:95,RuleMintAllowanceBase.increaseMintAllowance +DA:89,3751 +DA:97,3241 +FN:97,RuleMintAllowanceBase.increaseMintAllowance FNDA:3241,RuleMintAllowanceBase.increaseMintAllowance -DA:96,3240 -DA:97,3240 DA:98,3240 -DA:107,3095 -FN:107,RuleMintAllowanceBase.decreaseMintAllowance -FNDA:3095,RuleMintAllowanceBase.decreaseMintAllowance -DA:108,3094 -DA:109,3094 -BRDA:109,0,0,1 -BRDA:109,0,1,3093 -DA:110,3093 -DA:111,3093 -DA:112,3093 -DA:124,4 -FN:124,RuleMintAllowanceBase.clearMintAllowances +DA:99,3240 +DA:100,3240 +DA:109,3097 +FN:109,RuleMintAllowanceBase.decreaseMintAllowance +FNDA:3097,RuleMintAllowanceBase.decreaseMintAllowance +DA:110,3096 +DA:111,3096 +BRDA:111,0,0,1 +BRDA:111,0,1,3095 +DA:112,3095 +DA:113,3095 +DA:114,3095 +DA:126,4 +FN:126,RuleMintAllowanceBase.clearMintAllowances FNDA:4,RuleMintAllowanceBase.clearMintAllowances -DA:125,3 -DA:126,7 -DA:142,323 -FN:142,RuleMintAllowanceBase.bindToken -FNDA:323,RuleMintAllowanceBase.bindToken -DA:143,321 -BRDA:143,1,0,2 -BRDA:143,1,1,319 -DA:144,319 -DA:154,3 -FN:154,RuleMintAllowanceBase.transferred.0 +DA:127,3 +DA:128,7 +DA:144,324 +FN:144,RuleMintAllowanceBase.bindToken +FNDA:324,RuleMintAllowanceBase.bindToken +DA:150,322 +BRDA:150,1,0,2 +BRDA:150,1,1,320 +DA:151,320 +DA:161,3 +FN:161,RuleMintAllowanceBase.transferred.0 FNDA:3,RuleMintAllowanceBase.transferred.0 -DA:160,2 -DA:171,6849 -FN:171,RuleMintAllowanceBase.transferred.1 -FNDA:6849,RuleMintAllowanceBase.transferred.1 -DA:177,6848 -DA:183,2 -FN:183,RuleMintAllowanceBase.messageForTransferRestriction +DA:167,2 +DA:178,6837 +FN:178,RuleMintAllowanceBase.transferred.1 +FNDA:6837,RuleMintAllowanceBase.transferred.1 +DA:184,6836 +DA:190,2 +FN:190,RuleMintAllowanceBase.messageForTransferRestriction FNDA:2,RuleMintAllowanceBase.messageForTransferRestriction -DA:189,2 -BRDA:189,2,0,1 -DA:190,1 -DA:192,1 -DA:200,5 -FN:200,RuleMintAllowanceBase.detectTransferRestriction -FNDA:5,RuleMintAllowanceBase.detectTransferRestriction -DA:207,5 -DA:213,9 -FN:213,RuleMintAllowanceBase.detectTransferRestrictionFrom -FNDA:9,RuleMintAllowanceBase.detectTransferRestrictionFrom -DA:220,15 -DA:227,3 -FN:227,RuleMintAllowanceBase.canTransfer -FNDA:3,RuleMintAllowanceBase.canTransfer +DA:196,2 +BRDA:196,2,0,1 +DA:197,1 +DA:199,1 +DA:207,9 +FN:207,RuleMintAllowanceBase.detectTransferRestriction +FNDA:9,RuleMintAllowanceBase.detectTransferRestriction +DA:214,9 +DA:220,12 +FN:220,RuleMintAllowanceBase.detectTransferRestrictionFrom +FNDA:12,RuleMintAllowanceBase.detectTransferRestrictionFrom +DA:227,18 DA:234,3 -DA:240,6 -FN:240,RuleMintAllowanceBase.canTransferFrom -FNDA:6,RuleMintAllowanceBase.canTransferFrom +FN:234,RuleMintAllowanceBase.canTransfer +FNDA:3,RuleMintAllowanceBase.canTransfer +DA:241,3 DA:247,6 -DA:258,2 -FN:258,RuleMintAllowanceBase._transferred +FN:247,RuleMintAllowanceBase.canTransferFrom +FNDA:6,RuleMintAllowanceBase.canTransferFrom +DA:254,6 +DA:265,2 +FN:265,RuleMintAllowanceBase._transferred FNDA:2,RuleMintAllowanceBase._transferred -DA:270,6848 -FN:270,RuleMintAllowanceBase._transferredFrom -FNDA:6848,RuleMintAllowanceBase._transferredFrom -DA:271,6848 -BRDA:271,3,0,6848 -DA:272,6848 -DA:274,3577 -DA:275,3577 -BRDA:275,4,0,258 -BRDA:275,4,1,3319 -DA:276,3319 -DA:277,3319 -DA:278,3319 -DA:286,3758 -FN:286,RuleMintAllowanceBase._setMintAllowance +DA:277,6836 +FN:277,RuleMintAllowanceBase._transferredFrom +FNDA:6836,RuleMintAllowanceBase._transferredFrom +DA:278,6836 +BRDA:278,3,0,6836 +DA:279,6836 +DA:281,3565 +DA:282,3565 +BRDA:282,4,0,249 +BRDA:282,4,1,3316 +DA:283,3316 +DA:284,3316 +DA:285,3316 +DA:293,3758 +FN:293,RuleMintAllowanceBase._setMintAllowance FNDA:3758,RuleMintAllowanceBase._setMintAllowance -DA:287,3758 -DA:288,3758 -DA:299,15 -FN:299,RuleMintAllowanceBase._detectTransferRestrictionFrom -FNDA:15,RuleMintAllowanceBase._detectTransferRestrictionFrom -DA:305,15 -BRDA:305,5,0,7 -DA:306,7 -DA:308,8 -DA:314,0 -FN:314,RuleMintAllowanceBase._authorizeSetMintAllowance +DA:294,3758 +DA:295,3758 +DA:306,18 +FN:306,RuleMintAllowanceBase._detectTransferRestrictionFrom +FNDA:18,RuleMintAllowanceBase._detectTransferRestrictionFrom +DA:312,18 +BRDA:312,5,0,10 +DA:313,10 +DA:315,8 +DA:321,0 +FN:321,RuleMintAllowanceBase._authorizeSetMintAllowance FNDA:0,RuleMintAllowanceBase._authorizeSetMintAllowance FNF:21 FNH:20 @@ -1434,556 +750,437 @@ BRF:9 BRH:9 end_of_record TN: +SF:src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol +DA:39,556 +FN:39,AddressSetBatchLib.addBatch +FNDA:556,AddressSetBatchLib.addBatch +DA:44,556 +DA:45,1628 +DA:46,1622 +BRDA:46,0,0,1135 +BRDA:46,0,1,487 +DA:47,1135 +DA:49,487 +DA:63,270 +FN:63,AddressSetBatchLib.removeBatch +FNDA:270,AddressSetBatchLib.removeBatch +DA:67,270 +DA:68,794 +BRDA:68,1,0,531 +BRDA:68,1,1,263 +DA:69,531 +DA:71,263 +FNF:2 +FNH:2 +LF:11 +LH:11 +BRF:4 +BRH:4 +end_of_record +TN: SF:src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol -DA:40,279 -FN:40,RuleAddressSet.onlyAddressListAdd -FNDA:279,RuleAddressSet.onlyAddressListAdd -DA:41,279 -DA:45,17 -FN:45,RuleAddressSet.onlyAddressListRemove -FNDA:17,RuleAddressSet.onlyAddressListRemove -DA:46,17 -DA:61,279 -FN:61,RuleAddressSet.addAddresses -FNDA:279,RuleAddressSet.addAddresses -DA:62,278 -DA:63,276 -DA:73,261 -FN:73,RuleAddressSet.removeAddresses -FNDA:261,RuleAddressSet.removeAddresses -DA:74,260 -DA:75,260 -DA:85,182 -FN:85,RuleAddressSet.addAddress -FNDA:182,RuleAddressSet.addAddress -DA:86,175 -BRDA:86,0,0,3 -BRDA:86,0,1,172 -DA:87,172 -BRDA:87,1,0,1 -BRDA:87,1,1,171 -DA:88,171 -DA:89,171 -DA:99,17 -FN:99,RuleAddressSet.removeAddress -FNDA:17,RuleAddressSet.removeAddress -DA:100,10 -BRDA:100,2,0,1 -BRDA:100,2,1,9 -DA:101,9 -DA:102,9 -DA:109,545 -FN:109,RuleAddressSet.listedAddressCount -FNDA:545,RuleAddressSet.listedAddressCount -DA:110,545 -DA:118,4 -FN:118,RuleAddressSet.contains +DA:42,539 +FN:42,RuleAddressSet.onlyAddressListAdd +FNDA:539,RuleAddressSet.onlyAddressListAdd +DA:43,539 +DA:47,18 +FN:47,RuleAddressSet.onlyAddressListRemove +FNDA:18,RuleAddressSet.onlyAddressListRemove +DA:48,18 +DA:66,539 +FN:66,RuleAddressSet.addAddresses +FNDA:539,RuleAddressSet.addAddresses +DA:67,538 +DA:68,536 +DA:78,262 +FN:78,RuleAddressSet.removeAddresses +FNDA:262,RuleAddressSet.removeAddresses +DA:79,261 +DA:80,261 +DA:90,422 +FN:90,RuleAddressSet.addAddress +FNDA:422,RuleAddressSet.addAddress +DA:91,415 +BRDA:91,0,0,3 +BRDA:91,0,1,412 +DA:92,412 +BRDA:92,1,0,1 +BRDA:92,1,1,411 +DA:93,411 +DA:103,18 +FN:103,RuleAddressSet.removeAddress +FNDA:18,RuleAddressSet.removeAddress +DA:104,11 +BRDA:104,2,0,1 +BRDA:104,2,1,10 +DA:105,10 +DA:112,548 +FN:112,RuleAddressSet.listedAddressCount +FNDA:548,RuleAddressSet.listedAddressCount +DA:113,548 +DA:121,4 +FN:121,RuleAddressSet.contains FNDA:4,RuleAddressSet.contains -DA:119,4 -DA:127,86 -FN:127,RuleAddressSet.isAddressListed -FNDA:86,RuleAddressSet.isAddressListed -DA:128,623 -DA:136,157 -FN:136,RuleAddressSet.areAddressesListed -FNDA:157,RuleAddressSet.areAddressesListed -DA:137,157 -DA:138,157 -DA:139,349 -DA:150,0 -FN:150,RuleAddressSet._authorizeAddressListAdd +DA:122,4 +DA:130,89 +FN:130,RuleAddressSet.isAddressListed +FNDA:89,RuleAddressSet.isAddressListed +DA:131,876 +DA:139,179 +FN:139,RuleAddressSet.areAddressesListed +FNDA:179,RuleAddressSet.areAddressesListed +DA:140,179 +DA:141,179 +DA:142,395 +DA:153,0 +FN:153,RuleAddressSet._authorizeAddressListAdd FNDA:0,RuleAddressSet._authorizeAddressListAdd -DA:155,0 -FN:155,RuleAddressSet._authorizeAddressListRemove +DA:158,0 +FN:158,RuleAddressSet._authorizeAddressListRemove FNDA:0,RuleAddressSet._authorizeAddressListRemove -DA:160,1127 -FN:160,RuleAddressSet._msgSender -FNDA:1127,RuleAddressSet._msgSender -DA:161,1127 -DA:167,8 -FN:167,RuleAddressSet._msgData +DA:163,1693 +FN:163,RuleAddressSet._msgSender +FNDA:1693,RuleAddressSet._msgSender +DA:164,1693 +DA:170,8 +FN:170,RuleAddressSet._msgData FNDA:8,RuleAddressSet._msgData -DA:168,8 -DA:174,1139 -FN:174,RuleAddressSet._contextSuffixLength -FNDA:1139,RuleAddressSet._contextSuffixLength -DA:175,1139 +DA:171,8 +DA:177,1705 +FN:177,RuleAddressSet._contextSuffixLength +FNDA:1705,RuleAddressSet._contextSuffixLength +DA:178,1705 FNF:15 FNH:13 -LF:37 -LH:35 +LF:35 +LH:33 BRF:6 BRH:6 end_of_record TN: SF:src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol -DA:41,278 -FN:41,RuleAddressSetInternal._addAddresses -FNDA:278,RuleAddressSetInternal._addAddresses -DA:42,278 -DA:49,815 -BRDA:49,0,0,2 -BRDA:49,0,1,813 -DA:50,813 -BRDA:50,1,0,553 -BRDA:50,1,1,260 -DA:51,553 -DA:53,260 -DA:67,260 -FN:67,RuleAddressSetInternal._removeAddresses -FNDA:260,RuleAddressSetInternal._removeAddresses -DA:71,260 -DA:72,777 -BRDA:72,2,0,520 -BRDA:72,2,1,257 -DA:73,520 -DA:75,257 -DA:84,245 -FN:84,RuleAddressSetInternal._addAddress -FNDA:245,RuleAddressSetInternal._addAddress -DA:85,245 -DA:92,14 -FN:92,RuleAddressSetInternal._removeAddress -FNDA:14,RuleAddressSetInternal._removeAddress -DA:93,14 -DA:100,548 -FN:100,RuleAddressSetInternal._listedAddressCount -FNDA:548,RuleAddressSetInternal._listedAddressCount -DA:101,548 -DA:109,1345 -FN:109,RuleAddressSetInternal._isAddressListed -FNDA:1345,RuleAddressSetInternal._isAddressListed -DA:110,1345 -FNF:6 -FNH:6 -LF:19 -LH:19 -BRF:6 -BRH:6 +DA:44,543 +FN:44,RuleAddressSetInternal._addAddresses +FNDA:543,RuleAddressSetInternal._addAddresses +DA:49,543 +DA:64,1602 +FN:64,RuleAddressSetInternal._requireNotZeroAddress +FNDA:1602,RuleAddressSetInternal._requireNotZeroAddress +DA:65,1602 +BRDA:65,0,0,3 +BRDA:65,0,1,1599 +DA:77,263 +FN:77,RuleAddressSetInternal._removeAddresses +FNDA:263,RuleAddressSetInternal._removeAddresses +DA:82,263 +DA:93,513 +FN:93,RuleAddressSetInternal._addAddress +FNDA:513,RuleAddressSetInternal._addAddress +DA:94,513 +DA:103,24 +FN:103,RuleAddressSetInternal._removeAddress +FNDA:24,RuleAddressSetInternal._removeAddress +DA:104,24 +DA:111,557 +FN:111,RuleAddressSetInternal._listedAddressCount +FNDA:557,RuleAddressSetInternal._listedAddressCount +DA:112,557 +DA:120,1467 +FN:120,RuleAddressSetInternal._isAddressListed +FNDA:1467,RuleAddressSetInternal._isAddressListed +DA:121,1467 +FNF:7 +FNH:7 +LF:14 +LH:14 +BRF:2 +BRH:2 end_of_record TN: SF:src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol -DA:44,4 -FN:44,RuleERC2980Internal._addWhitelistAddresses -FNDA:4,RuleERC2980Internal._addWhitelistAddresses -DA:48,4 -DA:51,6 -BRDA:51,0,0,- -BRDA:51,0,1,6 -DA:52,6 -BRDA:52,1,0,5 -BRDA:52,1,1,1 -DA:53,5 -DA:55,1 -DA:66,3 -FN:66,RuleERC2980Internal._removeWhitelistAddresses -FNDA:3,RuleERC2980Internal._removeWhitelistAddresses -DA:70,3 -DA:71,3 -BRDA:71,2,0,2 -BRDA:71,2,1,1 -DA:72,2 -DA:74,1 -DA:83,44 -FN:83,RuleERC2980Internal._addWhitelistAddress -FNDA:44,RuleERC2980Internal._addWhitelistAddress -DA:84,44 -DA:91,4 -FN:91,RuleERC2980Internal._removeWhitelistAddress -FNDA:4,RuleERC2980Internal._removeWhitelistAddress -DA:92,4 -DA:105,4 -FN:105,RuleERC2980Internal._addFrozenlistAddresses -FNDA:4,RuleERC2980Internal._addFrozenlistAddresses -DA:109,4 -DA:112,6 -BRDA:112,3,0,- -BRDA:112,3,1,6 -DA:113,6 -BRDA:113,4,0,5 -BRDA:113,4,1,1 -DA:114,5 -DA:116,1 -DA:127,2 -FN:127,RuleERC2980Internal._removeFrozenlistAddresses -FNDA:2,RuleERC2980Internal._removeFrozenlistAddresses -DA:131,2 -DA:132,2 -BRDA:132,5,0,1 -BRDA:132,5,1,1 -DA:133,1 -DA:135,1 -DA:144,19 -FN:144,RuleERC2980Internal._addFrozenlistAddress -FNDA:19,RuleERC2980Internal._addFrozenlistAddress -DA:145,19 -DA:152,4 -FN:152,RuleERC2980Internal._removeFrozenlistAddress -FNDA:4,RuleERC2980Internal._removeFrozenlistAddress -DA:153,4 -DA:165,115 -FN:165,RuleERC2980Internal._isWhitelisted -FNDA:115,RuleERC2980Internal._isWhitelisted -DA:166,115 -DA:173,5 -FN:173,RuleERC2980Internal._whitelistCount +DA:47,7 +FN:47,RuleERC2980Internal._addWhitelistAddresses +FNDA:7,RuleERC2980Internal._addWhitelistAddresses +DA:52,7 +DA:61,4 +FN:61,RuleERC2980Internal._removeWhitelistAddresses +FNDA:4,RuleERC2980Internal._removeWhitelistAddresses +DA:66,4 +DA:73,51 +FN:73,RuleERC2980Internal._addWhitelistAddress +FNDA:51,RuleERC2980Internal._addWhitelistAddress +DA:74,51 +DA:81,5 +FN:81,RuleERC2980Internal._removeWhitelistAddress +FNDA:5,RuleERC2980Internal._removeWhitelistAddress +DA:82,5 +DA:96,6 +FN:96,RuleERC2980Internal._addFrozenlistAddresses +FNDA:6,RuleERC2980Internal._addFrozenlistAddresses +DA:101,6 +DA:110,3 +FN:110,RuleERC2980Internal._removeFrozenlistAddresses +FNDA:3,RuleERC2980Internal._removeFrozenlistAddresses +DA:115,3 +DA:122,22 +FN:122,RuleERC2980Internal._addFrozenlistAddress +FNDA:22,RuleERC2980Internal._addFrozenlistAddress +DA:123,22 +DA:130,5 +FN:130,RuleERC2980Internal._removeFrozenlistAddress +FNDA:5,RuleERC2980Internal._removeFrozenlistAddress +DA:131,5 +DA:142,25 +FN:142,RuleERC2980Internal._requireNotZeroAddress +FNDA:25,RuleERC2980Internal._requireNotZeroAddress +DA:143,25 +BRDA:143,0,0,2 +BRDA:143,0,1,23 +DA:155,80 +FN:155,RuleERC2980Internal._isWhitelisted +FNDA:80,RuleERC2980Internal._isWhitelisted +DA:156,80 +DA:163,5 +FN:163,RuleERC2980Internal._whitelistCount FNDA:5,RuleERC2980Internal._whitelistCount -DA:174,5 -DA:182,163 -FN:182,RuleERC2980Internal._isFrozen -FNDA:163,RuleERC2980Internal._isFrozen -DA:183,163 -DA:190,4 -FN:190,RuleERC2980Internal._frozenlistCount +DA:164,5 +DA:172,165 +FN:172,RuleERC2980Internal._isFrozen +FNDA:165,RuleERC2980Internal._isFrozen +DA:173,165 +DA:180,4 +FN:180,RuleERC2980Internal._frozenlistCount FNDA:4,RuleERC2980Internal._frozenlistCount -DA:191,4 -FNF:12 -FNH:12 -LF:38 -LH:38 -BRF:12 -BRH:10 +DA:181,4 +FNF:13 +FNH:13 +LF:26 +LH:26 +BRF:2 +BRH:2 end_of_record TN: SF:src/rules/validation/abstract/base/RuleBlacklistBase.sol -DA:37,18 -FN:37,RuleBlacklistBase.transferred.0 -FNDA:18,RuleBlacklistBase.transferred.0 -DA:43,18 -DA:50,46 -FN:50,RuleBlacklistBase.transferred.1 -FNDA:46,RuleBlacklistBase.transferred.1 -DA:56,46 -DA:62,4 -FN:62,RuleBlacklistBase.canReturnTransferRestrictionCode -FNDA:4,RuleBlacklistBase.canReturnTransferRestrictionCode -DA:69,4 -DA:70,1 -DA:76,12 -FN:76,RuleBlacklistBase.messageForTransferRestriction -FNDA:12,RuleBlacklistBase.messageForTransferRestriction -DA:83,12 -BRDA:83,0,0,5 -BRDA:83,0,1,3 -DA:84,5 -DA:85,7 -BRDA:85,1,0,3 -BRDA:85,1,1,3 -DA:86,3 -DA:87,4 -BRDA:87,2,0,1 -BRDA:87,2,1,3 -DA:88,1 -DA:90,3 -DA:97,65 -FN:97,RuleBlacklistBase.supportsInterface -FNDA:65,RuleBlacklistBase.supportsInterface -DA:100,65 -DA:101,63 -DA:114,140 -FN:114,RuleBlacklistBase._detectTransferRestriction -FNDA:140,RuleBlacklistBase._detectTransferRestriction -DA:124,140 -BRDA:124,3,0,40 -BRDA:124,3,1,81 -DA:125,40 -DA:126,100 -BRDA:126,4,0,19 -DA:127,19 -DA:129,81 -DA:140,80 -FN:140,RuleBlacklistBase._detectTransferRestrictionFrom -FNDA:80,RuleBlacklistBase._detectTransferRestrictionFrom -DA:146,80 -BRDA:146,5,0,8 -DA:147,8 -DA:149,72 -DA:158,29 -FN:158,RuleBlacklistBase._transferred -FNDA:29,RuleBlacklistBase._transferred -DA:159,29 -DA:160,29 -BRDA:160,6,0,17 -BRDA:160,6,1,12 -DA:173,54 -FN:173,RuleBlacklistBase._transferredFrom -FNDA:54,RuleBlacklistBase._transferredFrom -DA:174,54 -DA:175,54 -BRDA:175,7,0,9 -BRDA:175,7,1,45 -FNF:9 -FNH:9 -LF:34 -LH:34 +DA:43,29 +FN:43,RuleBlacklistBase.transferred.0 +FNDA:29,RuleBlacklistBase.transferred.0 +DA:49,29 +DA:56,94 +FN:56,RuleBlacklistBase.transferred.1 +FNDA:94,RuleBlacklistBase.transferred.1 +DA:62,94 +DA:68,7 +FN:68,RuleBlacklistBase.canReturnTransferRestrictionCode +FNDA:7,RuleBlacklistBase.canReturnTransferRestrictionCode +DA:75,7 +DA:76,3 +DA:82,13 +FN:82,RuleBlacklistBase.messageForTransferRestriction +FNDA:13,RuleBlacklistBase.messageForTransferRestriction +DA:89,13 +BRDA:89,0,0,6 +BRDA:89,0,1,3 +DA:90,6 +DA:91,7 +BRDA:91,1,0,3 +BRDA:91,1,1,3 +DA:92,3 +DA:93,4 +BRDA:93,2,0,1 +BRDA:93,2,1,3 +DA:94,1 +DA:96,3 +DA:103,109 +FN:103,RuleBlacklistBase.supportsInterface +FNDA:109,RuleBlacklistBase.supportsInterface +DA:106,109 +DA:107,107 +DA:108,105 +DA:109,103 +DA:116,3 +FN:116,RuleBlacklistBase.isAllowList +FNDA:3,RuleBlacklistBase.isAllowList +DA:117,3 +DA:130,230 +FN:130,RuleBlacklistBase._detectTransferRestriction +FNDA:230,RuleBlacklistBase._detectTransferRestriction +DA:141,230 +BRDA:141,3,0,54 +BRDA:141,3,1,153 +DA:142,54 +DA:143,176 +BRDA:143,4,0,23 +DA:144,23 +DA:146,153 +DA:157,134 +FN:157,RuleBlacklistBase._detectTransferRestrictionFrom +FNDA:134,RuleBlacklistBase._detectTransferRestrictionFrom +DA:164,134 +BRDA:164,5,0,8 +DA:165,8 +DA:167,126 +DA:176,46 +FN:176,RuleBlacklistBase._transferred +FNDA:46,RuleBlacklistBase._transferred +DA:177,46 +DA:178,46 +BRDA:178,6,0,25 +BRDA:178,6,1,21 +DA:191,102 +FN:191,RuleBlacklistBase._transferredFrom +FNDA:102,RuleBlacklistBase._transferredFrom +DA:192,102 +DA:193,102 +BRDA:193,7,0,10 +BRDA:193,7,1,92 +FNF:10 +FNH:10 +LF:38 +LH:38 BRF:14 BRH:14 end_of_record TN: SF:src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol -DA:74,600 -FN:74,RuleChainlinkPoRBase.constructor -FNDA:600,RuleChainlinkPoRBase.constructor -DA:80,600 -DA:81,598 -DA:82,596 -DA:94,8 -FN:94,RuleChainlinkPoRBase.canReturnTransferRestrictionCode -FNDA:8,RuleChainlinkPoRBase.canReturnTransferRestrictionCode -DA:95,8 -DA:96,4 -DA:109,8 -FN:109,RuleChainlinkPoRBase.setReservesFeed -FNDA:8,RuleChainlinkPoRBase.setReservesFeed -DA:110,6 -DA:119,12 -FN:119,RuleChainlinkPoRBase.setTokenMetadata -FNDA:12,RuleChainlinkPoRBase.setTokenMetadata -DA:120,10 -DA:127,5 -FN:127,RuleChainlinkPoRBase.setMaxStalenessSeconds -FNDA:5,RuleChainlinkPoRBase.setMaxStalenessSeconds -DA:128,3 +DA:42,624 +FN:42,RuleChainlinkPoRBase.constructor +FNDA:624,RuleChainlinkPoRBase.constructor +DA:48,624 +DA:49,622 +DA:50,620 +DA:62,9 +FN:62,RuleChainlinkPoRBase.canReturnTransferRestrictionCode +FNDA:9,RuleChainlinkPoRBase.canReturnTransferRestrictionCode +DA:63,9 +DA:64,5 +DA:65,2 +DA:75,16 +FN:75,RuleChainlinkPoRBase.transferred.0 +FNDA:16,RuleChainlinkPoRBase.transferred.0 +DA:76,16 +DA:82,16 +FN:82,RuleChainlinkPoRBase.transferred.1 +FNDA:16,RuleChainlinkPoRBase.transferred.1 +DA:83,16 +DA:89,9 +FN:89,RuleChainlinkPoRBase.messageForTransferRestriction +FNDA:9,RuleChainlinkPoRBase.messageForTransferRestriction +DA:95,9 +BRDA:95,0,0,2 +BRDA:95,0,1,1 +DA:96,2 +DA:97,7 +BRDA:97,1,0,2 +BRDA:97,1,1,1 +DA:98,2 +DA:99,5 +BRDA:99,2,0,2 +BRDA:99,2,1,1 +DA:100,2 +DA:101,3 +BRDA:101,3,0,1 +BRDA:101,3,1,1 +DA:102,1 +DA:103,2 +BRDA:103,4,0,1 +DA:104,1 +DA:106,1 +DA:116,629 +FN:116,RuleChainlinkPoRBase._detectTransferRestriction +FNDA:629,RuleChainlinkPoRBase._detectTransferRestriction +DA:129,629 +BRDA:129,5,0,9 +DA:130,9 +DA:132,620 +DA:133,620 +BRDA:133,6,0,151 +DA:134,151 +DA:136,469 +DA:137,469 +BRDA:137,7,0,4 DA:138,4 -FN:138,RuleChainlinkPoRBase.feedDecimals -FNDA:4,RuleChainlinkPoRBase.feedDecimals -DA:139,4 -DA:152,657 -FN:152,RuleChainlinkPoRBase.maxBackedSupply -FNDA:657,RuleChainlinkPoRBase.maxBackedSupply -DA:153,657 -DA:159,4 -FN:159,RuleChainlinkPoRBase.transferred.0 -FNDA:4,RuleChainlinkPoRBase.transferred.0 -DA:160,4 -DA:166,13 -FN:166,RuleChainlinkPoRBase.transferred.1 -FNDA:13,RuleChainlinkPoRBase.transferred.1 -DA:167,13 -DA:173,8 -FN:173,RuleChainlinkPoRBase.messageForTransferRestriction -FNDA:8,RuleChainlinkPoRBase.messageForTransferRestriction -DA:179,8 -BRDA:179,0,0,2 -BRDA:179,0,1,1 -DA:180,2 -DA:181,6 -BRDA:181,1,0,2 -BRDA:181,1,1,1 -DA:182,2 -DA:183,4 -BRDA:183,2,0,2 -BRDA:183,2,1,1 -DA:184,2 -DA:185,2 -BRDA:185,3,0,1 -DA:186,1 -DA:188,1 -DA:195,8 -FN:195,RuleChainlinkPoRBase.onlyChainlinkPoRManager -FNDA:8,RuleChainlinkPoRBase.onlyChainlinkPoRManager -DA:196,8 -DA:204,0 -FN:204,RuleChainlinkPoRBase._authorizeChainlinkPoRManager -FNDA:0,RuleChainlinkPoRBase._authorizeChainlinkPoRManager -DA:218,606 -FN:218,RuleChainlinkPoRBase._setReservesFeed -FNDA:606,RuleChainlinkPoRBase._setReservesFeed -DA:219,606 -DA:220,606 -BRDA:220,4,0,1 -BRDA:220,4,1,605 -DA:221,605 -BRDA:221,5,0,1 -BRDA:221,5,1,604 -DA:222,604 -DA:223,604 -BRDA:223,6,0,604 -DA:224,603 -DA:225,1 -BRDA:225,6,1,1 -DA:226,1 -DA:228,603 -BRDA:228,7,0,1 -BRDA:228,7,1,602 -DA:229,602 -DA:230,602 -DA:241,608 -FN:241,RuleChainlinkPoRBase._setTokenMetadata -FNDA:608,RuleChainlinkPoRBase._setTokenMetadata -DA:242,608 -BRDA:242,8,0,2 -BRDA:242,8,1,606 -DA:246,606 -BRDA:246,9,0,2 -BRDA:246,9,1,604 -DA:247,604 -BRDA:247,10,0,1 -BRDA:247,10,1,603 -DA:248,603 -BRDA:248,11,0,603 -DA:249,602 -BRDA:249,12,0,1 -BRDA:249,12,1,601 -DA:258,602 -BRDA:258,13,0,602 -DA:259,1 -BRDA:259,13,1,1 -DA:260,1 -DA:262,601 -DA:263,601 -DA:264,601 -DA:271,599 -FN:271,RuleChainlinkPoRBase._setMaxStalenessSeconds -FNDA:599,RuleChainlinkPoRBase._setMaxStalenessSeconds -DA:272,599 -DA:273,599 -DA:282,1252 -FN:282,RuleChainlinkPoRBase._maxBackedSupply -FNDA:1252,RuleChainlinkPoRBase._maxBackedSupply -DA:283,1252 -DA:286,1252 -DA:287,1252 -BRDA:287,14,0,1252 -DA:288,1250 -DA:289,2 -BRDA:289,14,1,2 -DA:290,2 -DA:294,1250 -BRDA:294,15,0,3 -DA:295,3 -DA:297,1247 -BRDA:297,16,0,1247 -DA:299,1245 -BRDA:299,17,0,135 -DA:300,135 -DA:302,1110 -DA:303,1110 -BRDA:303,18,0,3 -DA:304,3 -DA:308,1107 -DA:309,1107 -DA:310,2 -BRDA:310,16,1,2 -DA:311,2 -DA:325,453 -FN:325,RuleChainlinkPoRBase._currentSupply -FNDA:453,RuleChainlinkPoRBase._currentSupply -DA:326,453 -DA:327,453 -BRDA:327,19,0,453 -DA:328,449 -DA:329,4 -BRDA:329,19,1,4 -DA:330,4 -DA:343,1107 -FN:343,RuleChainlinkPoRBase._scaleReserve -FNDA:1107,RuleChainlinkPoRBase._scaleReserve -DA:344,1107 -DA:345,1107 -BRDA:345,20,0,58 -DA:346,58 -DA:348,1049 -BRDA:348,21,0,639 -DA:350,639 -DA:351,639 -BRDA:351,22,0,32 -DA:352,32 -DA:354,607 -DA:357,410 -DA:363,601 -FN:363,RuleChainlinkPoRBase._detectTransferRestriction -FNDA:601,RuleChainlinkPoRBase._detectTransferRestriction -DA:375,601 -BRDA:375,23,0,6 -DA:376,6 -DA:378,595 -DA:379,595 -BRDA:379,24,0,142 -DA:380,142 -DA:382,453 -DA:383,453 -BRDA:383,25,0,4 -DA:384,4 -DA:388,449 -BRDA:388,26,0,224 -DA:389,224 -DA:391,225 -DA:397,18 -FN:397,RuleChainlinkPoRBase._detectTransferRestrictionFrom -FNDA:18,RuleChainlinkPoRBase._detectTransferRestrictionFrom -DA:403,18 -DA:412,4 -FN:412,RuleChainlinkPoRBase._transferred -FNDA:4,RuleChainlinkPoRBase._transferred -DA:413,4 -DA:414,4 -BRDA:414,27,0,2 -BRDA:414,27,1,2 -DA:427,13 -FN:427,RuleChainlinkPoRBase._transferredFrom -FNDA:13,RuleChainlinkPoRBase._transferredFrom -DA:428,13 -DA:429,13 -BRDA:429,28,0,5 -BRDA:429,28,1,8 -FNF:22 -FNH:21 -LF:116 -LH:115 -BRF:46 -BRH:46 +DA:142,465 +BRDA:142,8,0,251 +DA:143,251 +DA:145,214 +DA:151,5 +FN:151,RuleChainlinkPoRBase._detectTransferRestrictionFrom +FNDA:5,RuleChainlinkPoRBase._detectTransferRestrictionFrom +DA:158,5 +DA:175,19 +FN:175,RuleChainlinkPoRBase._detectTransferRestrictionOnNotify +FNDA:19,RuleChainlinkPoRBase._detectTransferRestrictionOnNotify +DA:181,19 +DA:190,16 +FN:190,RuleChainlinkPoRBase._transferred +FNDA:16,RuleChainlinkPoRBase._transferred +DA:191,16 +DA:192,16 +BRDA:192,9,0,7 +BRDA:192,9,1,9 +DA:205,16 +FN:205,RuleChainlinkPoRBase._transferredFrom +FNDA:16,RuleChainlinkPoRBase._transferredFrom +DA:206,16 +DA:207,16 +BRDA:207,10,0,6 +BRDA:207,10,1,10 +FNF:10 +FNH:10 +LF:46 +LH:46 +BRF:17 +BRH:17 end_of_record TN: SF:src/rules/validation/abstract/base/RuleERC2980Base.sol -DA:64,75 +DA:64,87 FN:64,RuleERC2980Base.constructor -FNDA:75,RuleERC2980Base.constructor -DA:65,75 -DA:66,75 -DA:67,75 -DA:68,75 +FNDA:87,RuleERC2980Base.constructor +DA:65,87 +DA:66,87 +DA:67,87 +DA:68,87 DA:75,5 FN:75,RuleERC2980Base.onlyMintBurnManager FNDA:5,RuleERC2980Base.onlyMintBurnManager DA:76,5 -DA:80,6 +DA:80,9 FN:80,RuleERC2980Base.onlyWhitelistAdd -FNDA:6,RuleERC2980Base.onlyWhitelistAdd -DA:81,6 +FNDA:9,RuleERC2980Base.onlyWhitelistAdd +DA:81,9 DA:85,7 FN:85,RuleERC2980Base.onlyWhitelistRemove FNDA:7,RuleERC2980Base.onlyWhitelistRemove DA:86,7 -DA:90,6 +DA:90,8 FN:90,RuleERC2980Base.onlyFrozenlistAdd -FNDA:6,RuleERC2980Base.onlyFrozenlistAdd -DA:91,6 -DA:95,2 +FNDA:8,RuleERC2980Base.onlyFrozenlistAdd +DA:91,8 +DA:95,3 FN:95,RuleERC2980Base.onlyFrozenlistRemove -FNDA:2,RuleERC2980Base.onlyFrozenlistRemove -DA:96,2 -DA:109,6 -FN:109,RuleERC2980Base.addWhitelistAddresses -FNDA:6,RuleERC2980Base.addWhitelistAddresses -DA:110,4 -DA:111,4 -DA:119,4 -FN:119,RuleERC2980Base.removeWhitelistAddresses -FNDA:4,RuleERC2980Base.removeWhitelistAddresses -DA:120,3 -DA:121,3 -DA:133,49 -FN:133,RuleERC2980Base.addWhitelistAddress -FNDA:49,RuleERC2980Base.addWhitelistAddress -DA:134,46 -BRDA:134,0,0,1 -BRDA:134,0,1,45 -DA:135,45 -BRDA:135,1,0,1 -BRDA:135,1,1,44 -DA:136,44 -DA:137,44 +FNDA:3,RuleERC2980Base.onlyFrozenlistRemove +DA:96,3 +DA:110,9 +FN:110,RuleERC2980Base.addWhitelistAddresses +FNDA:9,RuleERC2980Base.addWhitelistAddresses +DA:111,7 +DA:112,6 +DA:120,5 +FN:120,RuleERC2980Base.removeWhitelistAddresses +FNDA:5,RuleERC2980Base.removeWhitelistAddresses +DA:121,4 +DA:122,4 +DA:134,55 +FN:134,RuleERC2980Base.addWhitelistAddress +FNDA:55,RuleERC2980Base.addWhitelistAddress +DA:135,52 +BRDA:135,0,0,1 +BRDA:135,0,1,51 +DA:136,51 +BRDA:136,1,0,1 +BRDA:136,1,1,50 +DA:137,50 DA:149,7 FN:149,RuleERC2980Base.removeWhitelistAddress FNDA:7,RuleERC2980Base.removeWhitelistAddress @@ -1991,467 +1188,507 @@ DA:150,5 BRDA:150,2,0,1 BRDA:150,2,1,4 DA:151,4 -DA:152,4 -DA:164,6 +DA:164,8 FN:164,RuleERC2980Base.addFrozenlistAddresses -FNDA:6,RuleERC2980Base.addFrozenlistAddresses -DA:165,4 -DA:166,4 -DA:174,2 +FNDA:8,RuleERC2980Base.addFrozenlistAddresses +DA:165,6 +DA:166,5 +DA:174,3 FN:174,RuleERC2980Base.removeFrozenlistAddresses -FNDA:2,RuleERC2980Base.removeFrozenlistAddresses -DA:175,2 -DA:176,2 -DA:188,24 +FNDA:3,RuleERC2980Base.removeFrozenlistAddresses +DA:175,3 +DA:176,3 +DA:188,26 FN:188,RuleERC2980Base.addFrozenlistAddress -FNDA:24,RuleERC2980Base.addFrozenlistAddress -DA:189,21 +FNDA:26,RuleERC2980Base.addFrozenlistAddress +DA:189,23 BRDA:189,3,0,1 -BRDA:189,3,1,20 -DA:190,20 +BRDA:189,3,1,22 +DA:190,22 BRDA:190,4,0,1 -BRDA:190,4,1,19 -DA:191,19 -DA:192,19 -DA:204,7 -FN:204,RuleERC2980Base.removeFrozenlistAddress +BRDA:190,4,1,21 +DA:191,21 +DA:203,7 +FN:203,RuleERC2980Base.removeFrozenlistAddress FNDA:7,RuleERC2980Base.removeFrozenlistAddress -DA:205,5 -BRDA:205,5,0,1 -BRDA:205,5,1,4 -DA:206,4 -DA:207,4 -DA:218,5 -FN:218,RuleERC2980Base.setAllowMint +DA:204,5 +BRDA:204,5,0,1 +BRDA:204,5,1,4 +DA:205,4 +DA:216,5 +FN:216,RuleERC2980Base.setAllowMint FNDA:5,RuleERC2980Base.setAllowMint -DA:219,3 -DA:220,3 -DA:227,3 -FN:227,RuleERC2980Base.setAllowBurn +DA:217,3 +DA:218,3 +DA:225,3 +FN:225,RuleERC2980Base.setAllowBurn FNDA:3,RuleERC2980Base.setAllowBurn -DA:228,2 -DA:229,2 -DA:235,6 -FN:235,RuleERC2980Base.transferred.0 -FNDA:6,RuleERC2980Base.transferred.0 -DA:241,6 -DA:247,4 -FN:247,RuleERC2980Base.transferred.1 +DA:226,2 +DA:227,2 +DA:233,8 +FN:233,RuleERC2980Base.transferred.0 +FNDA:8,RuleERC2980Base.transferred.0 +DA:239,8 +DA:245,4 +FN:245,RuleERC2980Base.transferred.1 FNDA:4,RuleERC2980Base.transferred.1 -DA:253,4 -DA:259,5 -FN:259,RuleERC2980Base.canReturnTransferRestrictionCode +DA:251,4 +DA:257,5 +FN:257,RuleERC2980Base.canReturnTransferRestrictionCode FNDA:5,RuleERC2980Base.canReturnTransferRestrictionCode -DA:266,5 -DA:267,3 -DA:268,1 -DA:274,7 -FN:274,RuleERC2980Base.messageForTransferRestriction +DA:264,5 +DA:265,3 +DA:266,1 +DA:272,7 +FN:272,RuleERC2980Base.messageForTransferRestriction FNDA:7,RuleERC2980Base.messageForTransferRestriction -DA:281,7 -BRDA:281,6,0,1 -BRDA:281,6,1,1 +DA:279,7 +BRDA:279,6,0,1 +BRDA:279,6,1,1 +DA:280,1 +DA:281,6 +BRDA:281,7,0,1 +BRDA:281,7,1,1 DA:282,1 -DA:283,6 -BRDA:283,7,0,1 -BRDA:283,7,1,1 +DA:283,5 +BRDA:283,8,0,1 +BRDA:283,8,1,1 DA:284,1 -DA:285,5 -BRDA:285,8,0,1 -BRDA:285,8,1,1 +DA:285,4 +BRDA:285,9,0,1 +BRDA:285,9,1,1 DA:286,1 -DA:287,4 -BRDA:287,9,0,1 -BRDA:287,9,1,1 +DA:287,3 +BRDA:287,10,0,1 +BRDA:287,10,1,1 DA:288,1 -DA:289,3 -BRDA:289,10,0,1 -BRDA:289,10,1,1 +DA:289,2 +BRDA:289,11,0,1 +BRDA:289,11,1,1 DA:290,1 -DA:291,2 -BRDA:291,11,0,1 -BRDA:291,11,1,1 DA:292,1 -DA:294,1 -DA:301,3 -FN:301,RuleERC2980Base.supportsInterface +DA:299,3 +FN:299,RuleERC2980Base.supportsInterface FNDA:3,RuleERC2980Base.supportsInterface -DA:302,3 -DA:309,5 -FN:309,RuleERC2980Base.whitelistAddressCount +DA:300,3 +DA:307,5 +FN:307,RuleERC2980Base.whitelistAddressCount FNDA:5,RuleERC2980Base.whitelistAddressCount -DA:310,5 -DA:318,15 -FN:318,RuleERC2980Base.isWhitelisted -FNDA:15,RuleERC2980Base.isWhitelisted -DA:319,15 -DA:327,11 -FN:327,RuleERC2980Base.whitelist -FNDA:11,RuleERC2980Base.whitelist -DA:328,11 -DA:338,5 -FN:338,RuleERC2980Base.isVerified +DA:308,5 +DA:316,16 +FN:316,RuleERC2980Base.isWhitelisted +FNDA:16,RuleERC2980Base.isWhitelisted +DA:317,16 +DA:325,17 +FN:325,RuleERC2980Base.whitelist +FNDA:17,RuleERC2980Base.whitelist +DA:326,17 +DA:336,5 +FN:336,RuleERC2980Base.isVerified FNDA:5,RuleERC2980Base.isVerified -DA:339,5 -DA:347,1 -FN:347,RuleERC2980Base.areWhitelisted +DA:337,5 +DA:345,1 +FN:345,RuleERC2980Base.areWhitelisted FNDA:1,RuleERC2980Base.areWhitelisted -DA:348,1 -DA:349,1 -DA:350,2 -DA:358,4 -FN:358,RuleERC2980Base.frozenlistAddressCount +DA:346,1 +DA:347,1 +DA:348,2 +DA:356,4 +FN:356,RuleERC2980Base.frozenlistAddressCount FNDA:4,RuleERC2980Base.frozenlistAddressCount -DA:359,4 -DA:367,12 -FN:367,RuleERC2980Base.isFrozen +DA:357,4 +DA:365,12 +FN:365,RuleERC2980Base.isFrozen FNDA:12,RuleERC2980Base.isFrozen -DA:368,12 -DA:376,7 -FN:376,RuleERC2980Base.frozenlist -FNDA:7,RuleERC2980Base.frozenlist -DA:377,7 -DA:385,1 -FN:385,RuleERC2980Base.areFrozen +DA:366,12 +DA:374,10 +FN:374,RuleERC2980Base.frozenlist +FNDA:10,RuleERC2980Base.frozenlist +DA:375,10 +DA:383,1 +FN:383,RuleERC2980Base.areFrozen FNDA:1,RuleERC2980Base.areFrozen -DA:386,1 -DA:387,1 -DA:388,2 -DA:399,0 -FN:399,RuleERC2980Base._authorizeMintBurnManager +DA:384,1 +DA:385,1 +DA:386,2 +DA:397,0 +FN:397,RuleERC2980Base._authorizeMintBurnManager FNDA:0,RuleERC2980Base._authorizeMintBurnManager -DA:404,0 -FN:404,RuleERC2980Base._authorizeWhitelistAdd +DA:402,0 +FN:402,RuleERC2980Base._authorizeWhitelistAdd FNDA:0,RuleERC2980Base._authorizeWhitelistAdd -DA:408,0 -FN:408,RuleERC2980Base._authorizeWhitelistRemove +DA:406,0 +FN:406,RuleERC2980Base._authorizeWhitelistRemove FNDA:0,RuleERC2980Base._authorizeWhitelistRemove -DA:412,0 -FN:412,RuleERC2980Base._authorizeFrozenlistAdd +DA:410,0 +FN:410,RuleERC2980Base._authorizeFrozenlistAdd FNDA:0,RuleERC2980Base._authorizeFrozenlistAdd -DA:416,0 -FN:416,RuleERC2980Base._authorizeFrozenlistRemove +DA:414,0 +FN:414,RuleERC2980Base._authorizeFrozenlistRemove FNDA:0,RuleERC2980Base._authorizeFrozenlistRemove -DA:421,62 -FN:421,RuleERC2980Base._detectTransferRestriction -FNDA:62,RuleERC2980Base._detectTransferRestriction -DA:432,62 -DA:433,62 -DA:436,62 -BRDA:436,12,0,1 -DA:437,1 -DA:439,61 -BRDA:439,13,0,2 -DA:440,2 -DA:444,59 -BRDA:444,14,0,20 -DA:445,20 -DA:447,39 -BRDA:447,15,0,4 -DA:448,4 -DA:451,35 -BRDA:451,16,0,5 -DA:452,5 -DA:454,30 -DA:460,24 -FN:460,RuleERC2980Base._detectTransferRestrictionFrom +DA:419,78 +FN:419,RuleERC2980Base._detectTransferRestriction +FNDA:78,RuleERC2980Base._detectTransferRestriction +DA:430,78 +DA:431,78 +DA:434,78 +BRDA:434,12,0,1 +DA:435,1 +DA:437,77 +BRDA:437,13,0,2 +DA:438,2 +DA:442,75 +BRDA:442,14,0,28 +DA:443,28 +DA:445,47 +BRDA:445,15,0,4 +DA:446,4 +DA:449,43 +BRDA:449,16,0,5 +DA:450,5 +DA:452,38 +DA:458,24 +FN:458,RuleERC2980Base._detectTransferRestrictionFrom FNDA:24,RuleERC2980Base._detectTransferRestrictionFrom -DA:467,24 -BRDA:467,17,0,4 -DA:468,4 -DA:470,20 -DA:476,13 -FN:476,RuleERC2980Base._transferred -FNDA:13,RuleERC2980Base._transferred -DA:477,13 -DA:478,13 -BRDA:478,18,0,7 -BRDA:478,18,1,6 -DA:487,11 -FN:487,RuleERC2980Base._transferredFrom +DA:465,24 +BRDA:465,17,0,4 +DA:466,4 +DA:468,20 +DA:474,21 +FN:474,RuleERC2980Base._transferred +FNDA:21,RuleERC2980Base._transferred +DA:475,21 +DA:476,21 +BRDA:476,18,0,11 +BRDA:476,18,1,10 +DA:485,11 +FN:485,RuleERC2980Base._transferredFrom FNDA:11,RuleERC2980Base._transferredFrom -DA:488,11 -DA:489,11 -BRDA:489,19,0,5 -BRDA:489,19,1,6 -DA:498,293 -FN:498,RuleERC2980Base._msgSender -FNDA:293,RuleERC2980Base._msgSender -DA:499,293 -DA:505,2 -FN:505,RuleERC2980Base._msgData +DA:486,11 +DA:487,11 +BRDA:487,19,0,5 +BRDA:487,19,1,6 +DA:496,320 +FN:496,RuleERC2980Base._msgSender +FNDA:320,RuleERC2980Base._msgSender +DA:497,320 +DA:503,2 +FN:503,RuleERC2980Base._msgData FNDA:2,RuleERC2980Base._msgData -DA:506,2 -DA:512,295 -FN:512,RuleERC2980Base._contextSuffixLength -FNDA:295,RuleERC2980Base._contextSuffixLength -DA:513,295 +DA:504,2 +DA:510,322 +FN:510,RuleERC2980Base._contextSuffixLength +FNDA:322,RuleERC2980Base._contextSuffixLength +DA:511,322 FNF:42 FNH:37 -LF:132 -LH:127 +LF:128 +LH:123 BRF:34 BRH:34 end_of_record TN: SF:src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol -DA:63,39 -FN:63,RuleIdentityRegistryBase.constructor -FNDA:39,RuleIdentityRegistryBase.constructor -DA:64,39 -BRDA:64,0,0,37 -DA:65,37 -DA:67,39 -DA:68,39 -DA:69,39 -DA:70,39 -DA:77,5 -FN:77,RuleIdentityRegistryBase.onlyIdentityRegistryManager +DA:55,64 +FN:55,RuleIdentityRegistryBase.constructor +FNDA:64,RuleIdentityRegistryBase.constructor +DA:60,64 +BRDA:60,0,0,57 +DA:61,57 +DA:62,57 +DA:64,64 +DA:65,64 +DA:66,64 +DA:67,64 +DA:74,5 +FN:74,RuleIdentityRegistryBase.onlyIdentityRegistryManager FNDA:5,RuleIdentityRegistryBase.onlyIdentityRegistryManager -DA:78,5 -DA:91,4 -FN:91,RuleIdentityRegistryBase.canReturnTransferRestrictionCode +DA:75,5 +DA:88,4 +FN:88,RuleIdentityRegistryBase.canReturnTransferRestrictionCode FNDA:4,RuleIdentityRegistryBase.canReturnTransferRestrictionCode -DA:92,4 -DA:93,2 -DA:104,4 -FN:104,RuleIdentityRegistryBase.setIdentityRegistry +DA:89,4 +DA:90,2 +DA:101,4 +FN:101,RuleIdentityRegistryBase.setIdentityRegistry FNDA:4,RuleIdentityRegistryBase.setIdentityRegistry -DA:105,2 -BRDA:105,1,0,1 -BRDA:105,1,1,1 -DA:106,1 -DA:107,1 -DA:116,2 -FN:116,RuleIdentityRegistryBase.setCheckSender -FNDA:2,RuleIdentityRegistryBase.setCheckSender -DA:117,2 -DA:118,2 -DA:126,5 -FN:126,RuleIdentityRegistryBase.setCheckSpender -FNDA:5,RuleIdentityRegistryBase.setCheckSpender -DA:127,5 -DA:128,5 -DA:134,5 -FN:134,RuleIdentityRegistryBase.clearIdentityRegistry +DA:102,2 +BRDA:102,1,0,1 +BRDA:102,1,1,1 +DA:103,1 +DA:104,1 +DA:113,3 +FN:113,RuleIdentityRegistryBase.setCheckSender +FNDA:3,RuleIdentityRegistryBase.setCheckSender +DA:114,3 +DA:115,3 +DA:123,6 +FN:123,RuleIdentityRegistryBase.setCheckSpender +FNDA:6,RuleIdentityRegistryBase.setCheckSpender +DA:124,6 +DA:125,6 +DA:131,5 +FN:131,RuleIdentityRegistryBase.clearIdentityRegistry FNDA:5,RuleIdentityRegistryBase.clearIdentityRegistry -DA:135,3 -DA:136,3 -DA:142,5 -FN:142,RuleIdentityRegistryBase.transferred.0 -FNDA:5,RuleIdentityRegistryBase.transferred.0 -DA:143,5 -DA:149,7 -FN:149,RuleIdentityRegistryBase.transferred.1 -FNDA:7,RuleIdentityRegistryBase.transferred.1 -DA:150,7 -DA:156,4 -FN:156,RuleIdentityRegistryBase.messageForTransferRestriction +DA:132,3 +DA:133,3 +DA:139,14 +FN:139,RuleIdentityRegistryBase.transferred.0 +FNDA:14,RuleIdentityRegistryBase.transferred.0 +DA:140,14 +DA:146,23 +FN:146,RuleIdentityRegistryBase.transferred.1 +FNDA:23,RuleIdentityRegistryBase.transferred.1 +DA:147,23 +DA:153,4 +FN:153,RuleIdentityRegistryBase.messageForTransferRestriction FNDA:4,RuleIdentityRegistryBase.messageForTransferRestriction -DA:162,4 -BRDA:162,2,0,1 -BRDA:162,2,1,1 -DA:163,1 -DA:164,3 -BRDA:164,3,0,1 -BRDA:164,3,1,1 -DA:165,1 -DA:166,2 -BRDA:166,4,0,1 -DA:167,1 -DA:169,1 -DA:179,0 -FN:179,RuleIdentityRegistryBase._authorizeIdentityRegistryManager +DA:159,4 +BRDA:159,2,0,1 +BRDA:159,2,1,1 +DA:160,1 +DA:161,3 +BRDA:161,3,0,1 +BRDA:161,3,1,1 +DA:162,1 +DA:163,2 +BRDA:163,4,0,1 +DA:164,1 +DA:166,1 +DA:176,0 +FN:176,RuleIdentityRegistryBase._authorizeIdentityRegistryManager FNDA:0,RuleIdentityRegistryBase._authorizeIdentityRegistryManager -DA:187,62 -FN:187,RuleIdentityRegistryBase._detectTransferRestriction -FNDA:62,RuleIdentityRegistryBase._detectTransferRestriction -DA:197,62 -BRDA:197,5,0,3 -DA:198,3 -DA:201,59 -BRDA:201,6,0,3 -DA:202,3 -DA:206,56 -BRDA:206,7,0,1 -DA:207,1 -DA:212,55 -BRDA:212,8,0,6 -DA:213,6 -DA:215,49 -DA:226,31 -FN:226,RuleIdentityRegistryBase._detectTransferRestrictionFrom -FNDA:31,RuleIdentityRegistryBase._detectTransferRestrictionFrom -DA:232,31 -BRDA:232,9,0,1 -DA:233,1 -DA:236,30 -BRDA:236,10,0,2 -DA:237,2 -DA:245,5 -DA:246,4 -DA:247,3 -BRDA:247,11,0,3 -DA:248,3 -DA:250,25 -DA:256,11 -FN:256,RuleIdentityRegistryBase._transferred -FNDA:11,RuleIdentityRegistryBase._transferred -DA:257,11 -DA:258,11 -BRDA:258,12,0,1 -BRDA:258,12,1,10 -DA:267,13 -FN:267,RuleIdentityRegistryBase._transferredFrom -FNDA:13,RuleIdentityRegistryBase._transferredFrom -DA:268,13 -DA:269,13 -BRDA:269,13,0,2 -BRDA:269,13,1,11 +DA:184,121 +FN:184,RuleIdentityRegistryBase._detectTransferRestriction +FNDA:121,RuleIdentityRegistryBase._detectTransferRestriction +DA:197,121 +DA:198,121 +BRDA:198,5,0,10 +DA:199,10 +DA:202,111 +BRDA:202,6,0,10 +DA:203,10 +DA:207,101 +BRDA:207,7,0,2 +DA:208,2 +DA:213,99 +BRDA:213,8,0,13 +DA:214,13 +DA:216,86 +DA:227,57 +FN:227,RuleIdentityRegistryBase._detectTransferRestrictionFrom +FNDA:57,RuleIdentityRegistryBase._detectTransferRestrictionFrom +DA:234,57 +DA:239,57 +BRDA:239,9,0,11 +DA:240,11 +DA:249,46 +BRDA:249,10,0,5 +DA:250,5 +DA:252,41 +DA:258,26 +FN:258,RuleIdentityRegistryBase._transferred +FNDA:26,RuleIdentityRegistryBase._transferred +DA:259,26 +DA:260,26 +BRDA:260,11,0,5 +BRDA:260,11,1,21 +DA:269,29 +FN:269,RuleIdentityRegistryBase._transferredFrom +FNDA:29,RuleIdentityRegistryBase._transferredFrom +DA:270,29 +DA:271,29 +BRDA:271,12,0,4 +BRDA:271,12,1,25 FNF:15 FNH:14 -LF:64 -LH:63 -BRF:19 -BRH:19 +LF:63 +LH:62 +BRF:18 +BRH:18 end_of_record TN: -SF:src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol -DA:36,550 -FN:36,RuleMaxTotalSupplyBase.constructor -FNDA:550,RuleMaxTotalSupplyBase.constructor -DA:37,550 -DA:38,547 -DA:39,547 -DA:51,3 -FN:51,RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode -FNDA:3,RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode -DA:52,3 -DA:63,260 -FN:63,RuleMaxTotalSupplyBase.setMaxTotalSupply -FNDA:260,RuleMaxTotalSupplyBase.setMaxTotalSupply -DA:64,258 -DA:65,258 -DA:72,8 -FN:72,RuleMaxTotalSupplyBase.setTokenContract -FNDA:8,RuleMaxTotalSupplyBase.setTokenContract -DA:73,6 -DA:74,3 -DA:75,3 -DA:81,3 -FN:81,RuleMaxTotalSupplyBase.transferred.0 -FNDA:3,RuleMaxTotalSupplyBase.transferred.0 -DA:82,3 -DA:88,2 -FN:88,RuleMaxTotalSupplyBase.transferred.1 -FNDA:2,RuleMaxTotalSupplyBase.transferred.1 -DA:89,2 -DA:95,3 -FN:95,RuleMaxTotalSupplyBase.messageForTransferRestriction -FNDA:3,RuleMaxTotalSupplyBase.messageForTransferRestriction +SF:src/rules/validation/abstract/base/RuleMaxBalanceBase.sol +DA:42,63 +FN:42,RuleMaxBalanceBase.constructor +FNDA:63,RuleMaxBalanceBase.constructor +DA:43,63 +DA:44,60 +DA:56,3 +FN:56,RuleMaxBalanceBase.canReturnTransferRestrictionCode +FNDA:3,RuleMaxBalanceBase.canReturnTransferRestrictionCode +DA:57,3 +DA:76,5 +FN:76,RuleMaxBalanceBase.remainingCapacity +FNDA:5,RuleMaxBalanceBase.remainingCapacity +DA:77,5 +DA:78,5 +BRDA:78,0,0,1 +DA:79,1 +DA:81,4 +DA:87,5 +FN:87,RuleMaxBalanceBase.transferred.0 +FNDA:5,RuleMaxBalanceBase.transferred.0 +DA:88,5 +DA:94,15 +FN:94,RuleMaxBalanceBase.transferred.1 +FNDA:15,RuleMaxBalanceBase.transferred.1 +DA:95,15 DA:101,3 -BRDA:101,0,0,1 -BRDA:101,0,1,1 -DA:102,1 -DA:103,2 -BRDA:103,1,0,1 -DA:104,1 -DA:106,1 -DA:113,260 -FN:113,RuleMaxTotalSupplyBase.onlyMaxTotalSupplyManager -FNDA:260,RuleMaxTotalSupplyBase.onlyMaxTotalSupplyManager -DA:114,260 -DA:121,0 -FN:121,RuleMaxTotalSupplyBase._authorizeMaxTotalSupplyManager -FNDA:0,RuleMaxTotalSupplyBase._authorizeMaxTotalSupplyManager -DA:135,556 -FN:135,RuleMaxTotalSupplyBase._validateTokenContract -FNDA:556,RuleMaxTotalSupplyBase._validateTokenContract -DA:136,556 -BRDA:136,2,0,2 -BRDA:136,2,1,554 -DA:137,554 -BRDA:137,3,0,2 -BRDA:137,3,1,552 -DA:138,552 -BRDA:138,4,0,552 -DA:139,2 -BRDA:139,4,1,2 -DA:140,2 -DA:157,788 -FN:157,RuleMaxTotalSupplyBase._currentSupply -FNDA:788,RuleMaxTotalSupplyBase._currentSupply -DA:158,788 -DA:159,788 -BRDA:159,5,0,788 -DA:160,784 -DA:161,4 -BRDA:161,5,1,4 -DA:162,4 -DA:169,793 -FN:169,RuleMaxTotalSupplyBase._detectTransferRestriction -FNDA:793,RuleMaxTotalSupplyBase._detectTransferRestriction -DA:180,793 -BRDA:180,6,0,788 -DA:181,788 -DA:182,788 -BRDA:182,7,0,4 -DA:183,4 -DA:187,784 -BRDA:187,8,0,452 -DA:188,452 -DA:191,337 -DA:197,4 -FN:197,RuleMaxTotalSupplyBase._detectTransferRestrictionFrom +FN:101,RuleMaxBalanceBase.messageForTransferRestriction +FNDA:3,RuleMaxBalanceBase.messageForTransferRestriction +DA:107,3 +BRDA:107,1,0,1 +BRDA:107,1,1,1 +DA:108,1 +DA:109,2 +BRDA:109,2,0,1 +DA:110,1 +DA:112,1 +DA:122,46 +FN:122,RuleMaxBalanceBase._detectTransferRestriction +FNDA:46,RuleMaxBalanceBase._detectTransferRestriction +DA:134,46 +DA:135,46 +BRDA:135,3,0,2 +DA:136,2 +DA:138,17 +BRDA:138,4,0,17 +DA:139,17 +DA:141,27 +DA:149,2 +FN:149,RuleMaxBalanceBase._detectTransferRestrictionFrom +FNDA:2,RuleMaxBalanceBase._detectTransferRestrictionFrom +DA:156,2 +DA:173,18 +FN:173,RuleMaxBalanceBase._detectTransferRestrictionOnNotify +FNDA:18,RuleMaxBalanceBase._detectTransferRestrictionOnNotify +DA:179,18 +DA:188,5 +FN:188,RuleMaxBalanceBase._transferred +FNDA:5,RuleMaxBalanceBase._transferred +DA:189,5 +DA:190,5 +BRDA:190,5,0,3 +BRDA:190,5,1,2 +DA:203,15 +FN:203,RuleMaxBalanceBase._transferredFrom +FNDA:15,RuleMaxBalanceBase._transferredFrom +DA:204,15 +DA:205,15 +BRDA:205,6,0,3 +BRDA:205,6,1,12 +FNF:11 +FNH:11 +LF:37 +LH:37 +BRF:10 +BRH:10 +end_of_record +TN: +SF:src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol +DA:28,588 +FN:28,RuleMaxTotalSupplyBase.constructor +FNDA:588,RuleMaxTotalSupplyBase.constructor +DA:29,588 +DA:30,585 +DA:42,4 +FN:42,RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode +FNDA:4,RuleMaxTotalSupplyBase.canReturnTransferRestrictionCode +DA:43,4 +DA:53,18 +FN:53,RuleMaxTotalSupplyBase.transferred.0 +FNDA:18,RuleMaxTotalSupplyBase.transferred.0 +DA:54,18 +DA:60,50 +FN:60,RuleMaxTotalSupplyBase.transferred.1 +FNDA:50,RuleMaxTotalSupplyBase.transferred.1 +DA:61,50 +DA:67,4 +FN:67,RuleMaxTotalSupplyBase.messageForTransferRestriction +FNDA:4,RuleMaxTotalSupplyBase.messageForTransferRestriction +DA:73,4 +BRDA:73,0,0,2 +BRDA:73,0,1,1 +DA:74,2 +DA:75,2 +BRDA:75,1,0,1 +DA:76,1 +DA:78,1 +DA:88,868 +FN:88,RuleMaxTotalSupplyBase._detectTransferRestriction +FNDA:868,RuleMaxTotalSupplyBase._detectTransferRestriction +DA:100,868 +BRDA:100,2,0,856 +DA:101,856 +DA:102,856 +BRDA:102,3,0,4 +DA:103,4 +DA:105,470 +BRDA:105,4,0,470 +DA:106,470 +DA:109,394 +DA:115,4 +FN:115,RuleMaxTotalSupplyBase._detectTransferRestrictionFrom FNDA:4,RuleMaxTotalSupplyBase._detectTransferRestrictionFrom -DA:203,4 -DA:212,3 -FN:212,RuleMaxTotalSupplyBase._transferred -FNDA:3,RuleMaxTotalSupplyBase._transferred -DA:213,3 -DA:214,3 -BRDA:214,9,0,2 -BRDA:214,9,1,1 -DA:227,2 -FN:227,RuleMaxTotalSupplyBase._transferredFrom -FNDA:2,RuleMaxTotalSupplyBase._transferredFrom -DA:228,2 -DA:229,2 -BRDA:229,10,0,1 -BRDA:229,10,1,1 -FNF:15 -FNH:14 -LF:54 -LH:53 -BRF:18 -BRH:18 +DA:122,4 +DA:139,54 +FN:139,RuleMaxTotalSupplyBase._detectTransferRestrictionOnNotify +FNDA:54,RuleMaxTotalSupplyBase._detectTransferRestrictionOnNotify +DA:145,54 +DA:154,18 +FN:154,RuleMaxTotalSupplyBase._transferred +FNDA:18,RuleMaxTotalSupplyBase._transferred +DA:155,18 +DA:156,18 +BRDA:156,5,0,7 +BRDA:156,5,1,11 +DA:169,50 +FN:169,RuleMaxTotalSupplyBase._transferredFrom +FNDA:50,RuleMaxTotalSupplyBase._transferredFrom +DA:170,50 +DA:171,50 +BRDA:171,6,0,4 +BRDA:171,6,1,46 +FNF:10 +FNH:10 +LF:33 +LH:33 +BRF:10 +BRH:10 end_of_record TN: SF:src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol -DA:68,2 -FN:68,RuleReceiverWhitelistBase.canReturnTransferRestrictionCode +DA:58,2 +FN:58,RuleReceiverWhitelistBase.canReturnTransferRestrictionCode FNDA:2,RuleReceiverWhitelistBase.canReturnTransferRestrictionCode -DA:69,2 -DA:79,4 -FN:79,RuleReceiverWhitelistBase.transferred.0 +DA:59,2 +DA:69,4 +FN:69,RuleReceiverWhitelistBase.transferred.0 FNDA:4,RuleReceiverWhitelistBase.transferred.0 -DA:80,4 -DA:86,2 -FN:86,RuleReceiverWhitelistBase.transferred.1 +DA:70,4 +DA:76,2 +FN:76,RuleReceiverWhitelistBase.transferred.1 FNDA:2,RuleReceiverWhitelistBase.transferred.1 -DA:87,2 -DA:93,2 -FN:93,RuleReceiverWhitelistBase.messageForTransferRestriction +DA:77,2 +DA:83,2 +FN:83,RuleReceiverWhitelistBase.messageForTransferRestriction FNDA:2,RuleReceiverWhitelistBase.messageForTransferRestriction -DA:99,2 -BRDA:99,0,0,1 -DA:100,1 -DA:102,1 -DA:108,7 -FN:108,RuleReceiverWhitelistBase.supportsInterface -FNDA:7,RuleReceiverWhitelistBase.supportsInterface -DA:111,7 -DA:112,6 +DA:89,2 +BRDA:89,0,0,1 +DA:90,1 +DA:92,1 +DA:98,8 +FN:98,RuleReceiverWhitelistBase.supportsInterface +FNDA:8,RuleReceiverWhitelistBase.supportsInterface +DA:101,8 +DA:102,7 +DA:103,7 +DA:104,6 +DA:111,1 +FN:111,RuleReceiverWhitelistBase.isAllowList +FNDA:1,RuleReceiverWhitelistBase.isAllowList +DA:112,1 DA:125,21 FN:125,RuleReceiverWhitelistBase._detectTransferRestriction FNDA:21,RuleReceiverWhitelistBase._detectTransferRestriction @@ -2477,26 +1714,26 @@ DA:175,2 DA:176,2 BRDA:176,3,0,1 BRDA:176,3,1,1 -FNF:9 -FNH:9 -LF:25 -LH:25 +FNF:10 +FNH:10 +LF:29 +LH:29 BRF:6 BRH:6 end_of_record TN: SF:src/rules/validation/abstract/base/RuleSanctionsListBase.sol -DA:32,49 +DA:32,82 FN:32,RuleSanctionsListBase.constructor -FNDA:49,RuleSanctionsListBase.constructor -DA:35,48 -BRDA:35,0,0,21 -DA:36,21 -DA:47,3 +FNDA:82,RuleSanctionsListBase.constructor +DA:35,81 +BRDA:35,0,0,51 +DA:36,51 +DA:47,5 FN:47,RuleSanctionsListBase.canReturnTransferRestrictionCode -FNDA:3,RuleSanctionsListBase.canReturnTransferRestrictionCode -DA:48,3 -DA:49,1 +FNDA:5,RuleSanctionsListBase.canReturnTransferRestrictionCode +DA:48,5 +DA:49,2 DA:61,18 FN:61,RuleSanctionsListBase.setSanctionListOracle FNDA:18,RuleSanctionsListBase.setSanctionListOracle @@ -2508,21 +1745,21 @@ DA:70,3 FN:70,RuleSanctionsListBase.clearSanctionListOracle FNDA:3,RuleSanctionsListBase.clearSanctionListOracle DA:71,3 -DA:77,9 +DA:77,18 FN:77,RuleSanctionsListBase.transferred.0 -FNDA:9,RuleSanctionsListBase.transferred.0 -DA:78,9 -DA:84,41 +FNDA:18,RuleSanctionsListBase.transferred.0 +DA:78,18 +DA:84,86 FN:84,RuleSanctionsListBase.transferred.1 -FNDA:41,RuleSanctionsListBase.transferred.1 -DA:85,41 -DA:91,4 +FNDA:86,RuleSanctionsListBase.transferred.1 +DA:85,86 +DA:91,5 FN:91,RuleSanctionsListBase.messageForTransferRestriction -FNDA:4,RuleSanctionsListBase.messageForTransferRestriction -DA:97,4 -BRDA:97,2,0,1 +FNDA:5,RuleSanctionsListBase.messageForTransferRestriction +DA:97,5 +BRDA:97,2,0,2 BRDA:97,2,1,1 -DA:98,1 +DA:98,2 DA:99,3 BRDA:99,3,0,1 BRDA:99,3,1,1 @@ -2535,407 +1772,669 @@ DA:111,3 FN:111,RuleSanctionsListBase.onlySanctionListManager FNDA:3,RuleSanctionsListBase.onlySanctionListManager DA:112,3 -DA:124,39 +DA:124,69 FN:124,RuleSanctionsListBase._setSanctionListOracle -FNDA:39,RuleSanctionsListBase._setSanctionListOracle -DA:125,39 -DA:126,39 +FNDA:69,RuleSanctionsListBase._setSanctionListOracle +DA:125,69 +DA:126,69 DA:133,0 FN:133,RuleSanctionsListBase._authorizeSanctionListManager FNDA:0,RuleSanctionsListBase._authorizeSanctionListManager -DA:141,119 -FN:141,RuleSanctionsListBase._detectTransferRestriction -FNDA:119,RuleSanctionsListBase._detectTransferRestriction -DA:151,119 -BRDA:151,5,0,112 -DA:152,112 -BRDA:152,6,0,27 -BRDA:152,6,1,73 -DA:153,27 -DA:154,85 -BRDA:154,7,0,12 -DA:155,12 -DA:158,80 -DA:169,69 -FN:169,RuleSanctionsListBase._detectTransferRestrictionFrom -FNDA:69,RuleSanctionsListBase._detectTransferRestrictionFrom -DA:176,69 -BRDA:176,8,0,68 -DA:177,68 -BRDA:177,9,0,6 -DA:178,6 -DA:180,62 -DA:182,1 -DA:191,19 -FN:191,RuleSanctionsListBase._transferred -FNDA:19,RuleSanctionsListBase._transferred -DA:192,19 -DA:193,19 -BRDA:193,10,0,10 -BRDA:193,10,1,9 -DA:206,48 -FN:206,RuleSanctionsListBase._transferredFrom -FNDA:48,RuleSanctionsListBase._transferredFrom -DA:207,48 -DA:208,48 -BRDA:208,11,0,6 -BRDA:208,11,1,42 +DA:147,212 +FN:147,RuleSanctionsListBase._detectTransferRestriction +FNDA:212,RuleSanctionsListBase._detectTransferRestriction +DA:160,212 +DA:161,212 +BRDA:161,5,0,198 +DA:162,198 +BRDA:162,6,0,40 +BRDA:162,6,1,141 +DA:163,40 +DA:164,158 +BRDA:164,7,0,17 +DA:165,17 +DA:168,155 +DA:179,124 +FN:179,RuleSanctionsListBase._detectTransferRestrictionFrom +FNDA:124,RuleSanctionsListBase._detectTransferRestrictionFrom +DA:186,124 +DA:191,124 +BRDA:191,8,0,8 +DA:192,8 +DA:194,116 +DA:203,34 +FN:203,RuleSanctionsListBase._transferred +FNDA:34,RuleSanctionsListBase._transferred +DA:204,34 +DA:205,34 +BRDA:205,9,0,16 +BRDA:205,9,1,18 +DA:218,93 +FN:218,RuleSanctionsListBase._transferredFrom +FNDA:93,RuleSanctionsListBase._transferredFrom +DA:219,93 +DA:220,93 +BRDA:220,10,0,6 +BRDA:220,10,1,87 FNF:14 FNH:13 LF:48 LH:47 -BRF:18 -BRH:18 +BRF:17 +BRH:17 end_of_record TN: SF:src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol -DA:38,2 -FN:38,RuleSpenderWhitelistBase.canReturnTransferRestrictionCode +DA:45,2 +FN:45,RuleSpenderWhitelistBase.canReturnTransferRestrictionCode FNDA:2,RuleSpenderWhitelistBase.canReturnTransferRestrictionCode -DA:39,2 -DA:49,3 -FN:49,RuleSpenderWhitelistBase.transferred.0 -FNDA:3,RuleSpenderWhitelistBase.transferred.0 -DA:54,6 -FN:54,RuleSpenderWhitelistBase.transferred.1 +DA:46,2 +DA:56,4 +FN:56,RuleSpenderWhitelistBase.transferred.0 +FNDA:4,RuleSpenderWhitelistBase.transferred.0 +DA:61,6 +FN:61,RuleSpenderWhitelistBase.transferred.1 FNDA:6,RuleSpenderWhitelistBase.transferred.1 -DA:55,6 -DA:61,2 -FN:61,RuleSpenderWhitelistBase.messageForTransferRestriction +DA:62,6 +DA:68,2 +FN:68,RuleSpenderWhitelistBase.messageForTransferRestriction FNDA:2,RuleSpenderWhitelistBase.messageForTransferRestriction -DA:67,2 -BRDA:67,0,0,1 -DA:68,1 -DA:70,1 -DA:76,8 -FN:76,RuleSpenderWhitelistBase.supportsInterface -FNDA:8,RuleSpenderWhitelistBase.supportsInterface -DA:79,8 -DA:80,6 -DA:91,12 -FN:91,RuleSpenderWhitelistBase._detectTransferRestriction -FNDA:12,RuleSpenderWhitelistBase._detectTransferRestriction -DA:92,12 -DA:102,35 -FN:102,RuleSpenderWhitelistBase._detectTransferRestrictionFrom -FNDA:35,RuleSpenderWhitelistBase._detectTransferRestrictionFrom -DA:111,35 -BRDA:111,1,0,13 -DA:112,13 -DA:114,22 -DA:120,9 -FN:120,RuleSpenderWhitelistBase._transferred -FNDA:9,RuleSpenderWhitelistBase._transferred -DA:131,17 -FN:131,RuleSpenderWhitelistBase._transferredFrom -FNDA:17,RuleSpenderWhitelistBase._transferredFrom -DA:132,17 -DA:133,17 -BRDA:133,2,0,7 -BRDA:133,2,1,10 +DA:74,2 +BRDA:74,0,0,1 +DA:75,1 +DA:77,1 +DA:83,14 +FN:83,RuleSpenderWhitelistBase.supportsInterface +FNDA:14,RuleSpenderWhitelistBase.supportsInterface +DA:86,14 +DA:87,12 +DA:88,10 +DA:99,18 +FN:99,RuleSpenderWhitelistBase._detectTransferRestriction +FNDA:18,RuleSpenderWhitelistBase._detectTransferRestriction +DA:100,18 +DA:110,38 +FN:110,RuleSpenderWhitelistBase._detectTransferRestrictionFrom +FNDA:38,RuleSpenderWhitelistBase._detectTransferRestrictionFrom +DA:119,38 +BRDA:119,1,0,16 +DA:120,16 +DA:122,22 +DA:128,14 +FN:128,RuleSpenderWhitelistBase._transferred +FNDA:14,RuleSpenderWhitelistBase._transferred +DA:139,18 +FN:139,RuleSpenderWhitelistBase._transferredFrom +FNDA:18,RuleSpenderWhitelistBase._transferredFrom +DA:140,18 +DA:141,18 +BRDA:141,2,0,8 +BRDA:141,2,1,10 FNF:9 FNH:9 -LF:22 -LH:22 +LF:23 +LH:23 +BRF:4 +BRH:4 +end_of_record +TN: +SF:src/rules/validation/abstract/base/RuleWhitelistBase.sol +DA:38,226 +FN:38,RuleWhitelistBase.constructor +FNDA:226,RuleWhitelistBase.constructor +DA:41,226 +DA:42,226 +DA:52,6 +FN:52,RuleWhitelistBase.isVerified +FNDA:6,RuleWhitelistBase.isVerified +DA:59,6 +DA:65,473 +FN:65,RuleWhitelistBase.supportsInterface +FNDA:473,RuleWhitelistBase.supportsInterface +DA:68,473 +DA:69,471 +DA:70,368 +DA:71,265 +DA:78,104 +FN:78,RuleWhitelistBase.isAllowList +FNDA:104,RuleWhitelistBase.isAllowList +DA:79,104 +DA:96,155 +FN:96,RuleWhitelistBase._detectTransferRestriction +FNDA:155,RuleWhitelistBase._detectTransferRestriction +DA:107,155 +DA:108,155 +DA:111,155 +DA:112,155 +BRDA:112,0,0,11 +DA:113,11 +DA:118,144 +BRDA:118,1,0,38 +DA:119,38 +DA:121,106 +BRDA:121,2,0,16 +DA:122,16 +DA:124,90 +DA:135,43 +FN:135,RuleWhitelistBase._detectTransferRestrictionFrom +FNDA:43,RuleWhitelistBase._detectTransferRestrictionFrom +DA:144,43 +BRDA:144,3,0,8 +DA:145,8 +DA:147,35 +FNF:6 +FNH:6 +LF:27 +LH:27 BRF:4 BRH:4 end_of_record TN: -SF:src/rules/validation/abstract/base/RuleWhitelistBase.sol -DA:32,201 -FN:32,RuleWhitelistBase.constructor -FNDA:201,RuleWhitelistBase.constructor -DA:35,201 -DA:36,201 -DA:48,3 -FN:48,RuleWhitelistBase.setCheckSpender -FNDA:3,RuleWhitelistBase.setCheckSpender -DA:49,2 -DA:50,2 -DA:56,6 -FN:56,RuleWhitelistBase.isVerified -FNDA:6,RuleWhitelistBase.isVerified -DA:63,6 -DA:69,61 -FN:69,RuleWhitelistBase.supportsInterface -FNDA:61,RuleWhitelistBase.supportsInterface -DA:72,61 -DA:73,59 -DA:80,3 -FN:80,RuleWhitelistBase.onlyCheckSpenderManager -FNDA:3,RuleWhitelistBase.onlyCheckSpenderManager -DA:81,3 -DA:93,2 -FN:93,RuleWhitelistBase._setCheckSpender -FNDA:2,RuleWhitelistBase._setCheckSpender -DA:94,2 -DA:101,0 -FN:101,RuleWhitelistBase._authorizeCheckSpenderManager -FNDA:0,RuleWhitelistBase._authorizeCheckSpenderManager -DA:109,132 -FN:109,RuleWhitelistBase._detectTransferRestriction -FNDA:132,RuleWhitelistBase._detectTransferRestriction -DA:120,132 -DA:121,132 -DA:124,132 -DA:125,132 -BRDA:125,0,0,9 -DA:126,9 -DA:131,123 -BRDA:131,1,0,30 -DA:132,30 -DA:134,93 -BRDA:134,2,0,15 -DA:135,15 -DA:137,78 -DA:148,38 -FN:148,RuleWhitelistBase._detectTransferRestrictionFrom -FNDA:38,RuleWhitelistBase._detectTransferRestrictionFrom -DA:157,38 -BRDA:157,3,0,8 -DA:158,8 -DA:160,30 -FNF:9 -FNH:8 -LF:31 -LH:30 -BRF:4 -BRH:4 +SF:src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol +DA:47,64 +FN:47,RuleWhitelistWrapperBase.constructor +FNDA:64,RuleWhitelistWrapperBase.constructor +DA:50,64 +DA:51,64 +DA:61,52 +FN:61,RuleWhitelistWrapperBase.supportsInterface +FNDA:52,RuleWhitelistWrapperBase.supportsInterface +DA:62,52 +DA:73,9 +FN:73,RuleWhitelistWrapperBase.isVerified +FNDA:9,RuleWhitelistWrapperBase.isVerified +DA:74,9 +DA:88,82 +FN:88,RuleWhitelistWrapperBase._detectTransferRestriction +FNDA:82,RuleWhitelistWrapperBase._detectTransferRestriction +DA:100,82 +DA:101,82 +BRDA:101,0,0,4 +DA:102,4 +DA:105,78 +DA:106,78 +DA:113,78 +BRDA:113,1,0,2 +DA:114,2 +DA:116,3 +BRDA:116,2,0,3 +DA:117,3 +BRDA:117,3,0,1 +DA:118,1 +DA:120,2 +DA:122,2 +BRDA:122,4,0,2 +DA:123,2 +BRDA:123,5,0,1 +DA:124,1 +DA:126,1 +DA:129,71 +DA:130,71 +DA:131,71 +DA:133,71 +DA:134,71 +BRDA:134,6,0,30 +BRDA:134,6,1,32 +DA:135,30 +DA:136,41 +BRDA:136,7,0,9 +BRDA:136,7,1,32 +DA:137,9 +DA:139,32 +DA:148,14 +FN:148,RuleWhitelistWrapperBase._isListedInAnyChild +FNDA:14,RuleWhitelistWrapperBase._isListedInAnyChild +DA:149,14 +DA:150,14 +DA:151,14 +DA:162,38 +FN:162,RuleWhitelistWrapperBase._detectTransferRestrictionFrom +FNDA:38,RuleWhitelistWrapperBase._detectTransferRestrictionFrom +DA:171,38 +BRDA:171,8,0,2 +DA:172,2 +DA:175,36 +DA:176,36 +DA:177,36 +DA:178,36 +DA:180,36 +DA:182,36 +BRDA:182,9,0,9 +BRDA:182,9,1,18 +DA:183,9 +DA:184,27 +BRDA:184,10,0,1 +BRDA:184,10,1,18 +DA:185,1 +DA:186,26 +BRDA:186,11,0,8 +BRDA:186,11,1,18 +DA:187,8 +DA:189,18 +DA:201,28 +FN:201,RuleWhitelistWrapperBase._transferred.0 +FNDA:28,RuleWhitelistWrapperBase._transferred.0 +DA:207,28 +DA:217,1 +FN:217,RuleWhitelistWrapperBase._transferred.1 +FNDA:1,RuleWhitelistWrapperBase._transferred.1 +DA:223,1 +DA:243,106 +FN:243,RuleWhitelistWrapperBase._checkRule +FNDA:106,RuleWhitelistWrapperBase._checkRule +DA:244,106 +DA:245,106 +BRDA:245,12,0,2 +BRDA:245,12,1,104 +DA:251,104 +BRDA:251,13,0,1 +BRDA:251,13,1,103 +DA:255,103 +BRDA:255,14,0,1 +BRDA:255,14,1,102 +DA:263,121 +FN:263,RuleWhitelistWrapperBase._detectTransferRestrictionForTargets +FNDA:121,RuleWhitelistWrapperBase._detectTransferRestrictionForTargets +DA:269,121 +DA:270,121 +DA:271,121 +DA:275,121 +DA:276,121 +DA:279,174 +DA:280,174 +DA:281,386 +BRDA:281,15,0,188 +DA:282,188 +DA:283,188 +DA:288,174 +BRDA:288,16,0,57 +DA:289,57 +DA:292,121 +DA:303,189 +FN:303,RuleWhitelistWrapperBase._msgSender +FNDA:189,RuleWhitelistWrapperBase._msgSender +DA:304,189 +DA:311,2 +FN:311,RuleWhitelistWrapperBase._msgData +FNDA:2,RuleWhitelistWrapperBase._msgData +DA:312,2 +DA:319,191 +FN:319,RuleWhitelistWrapperBase._contextSuffixLength +FNDA:191,RuleWhitelistWrapperBase._contextSuffixLength +DA:320,191 +FNF:13 +FNH:13 +LF:80 +LH:80 +BRF:25 +BRH:25 +end_of_record +TN: +SF:src/rules/validation/abstract/core/BalanceCapManager.sol +DA:43,4 +FN:43,BalanceCapManager.onlyMaxBalanceManager +FNDA:4,BalanceCapManager.onlyMaxBalanceManager +DA:44,4 +DA:59,6 +FN:59,BalanceCapManager.setMaxBalance +FNDA:6,BalanceCapManager.setMaxBalance +DA:60,4 +DA:67,5 +FN:67,BalanceCapManager.setBalanceToken +FNDA:5,BalanceCapManager.setBalanceToken +DA:68,4 +DA:78,12 +FN:78,BalanceCapManager.addExemptAddress +FNDA:12,BalanceCapManager.addExemptAddress +DA:79,10 +DA:88,3 +FN:88,BalanceCapManager.removeExemptAddress +FNDA:3,BalanceCapManager.removeExemptAddress +DA:89,3 +DA:98,4 +FN:98,BalanceCapManager.addExemptAddresses +FNDA:4,BalanceCapManager.addExemptAddresses +DA:99,4 +DA:100,3 +DA:108,2 +FN:108,BalanceCapManager.removeExemptAddresses +FNDA:2,BalanceCapManager.removeExemptAddresses +DA:109,2 +DA:110,2 +DA:118,5 +FN:118,BalanceCapManager.isExemptAddress +FNDA:5,BalanceCapManager.isExemptAddress +DA:119,5 +DA:126,6 +FN:126,BalanceCapManager.exemptAddressCount +FNDA:6,BalanceCapManager.exemptAddressCount +DA:127,6 +DA:146,10 +FN:146,BalanceCapManager._addExemptAddress +FNDA:10,BalanceCapManager._addExemptAddress +DA:147,10 +BRDA:147,0,0,1 +BRDA:147,0,1,9 +DA:148,9 +BRDA:148,1,0,1 +BRDA:148,1,1,8 +DA:149,8 +DA:156,3 +FN:156,BalanceCapManager._removeExemptAddress +FNDA:3,BalanceCapManager._removeExemptAddress +DA:157,3 +BRDA:157,2,0,1 +BRDA:157,2,1,2 +DA:158,2 +DA:165,64 +FN:165,BalanceCapManager._setMaxBalance +FNDA:64,BalanceCapManager._setMaxBalance +DA:166,64 +DA:167,64 +DA:176,67 +FN:176,BalanceCapManager._setBalanceToken +FNDA:67,BalanceCapManager._setBalanceToken +DA:177,67 +BRDA:177,3,0,1 +BRDA:177,3,1,66 +DA:180,66 +BRDA:180,4,0,2 +BRDA:180,4,1,64 +DA:181,64 +BRDA:181,5,0,64 +DA:184,2 +BRDA:184,5,1,2 +DA:185,2 +DA:187,62 +DA:188,62 +DA:195,0 +FN:195,BalanceCapManager._authorizeMaxBalanceManager +FNDA:0,BalanceCapManager._authorizeMaxBalanceManager +DA:208,5 +FN:208,BalanceCapManager._remainingCapacity +FNDA:5,BalanceCapManager._remainingCapacity +DA:209,5 +BRDA:209,6,0,1 +DA:210,1 +DA:212,4 +DA:213,4 +BRDA:213,7,0,1 +DA:214,1 +DA:216,3 +DA:228,44 +FN:228,BalanceCapManager._balanceOf +FNDA:44,BalanceCapManager._balanceOf +DA:229,44 +BRDA:229,8,0,44 +DA:230,41 +DA:231,3 +BRDA:231,8,1,3 +DA:232,3 +DA:248,46 +FN:248,BalanceCapManager._capExceeded +FNDA:46,BalanceCapManager._capExceeded +DA:256,46 +BRDA:256,9,0,6 +DA:257,6 +DA:259,40 +DA:260,40 +DA:261,40 +BRDA:261,10,0,2 +DA:262,2 +DA:264,38 +FNF:17 +FNH:16 +LF:59 +LH:58 +BRF:18 +BRH:18 +end_of_record +TN: +SF:src/rules/validation/abstract/core/CapAccounting.sol +DA:31,1355 +FN:31,CapAccounting._capExceededBy +FNDA:1355,CapAccounting._capExceededBy +DA:33,1355 +BRDA:33,0,0,444 +DA:34,444 +DA:36,911 +DA:45,3 +FN:45,CapAccounting._capHeadroom +FNDA:3,CapAccounting._capHeadroom +DA:46,3 +FNF:2 +FNH:2 +LF:6 +LH:6 +BRF:1 +BRH:1 end_of_record TN: -SF:src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol -DA:37,57 -FN:37,RuleWhitelistWrapperBase.constructor -FNDA:57,RuleWhitelistWrapperBase.constructor -DA:40,57 -DA:41,57 -DA:48,4 -FN:48,RuleWhitelistWrapperBase.onlyCheckSpenderManager -FNDA:4,RuleWhitelistWrapperBase.onlyCheckSpenderManager -DA:49,4 -DA:65,4 -FN:65,RuleWhitelistWrapperBase.setCheckSpender -FNDA:4,RuleWhitelistWrapperBase.setCheckSpender -DA:66,3 -DA:67,3 -DA:73,49 -FN:73,RuleWhitelistWrapperBase.supportsInterface -FNDA:49,RuleWhitelistWrapperBase.supportsInterface -DA:74,49 -DA:83,7 -FN:83,RuleWhitelistWrapperBase.isVerified -FNDA:7,RuleWhitelistWrapperBase.isVerified -DA:84,7 -DA:85,7 -DA:86,7 -DA:87,7 -DA:98,3 -FN:98,RuleWhitelistWrapperBase._setCheckSpender -FNDA:3,RuleWhitelistWrapperBase._setCheckSpender -DA:99,3 -DA:108,0 -FN:108,RuleWhitelistWrapperBase._authorizeCheckSpenderManager -FNDA:0,RuleWhitelistWrapperBase._authorizeCheckSpenderManager -DA:117,66 -FN:117,RuleWhitelistWrapperBase._detectTransferRestriction -FNDA:66,RuleWhitelistWrapperBase._detectTransferRestriction -DA:129,66 -DA:130,66 -BRDA:130,0,0,4 -DA:131,4 -DA:134,62 -DA:135,62 -DA:142,62 -BRDA:142,1,0,2 -DA:143,2 -DA:145,3 -BRDA:145,2,0,3 -DA:146,3 -BRDA:146,3,0,1 -DA:147,1 -DA:149,2 -DA:151,2 -BRDA:151,4,0,2 -DA:152,2 -BRDA:152,5,0,1 -DA:153,1 -DA:155,1 -DA:158,55 -DA:159,55 -DA:160,55 -DA:162,55 -DA:163,54 -BRDA:163,6,0,22 -BRDA:163,6,1,24 -DA:164,22 -DA:165,32 -BRDA:165,7,0,8 -BRDA:165,7,1,24 -DA:166,8 -DA:168,24 -DA:177,5 -FN:177,RuleWhitelistWrapperBase._isListedInAnyChild -FNDA:5,RuleWhitelistWrapperBase._isListedInAnyChild -DA:178,5 -DA:179,5 -DA:180,5 -DA:191,37 -FN:191,RuleWhitelistWrapperBase._detectTransferRestrictionFrom -FNDA:37,RuleWhitelistWrapperBase._detectTransferRestrictionFrom -DA:200,37 -BRDA:200,8,0,2 -DA:201,2 -DA:204,35 -DA:205,35 -DA:206,35 -DA:207,35 -DA:209,35 -DA:211,35 -BRDA:211,9,0,9 -BRDA:211,9,1,17 -DA:212,9 -DA:213,26 -BRDA:213,10,0,1 -BRDA:213,10,1,17 -DA:214,1 -DA:215,25 -BRDA:215,11,0,8 -BRDA:215,11,1,17 -DA:216,8 -DA:218,17 -DA:230,20 -FN:230,RuleWhitelistWrapperBase._transferred.0 -FNDA:20,RuleWhitelistWrapperBase._transferred.0 -DA:236,20 -DA:246,1 -FN:246,RuleWhitelistWrapperBase._transferred.1 -FNDA:1,RuleWhitelistWrapperBase._transferred.1 -DA:252,1 -DA:260,102 -FN:260,RuleWhitelistWrapperBase._detectTransferRestrictionForTargets -FNDA:102,RuleWhitelistWrapperBase._detectTransferRestrictionForTargets -DA:266,102 -DA:267,102 -DA:268,102 -DA:271,153 -DA:272,152 -DA:273,160 -BRDA:273,12,0,160 -DA:274,160 -DA:279,152 -DA:280,152 -DA:281,278 -BRDA:281,13,0,105 -DA:282,105 -DA:283,105 -DA:286,47 -BRDA:286,14,0,47 -DA:287,47 -DA:290,101 -DA:301,175 -FN:301,RuleWhitelistWrapperBase._msgSender -FNDA:175,RuleWhitelistWrapperBase._msgSender -DA:302,175 -DA:309,2 -FN:309,RuleWhitelistWrapperBase._msgData -FNDA:2,RuleWhitelistWrapperBase._msgData -DA:310,2 -DA:317,177 -FN:317,RuleWhitelistWrapperBase._contextSuffixLength -FNDA:177,RuleWhitelistWrapperBase._contextSuffixLength -DA:318,177 -FNF:16 -FNH:15 -LF:88 -LH:87 -BRF:20 -BRH:20 +SF:src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol +DA:52,8 +FN:52,ChainlinkPoRFeedManager.onlyChainlinkPoRManager +FNDA:8,ChainlinkPoRFeedManager.onlyChainlinkPoRManager +DA:53,8 +DA:67,8 +FN:67,ChainlinkPoRFeedManager.setReservesFeed +FNDA:8,ChainlinkPoRFeedManager.setReservesFeed +DA:68,6 +DA:77,12 +FN:77,ChainlinkPoRFeedManager.setTokenMetadata +FNDA:12,ChainlinkPoRFeedManager.setTokenMetadata +DA:78,10 +DA:85,8 +FN:85,ChainlinkPoRFeedManager.setMaxStalenessSeconds +FNDA:8,ChainlinkPoRFeedManager.setMaxStalenessSeconds +DA:86,5 +DA:96,4 +FN:96,ChainlinkPoRFeedManager.feedDecimals +FNDA:4,ChainlinkPoRFeedManager.feedDecimals +DA:97,4 +DA:110,658 +FN:110,ChainlinkPoRFeedManager.maxBackedSupply +FNDA:658,ChainlinkPoRFeedManager.maxBackedSupply +DA:111,658 +DA:126,630 +FN:126,ChainlinkPoRFeedManager._setReservesFeed +FNDA:630,ChainlinkPoRFeedManager._setReservesFeed +DA:127,630 +DA:128,630 +BRDA:128,0,0,1 +BRDA:128,0,1,629 +DA:129,629 +BRDA:129,1,0,1 +BRDA:129,1,1,628 +DA:130,628 +DA:131,628 +BRDA:131,2,0,628 +DA:132,627 +DA:133,1 +BRDA:133,2,1,1 +DA:134,1 +DA:136,627 +BRDA:136,3,0,1 +BRDA:136,3,1,626 +DA:137,626 +DA:138,626 +DA:149,632 +FN:149,ChainlinkPoRFeedManager._setTokenMetadata +FNDA:632,ChainlinkPoRFeedManager._setTokenMetadata +DA:150,632 +BRDA:150,4,0,2 +BRDA:150,4,1,630 +DA:154,630 +BRDA:154,5,0,2 +BRDA:154,5,1,628 +DA:155,628 +BRDA:155,6,0,1 +BRDA:155,6,1,627 +DA:156,627 +BRDA:156,7,0,627 +DA:157,611 +BRDA:157,8,0,1 +BRDA:157,8,1,610 +DA:166,626 +BRDA:166,9,0,1 +BRDA:166,9,1,625 +DA:169,625 +DA:170,625 +DA:171,625 +DA:178,625 +FN:178,ChainlinkPoRFeedManager._setMaxStalenessSeconds +FNDA:625,ChainlinkPoRFeedManager._setMaxStalenessSeconds +DA:179,625 +DA:180,625 +DA:187,0 +FN:187,ChainlinkPoRFeedManager._authorizeChainlinkPoRManager +FNDA:0,ChainlinkPoRFeedManager._authorizeChainlinkPoRManager +DA:195,1278 +FN:195,ChainlinkPoRFeedManager._maxBackedSupply +FNDA:1278,ChainlinkPoRFeedManager._maxBackedSupply +DA:196,1278 +DA:199,1278 +DA:200,1278 +BRDA:200,10,0,1278 +DA:201,1276 +DA:202,2 +BRDA:202,10,1,2 +DA:203,2 +DA:207,1276 +BRDA:207,11,0,3 +DA:208,3 +DA:210,1273 +BRDA:210,12,0,1273 +DA:216,1270 +BRDA:216,13,0,144 +DA:217,144 +DA:219,1126 +DA:221,1126 +BRDA:221,14,0,3 +DA:222,3 +DA:226,1123 +DA:227,1123 +DA:228,3 +BRDA:228,12,1,3 +DA:229,3 +DA:236,469 +FN:236,ChainlinkPoRFeedManager._supplyToken +FNDA:469,ChainlinkPoRFeedManager._supplyToken +DA:237,469 +DA:249,1123 +FN:249,ChainlinkPoRFeedManager._scaleReserve +FNDA:1123,ChainlinkPoRFeedManager._scaleReserve +DA:250,1123 +DA:251,1123 +BRDA:251,15,0,74 +DA:252,74 +DA:254,1049 +BRDA:254,16,0,614 +DA:256,614 +DA:257,614 +BRDA:257,17,0,26 +DA:258,26 +DA:260,588 +DA:263,435 +FNF:13 +FNH:12 +LF:69 +LH:68 +BRF:29 +BRH:29 end_of_record TN: SF:src/rules/validation/abstract/core/RuleNFTAdapter.sol -DA:46,34 -FN:46,RuleNFTAdapter.transferred.0 -FNDA:34,RuleNFTAdapter.transferred.0 -DA:47,34 -BRDA:47,0,0,17 -BRDA:47,0,1,17 -DA:48,17 -DA:50,17 -DA:57,36 -FN:57,RuleNFTAdapter.transferred.1 -FNDA:36,RuleNFTAdapter.transferred.1 -DA:58,36 -BRDA:58,1,0,17 -BRDA:58,1,1,19 -DA:59,17 -DA:61,19 -DA:72,28 -FN:72,RuleNFTAdapter.transferred.2 +DA:56,47 +FN:56,RuleNFTAdapter.transferred.0 +FNDA:47,RuleNFTAdapter.transferred.0 +DA:57,47 +BRDA:57,0,0,17 +BRDA:57,0,1,30 +DA:58,17 +DA:60,30 +DA:67,50 +FN:67,RuleNFTAdapter.transferred.1 +FNDA:50,RuleNFTAdapter.transferred.1 +DA:68,50 +BRDA:68,1,0,17 +BRDA:68,1,1,33 +DA:69,17 +DA:71,33 +DA:82,28 +FN:82,RuleNFTAdapter.transferred.2 FNDA:28,RuleNFTAdapter.transferred.2 -DA:83,28 -DA:89,23 -FN:89,RuleNFTAdapter.transferred.3 -FNDA:23,RuleNFTAdapter.transferred.3 -DA:101,23 -DA:107,31 -FN:107,RuleNFTAdapter.detectTransferRestriction -FNDA:31,RuleNFTAdapter.detectTransferRestriction -DA:120,31 -DA:126,27 -FN:126,RuleNFTAdapter.detectTransferRestrictionFrom -FNDA:27,RuleNFTAdapter.detectTransferRestrictionFrom -DA:140,27 -DA:146,29 -FN:146,RuleNFTAdapter.canTransfer +DA:93,28 +DA:99,38 +FN:99,RuleNFTAdapter.transferred.3 +FNDA:38,RuleNFTAdapter.transferred.3 +DA:111,38 +BRDA:111,2,0,24 +BRDA:111,2,1,14 +DA:112,24 +DA:114,14 +DA:121,32 +FN:121,RuleNFTAdapter.detectTransferRestriction +FNDA:32,RuleNFTAdapter.detectTransferRestriction +DA:134,32 +DA:140,42 +FN:140,RuleNFTAdapter.detectTransferRestrictionFrom +FNDA:42,RuleNFTAdapter.detectTransferRestrictionFrom +DA:154,80 +DA:162,29 +FN:162,RuleNFTAdapter.canTransfer FNDA:29,RuleNFTAdapter.canTransfer -DA:158,29 -DA:164,25 -FN:164,RuleNFTAdapter.canTransferFrom -FNDA:25,RuleNFTAdapter.canTransferFrom -DA:178,25 -DA:192,0 -FN:192,RuleNFTAdapter._transferred +DA:175,29 +DA:181,38 +FN:181,RuleNFTAdapter.canTransferFrom +FNDA:38,RuleNFTAdapter.canTransferFrom +DA:195,38 +DA:214,215 +FN:214,RuleNFTAdapter._isDelegated +FNDA:215,RuleNFTAdapter._isDelegated +DA:215,215 +DA:224,0 +FN:224,RuleNFTAdapter._transferred FNDA:0,RuleNFTAdapter._transferred -DA:201,0 -FN:201,RuleNFTAdapter._transferredFrom +DA:233,0 +FN:233,RuleNFTAdapter._transferredFrom FNDA:0,RuleNFTAdapter._transferredFrom -FNF:10 -FNH:8 -LF:22 -LH:20 -BRF:4 -BRH:4 +FNF:11 +FNH:9 +LF:26 +LH:24 +BRF:6 +BRH:6 end_of_record TN: SF:src/rules/validation/abstract/core/RuleTransferValidation.sol -DA:36,1526 +DA:36,1615 FN:36,RuleTransferValidation.detectTransferRestriction -FNDA:1526,RuleTransferValidation.detectTransferRestriction -DA:43,1526 -DA:49,66 +FNDA:1615,RuleTransferValidation.detectTransferRestriction +DA:43,1615 +DA:49,95 FN:49,RuleTransferValidation.detectTransferRestrictionFrom -FNDA:66,RuleTransferValidation.detectTransferRestrictionFrom -DA:56,66 -DA:67,43 +FNDA:95,RuleTransferValidation.detectTransferRestrictionFrom +DA:56,95 +DA:67,64 FN:67,RuleTransferValidation.canTransfer -FNDA:43,RuleTransferValidation.canTransfer -DA:73,43 -DA:79,33 -FN:79,RuleTransferValidation.canTransferFrom -FNDA:33,RuleTransferValidation.canTransferFrom -DA:86,33 -DA:95,280 -FN:95,RuleTransferValidation.supportsInterface -FNDA:280,RuleTransferValidation.supportsInterface -DA:96,280 -DA:97,277 -DA:98,274 -DA:99,157 -DA:113,0 -FN:113,RuleTransferValidation._detectTransferRestriction +FNDA:64,RuleTransferValidation.canTransfer +DA:74,64 +DA:80,36 +FN:80,RuleTransferValidation.canTransferFrom +FNDA:36,RuleTransferValidation.canTransferFrom +DA:87,36 +DA:96,655 +FN:96,RuleTransferValidation.supportsInterface +FNDA:655,RuleTransferValidation.supportsInterface +DA:97,655 +DA:98,652 +DA:99,649 +DA:100,452 +DA:114,0 +FN:114,RuleTransferValidation._detectTransferRestriction FNDA:0,RuleTransferValidation._detectTransferRestriction -DA:127,0 -FN:127,RuleTransferValidation._detectTransferRestrictionFrom +DA:128,0 +FN:128,RuleTransferValidation._detectTransferRestrictionFrom FNDA:0,RuleTransferValidation._detectTransferRestrictionFrom FNF:7 FNH:5 @@ -2950,121 +2449,227 @@ DA:46,32 FN:46,RuleWhitelistShared.onlyMintBurnManager FNDA:32,RuleWhitelistShared.onlyMintBurnManager DA:47,32 -DA:62,10 -FN:62,RuleWhitelistShared.canReturnTransferRestrictionCode +DA:51,8 +FN:51,RuleWhitelistShared.onlyCheckSpenderManager +FNDA:8,RuleWhitelistShared.onlyCheckSpenderManager +DA:52,8 +DA:67,10 +FN:67,RuleWhitelistShared.canReturnTransferRestrictionCode FNDA:10,RuleWhitelistShared.canReturnTransferRestrictionCode -DA:63,10 -DA:64,5 -DA:65,2 -DA:66,2 -DA:76,19 -FN:76,RuleWhitelistShared.messageForTransferRestriction +DA:68,10 +DA:69,5 +DA:70,2 +DA:71,2 +DA:81,19 +FN:81,RuleWhitelistShared.messageForTransferRestriction FNDA:19,RuleWhitelistShared.messageForTransferRestriction -DA:82,19 -BRDA:82,0,0,6 -BRDA:82,0,1,2 -DA:83,6 -DA:84,13 -BRDA:84,1,0,4 -BRDA:84,1,1,2 -DA:85,4 -DA:86,9 -BRDA:86,2,0,2 -BRDA:86,2,1,2 -DA:87,2 -DA:88,7 -BRDA:88,3,0,3 -BRDA:88,3,1,2 -DA:89,3 +DA:87,19 +BRDA:87,0,0,6 +BRDA:87,0,1,2 +DA:88,6 +DA:89,13 +BRDA:89,1,0,4 +BRDA:89,1,1,2 DA:90,4 -BRDA:90,4,0,2 -BRDA:90,4,1,2 -DA:91,2 -DA:93,2 -DA:105,32 -FN:105,RuleWhitelistShared.setAllowMint +DA:91,9 +BRDA:91,2,0,2 +BRDA:91,2,1,2 +DA:92,2 +DA:93,7 +BRDA:93,3,0,3 +BRDA:93,3,1,2 +DA:94,3 +DA:95,4 +BRDA:95,4,0,2 +BRDA:95,4,1,2 +DA:96,2 +DA:98,2 +DA:111,8 +FN:111,RuleWhitelistShared.setCheckSpender +FNDA:8,RuleWhitelistShared.setCheckSpender +DA:112,6 +DA:119,32 +FN:119,RuleWhitelistShared.setAllowMint FNDA:32,RuleWhitelistShared.setAllowMint -DA:106,29 -DA:107,29 -DA:114,8 -FN:114,RuleWhitelistShared.setAllowBurn +DA:120,29 +DA:121,29 +DA:128,8 +FN:128,RuleWhitelistShared.setAllowBurn FNDA:8,RuleWhitelistShared.setAllowBurn -DA:115,6 -DA:116,6 -DA:130,35 -FN:130,RuleWhitelistShared.transferred.0 -FNDA:35,RuleWhitelistShared.transferred.0 -DA:131,35 -DA:145,13 -FN:145,RuleWhitelistShared.transferred.1 -FNDA:13,RuleWhitelistShared.transferred.1 -DA:146,13 -DA:158,258 -FN:158,RuleWhitelistShared._setAllowMintBurn -FNDA:258,RuleWhitelistShared._setAllowMintBurn -DA:159,258 -DA:160,258 -DA:161,258 -DA:162,258 -DA:172,198 -FN:172,RuleWhitelistShared._detectMintBurnRestriction -FNDA:198,RuleWhitelistShared._detectMintBurnRestriction -DA:173,198 -BRDA:173,5,0,9 -DA:174,9 -DA:176,189 -BRDA:176,6,0,4 -DA:177,4 -DA:179,185 -DA:185,0 -FN:185,RuleWhitelistShared._authorizeMintBurnManager +DA:129,6 +DA:130,6 +DA:144,40 +FN:144,RuleWhitelistShared.transferred.0 +FNDA:40,RuleWhitelistShared.transferred.0 +DA:145,40 +DA:159,18 +FN:159,RuleWhitelistShared.transferred.1 +FNDA:18,RuleWhitelistShared.transferred.1 +DA:160,18 +DA:175,296 +FN:175,RuleWhitelistShared._setCheckSpender +FNDA:296,RuleWhitelistShared._setCheckSpender +DA:176,296 +DA:177,296 +DA:185,290 +FN:185,RuleWhitelistShared._setAllowMintBurn +FNDA:290,RuleWhitelistShared._setAllowMintBurn +DA:186,290 +DA:187,290 +DA:188,290 +DA:189,290 +DA:199,237 +FN:199,RuleWhitelistShared._detectMintBurnRestriction +FNDA:237,RuleWhitelistShared._detectMintBurnRestriction +DA:200,237 +BRDA:200,5,0,11 +DA:201,11 +DA:203,226 +BRDA:203,6,0,4 +DA:204,4 +DA:206,222 +DA:212,0 +FN:212,RuleWhitelistShared._authorizeMintBurnManager FNDA:0,RuleWhitelistShared._authorizeMintBurnManager -DA:190,56 -FN:190,RuleWhitelistShared._transferred -FNDA:56,RuleWhitelistShared._transferred -DA:191,56 -DA:192,56 -BRDA:192,7,0,24 -BRDA:192,7,1,32 -DA:201,32 -FN:201,RuleWhitelistShared._transferredFrom -FNDA:32,RuleWhitelistShared._transferredFrom -DA:202,32 -DA:203,32 -BRDA:203,8,0,12 -BRDA:203,8,1,20 -FNF:12 -FNH:11 -LF:47 -LH:46 +DA:219,0 +FN:219,RuleWhitelistShared._authorizeCheckSpenderManager +FNDA:0,RuleWhitelistShared._authorizeCheckSpenderManager +DA:224,73 +FN:224,RuleWhitelistShared._transferred +FNDA:73,RuleWhitelistShared._transferred +DA:225,73 +DA:226,73 +BRDA:226,7,0,33 +BRDA:226,7,1,40 +DA:235,37 +FN:235,RuleWhitelistShared._transferredFrom +FNDA:37,RuleWhitelistShared._transferredFrom +DA:236,37 +DA:237,37 +BRDA:237,8,0,13 +BRDA:237,8,1,24 +FNF:16 +FNH:14 +LF:55 +LH:53 BRF:16 BRH:16 end_of_record TN: +SF:src/rules/validation/abstract/core/TokenSupplyReader.sol +DA:31,0 +FN:31,TokenSupplyReader._supplyToken +FNDA:0,TokenSupplyReader._supplyToken +DA:43,1323 +FN:43,TokenSupplyReader._currentSupply +FNDA:1323,TokenSupplyReader._currentSupply +DA:44,1323 +BRDA:44,0,0,1323 +DA:45,1315 +DA:46,8 +BRDA:46,0,1,8 +DA:47,8 +DA:65,1216 +FN:65,TokenSupplyReader._probeTotalSupplyCallable +FNDA:1216,TokenSupplyReader._probeTotalSupplyCallable +DA:66,1216 +BRDA:66,1,0,1216 +DA:67,1213 +DA:68,3 +BRDA:68,1,1,3 +DA:69,3 +FNF:3 +FNH:2 +LF:11 +LH:10 +BRF:4 +BRH:4 +end_of_record +TN: +SF:src/rules/validation/abstract/core/TotalSupplyCapManager.sol +DA:38,263 +FN:38,TotalSupplyCapManager.onlyMaxTotalSupplyManager +FNDA:263,TotalSupplyCapManager.onlyMaxTotalSupplyManager +DA:39,263 +DA:51,264 +FN:51,TotalSupplyCapManager.setMaxTotalSupply +FNDA:264,TotalSupplyCapManager.setMaxTotalSupply +DA:52,261 +DA:59,8 +FN:59,TotalSupplyCapManager.setTokenContract +FNDA:8,TotalSupplyCapManager.setTokenContract +DA:60,6 +DA:73,846 +FN:73,TotalSupplyCapManager._setMaxTotalSupply +FNDA:846,TotalSupplyCapManager._setMaxTotalSupply +DA:74,846 +DA:75,846 +DA:83,594 +FN:83,TotalSupplyCapManager._setTokenContract +FNDA:594,TotalSupplyCapManager._setTokenContract +DA:84,594 +DA:85,588 +DA:86,588 +DA:97,594 +FN:97,TotalSupplyCapManager._validateTokenContract +FNDA:594,TotalSupplyCapManager._validateTokenContract +DA:98,594 +BRDA:98,0,0,2 +BRDA:98,0,1,592 +DA:99,592 +BRDA:99,1,0,2 +BRDA:99,1,1,590 +DA:100,590 +BRDA:100,2,0,2 +BRDA:100,2,1,588 +DA:107,0 +FN:107,TotalSupplyCapManager._authorizeMaxTotalSupplyManager +FNDA:0,TotalSupplyCapManager._authorizeMaxTotalSupplyManager +DA:112,854 +FN:112,TotalSupplyCapManager._supplyToken +FNDA:854,TotalSupplyCapManager._supplyToken +DA:113,854 +DA:126,856 +FN:126,TotalSupplyCapManager._capExceeded +FNDA:856,TotalSupplyCapManager._capExceeded +DA:127,856 +DA:128,856 +DA:129,856 +BRDA:129,3,0,4 +DA:130,4 +DA:132,852 +FNF:9 +FNH:8 +LF:26 +LH:25 +BRF:7 +BRH:7 +end_of_record +TN: SF:src/rules/validation/deployment/RuleBlacklist.sol -DA:37,92 +DA:37,157 FN:37,RuleBlacklist.supportsInterface -FNDA:92,RuleBlacklist.supportsInterface -DA:44,92 -DA:45,62 -DA:55,30 +FNDA:157,RuleBlacklist.supportsInterface +DA:44,157 +DA:45,106 +DA:55,39 FN:55,RuleBlacklist._authorizeAddressListAdd -FNDA:30,RuleBlacklist._authorizeAddressListAdd -DA:60,1 +FNDA:39,RuleBlacklist._authorizeAddressListAdd +DA:60,2 FN:60,RuleBlacklist._authorizeAddressListRemove -FNDA:1,RuleBlacklist._authorizeAddressListRemove -DA:70,88 +FNDA:2,RuleBlacklist._authorizeAddressListRemove +DA:70,128 FN:70,RuleBlacklist._msgSender -FNDA:88,RuleBlacklist._msgSender -DA:71,88 +FNDA:128,RuleBlacklist._msgSender +DA:71,128 DA:78,1 FN:78,RuleBlacklist._msgData FNDA:1,RuleBlacklist._msgData DA:79,1 -DA:86,89 +DA:86,129 FN:86,RuleBlacklist._contextSuffixLength -FNDA:89,RuleBlacklist._contextSuffixLength -DA:87,89 +FNDA:129,RuleBlacklist._contextSuffixLength +DA:87,129 FNF:6 FNH:6 LF:11 @@ -3111,9 +2716,9 @@ FN:47,RuleChainlinkPoR.supportsInterface FNDA:21,RuleChainlinkPoR.supportsInterface DA:54,21 DA:55,14 -DA:65,19 +DA:65,20 FN:65,RuleChainlinkPoR._authorizeChainlinkPoRManager -FNDA:19,RuleChainlinkPoR._authorizeChainlinkPoRManager +FNDA:20,RuleChainlinkPoR._authorizeChainlinkPoRManager FNF:2 FNH:2 LF:4 @@ -3122,15 +2727,41 @@ BRF:0 BRH:0 end_of_record TN: +SF:src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol +DA:55,9 +FN:55,RuleChainlinkPoRERC3643._detectTransferRestrictionOnNotify +FNDA:9,RuleChainlinkPoRERC3643._detectTransferRestrictionOnNotify +DA:66,9 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol +DA:55,2 +FN:55,RuleChainlinkPoRERC3643Ownable2Step._detectTransferRestrictionOnNotify +FNDA:2,RuleChainlinkPoRERC3643Ownable2Step._detectTransferRestrictionOnNotify +DA:66,2 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: SF:src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol DA:44,5 FN:44,RuleChainlinkPoROwnable2Step.supportsInterface FNDA:5,RuleChainlinkPoROwnable2Step.supportsInterface DA:51,5 DA:52,4 -DA:62,6 +DA:62,8 FN:62,RuleChainlinkPoROwnable2Step._authorizeChainlinkPoRManager -FNDA:6,RuleChainlinkPoROwnable2Step._authorizeChainlinkPoRManager +FNDA:8,RuleChainlinkPoROwnable2Step._authorizeChainlinkPoRManager FNF:2 FNH:2 LF:4 @@ -3140,37 +2771,37 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleERC2980.sol -DA:56,1 -FN:56,RuleERC2980.supportsInterface +DA:46,1 +FN:46,RuleERC2980.supportsInterface FNDA:1,RuleERC2980.supportsInterface -DA:63,1 -DA:73,5 -FN:73,RuleERC2980._authorizeMintBurnManager +DA:53,1 +DA:63,5 +FN:63,RuleERC2980._authorizeMintBurnManager FNDA:5,RuleERC2980._authorizeMintBurnManager -DA:78,48 -FN:78,RuleERC2980._authorizeWhitelistAdd -FNDA:48,RuleERC2980._authorizeWhitelistAdd +DA:68,57 +FN:68,RuleERC2980._authorizeWhitelistAdd +FNDA:57,RuleERC2980._authorizeWhitelistAdd +DA:73,9 +FN:73,RuleERC2980._authorizeWhitelistRemove +FNDA:9,RuleERC2980._authorizeWhitelistRemove +DA:78,28 +FN:78,RuleERC2980._authorizeFrozenlistAdd +FNDA:28,RuleERC2980._authorizeFrozenlistAdd DA:83,8 -FN:83,RuleERC2980._authorizeWhitelistRemove -FNDA:8,RuleERC2980._authorizeWhitelistRemove -DA:88,24 -FN:88,RuleERC2980._authorizeFrozenlistAdd -FNDA:24,RuleERC2980._authorizeFrozenlistAdd -DA:93,7 -FN:93,RuleERC2980._authorizeFrozenlistRemove -FNDA:7,RuleERC2980._authorizeFrozenlistRemove -DA:103,259 -FN:103,RuleERC2980._msgSender -FNDA:259,RuleERC2980._msgSender -DA:104,259 -DA:111,1 -FN:111,RuleERC2980._msgData +FN:83,RuleERC2980._authorizeFrozenlistRemove +FNDA:8,RuleERC2980._authorizeFrozenlistRemove +DA:93,286 +FN:93,RuleERC2980._msgSender +FNDA:286,RuleERC2980._msgSender +DA:94,286 +DA:101,1 +FN:101,RuleERC2980._msgData FNDA:1,RuleERC2980._msgData -DA:112,1 -DA:119,260 -FN:119,RuleERC2980._contextSuffixLength -FNDA:260,RuleERC2980._contextSuffixLength -DA:120,260 +DA:102,1 +DA:109,287 +FN:109,RuleERC2980._contextSuffixLength +FNDA:287,RuleERC2980._contextSuffixLength +DA:110,287 FNF:9 FNH:9 LF:13 @@ -3220,14 +2851,14 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleIdentityRegistry.sol -DA:45,27 +DA:45,66 FN:45,RuleIdentityRegistry.supportsInterface -FNDA:27,RuleIdentityRegistry.supportsInterface -DA:52,27 -DA:53,18 -DA:63,12 +FNDA:66,RuleIdentityRegistry.supportsInterface +DA:52,66 +DA:53,44 +DA:63,14 FN:63,RuleIdentityRegistry._authorizeIdentityRegistryManager -FNDA:12,RuleIdentityRegistry._authorizeIdentityRegistryManager +FNDA:14,RuleIdentityRegistry._authorizeIdentityRegistryManager FNF:2 FNH:2 LF:4 @@ -3253,15 +2884,49 @@ BRF:0 BRH:0 end_of_record TN: +SF:src/rules/validation/deployment/RuleMaxBalance.sol +DA:40,24 +FN:40,RuleMaxBalance.supportsInterface +FNDA:24,RuleMaxBalance.supportsInterface +DA:47,24 +DA:48,16 +DA:58,23 +FN:58,RuleMaxBalance._authorizeMaxBalanceManager +FNDA:23,RuleMaxBalance._authorizeMaxBalanceManager +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol +DA:41,3 +FN:41,RuleMaxBalanceOwnable2Step.supportsInterface +FNDA:3,RuleMaxBalanceOwnable2Step.supportsInterface +DA:48,3 +DA:49,2 +DA:59,9 +FN:59,RuleMaxBalanceOwnable2Step._authorizeMaxBalanceManager +FNDA:9,RuleMaxBalanceOwnable2Step._authorizeMaxBalanceManager +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record +TN: SF:src/rules/validation/deployment/RuleMaxTotalSupply.sol -DA:37,19 +DA:37,79 FN:37,RuleMaxTotalSupply.supportsInterface -FNDA:19,RuleMaxTotalSupply.supportsInterface -DA:44,19 -DA:45,13 -DA:55,264 +FNDA:79,RuleMaxTotalSupply.supportsInterface +DA:44,79 +DA:45,53 +DA:55,266 FN:55,RuleMaxTotalSupply._authorizeMaxTotalSupplyManager -FNDA:264,RuleMaxTotalSupply._authorizeMaxTotalSupplyManager +FNDA:266,RuleMaxTotalSupply._authorizeMaxTotalSupplyManager FNF:2 FNH:2 LF:4 @@ -3270,15 +2935,41 @@ BRF:0 BRH:0 end_of_record TN: +SF:src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol +DA:48,10 +FN:48,RuleMaxTotalSupplyERC3643._detectTransferRestrictionOnNotify +FNDA:10,RuleMaxTotalSupplyERC3643._detectTransferRestrictionOnNotify +DA:59,10 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol +DA:48,2 +FN:48,RuleMaxTotalSupplyERC3643Ownable2Step._detectTransferRestrictionOnNotify +FNDA:2,RuleMaxTotalSupplyERC3643Ownable2Step._detectTransferRestrictionOnNotify +DA:59,2 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record +TN: SF:src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol DA:39,5 FN:39,RuleMaxTotalSupplyOwnable2Step.supportsInterface FNDA:5,RuleMaxTotalSupplyOwnable2Step.supportsInterface DA:46,5 DA:47,2 -DA:57,4 +DA:57,6 FN:57,RuleMaxTotalSupplyOwnable2Step._authorizeMaxTotalSupplyManager -FNDA:4,RuleMaxTotalSupplyOwnable2Step._authorizeMaxTotalSupplyManager +FNDA:6,RuleMaxTotalSupplyOwnable2Step._authorizeMaxTotalSupplyManager FNF:2 FNH:2 LF:4 @@ -3288,29 +2979,29 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleReceiverWhitelist.sol -DA:38,6 +DA:38,7 FN:38,RuleReceiverWhitelist.supportsInterface -FNDA:6,RuleReceiverWhitelist.supportsInterface -DA:45,6 -DA:46,5 +FNDA:7,RuleReceiverWhitelist.supportsInterface +DA:45,7 +DA:46,6 DA:56,22 FN:56,RuleReceiverWhitelist._authorizeAddressListAdd FNDA:22,RuleReceiverWhitelist._authorizeAddressListAdd DA:61,3 FN:61,RuleReceiverWhitelist._authorizeAddressListRemove FNDA:3,RuleReceiverWhitelist._authorizeAddressListRemove -DA:71,62 +DA:71,64 FN:71,RuleReceiverWhitelist._msgSender -FNDA:62,RuleReceiverWhitelist._msgSender -DA:72,62 +FNDA:64,RuleReceiverWhitelist._msgSender +DA:72,64 DA:79,1 FN:79,RuleReceiverWhitelist._msgData FNDA:1,RuleReceiverWhitelist._msgData DA:80,1 -DA:87,64 +DA:87,66 FN:87,RuleReceiverWhitelist._contextSuffixLength -FNDA:64,RuleReceiverWhitelist._contextSuffixLength -DA:88,64 +FNDA:66,RuleReceiverWhitelist._contextSuffixLength +DA:88,66 FNF:6 FNH:6 LF:11 @@ -3352,26 +3043,26 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleSanctionsList.sol -DA:40,58 +DA:40,115 FN:40,RuleSanctionsList.supportsInterface -FNDA:58,RuleSanctionsList.supportsInterface -DA:47,58 -DA:48,39 +FNDA:115,RuleSanctionsList.supportsInterface +DA:47,115 +DA:48,77 DA:58,18 FN:58,RuleSanctionsList._authorizeSanctionListManager FNDA:18,RuleSanctionsList._authorizeSanctionListManager -DA:68,60 +DA:68,93 FN:68,RuleSanctionsList._msgSender -FNDA:60,RuleSanctionsList._msgSender -DA:69,60 +FNDA:93,RuleSanctionsList._msgSender +DA:69,93 DA:76,1 FN:76,RuleSanctionsList._msgData FNDA:1,RuleSanctionsList._msgData DA:77,1 -DA:84,61 +DA:84,94 FN:84,RuleSanctionsList._contextSuffixLength -FNDA:61,RuleSanctionsList._contextSuffixLength -DA:85,61 +FNDA:94,RuleSanctionsList._contextSuffixLength +DA:85,94 FNF:5 FNH:5 LF:10 @@ -3410,29 +3101,29 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleSpenderWhitelist.sol -DA:38,6 +DA:38,14 FN:38,RuleSpenderWhitelist.supportsInterface -FNDA:6,RuleSpenderWhitelist.supportsInterface -DA:45,6 -DA:46,5 -DA:56,7 +FNDA:14,RuleSpenderWhitelist.supportsInterface +DA:45,14 +DA:46,11 +DA:56,9 FN:56,RuleSpenderWhitelist._authorizeAddressListAdd -FNDA:7,RuleSpenderWhitelist._authorizeAddressListAdd +FNDA:9,RuleSpenderWhitelist._authorizeAddressListAdd DA:61,2 FN:61,RuleSpenderWhitelist._authorizeAddressListRemove FNDA:2,RuleSpenderWhitelist._authorizeAddressListRemove -DA:71,38 +DA:71,46 FN:71,RuleSpenderWhitelist._msgSender -FNDA:38,RuleSpenderWhitelist._msgSender -DA:72,38 +FNDA:46,RuleSpenderWhitelist._msgSender +DA:72,46 DA:79,1 FN:79,RuleSpenderWhitelist._msgData FNDA:1,RuleSpenderWhitelist._msgData DA:80,1 -DA:87,40 +DA:87,48 FN:87,RuleSpenderWhitelist._contextSuffixLength -FNDA:40,RuleSpenderWhitelist._contextSuffixLength -DA:88,40 +FNDA:48,RuleSpenderWhitelist._contextSuffixLength +DA:88,48 FNF:6 FNH:6 LF:11 @@ -3474,35 +3165,35 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleWhitelist.sol -DA:47,86 +DA:47,703 FN:47,RuleWhitelist.supportsInterface -FNDA:86,RuleWhitelist.supportsInterface -DA:54,86 -DA:55,58 -DA:65,1 +FNDA:703,RuleWhitelist.supportsInterface +DA:54,703 +DA:55,470 +DA:65,2 FN:65,RuleWhitelist._authorizeCheckSpenderManager -FNDA:1,RuleWhitelist._authorizeCheckSpenderManager +FNDA:2,RuleWhitelist._authorizeCheckSpenderManager DA:70,30 FN:70,RuleWhitelist._authorizeMintBurnManager FNDA:30,RuleWhitelist._authorizeMintBurnManager -DA:75,394 +DA:75,883 FN:75,RuleWhitelist._authorizeAddressListAdd -FNDA:394,RuleWhitelist._authorizeAddressListAdd -DA:80,264 +FNDA:883,RuleWhitelist._authorizeAddressListAdd +DA:80,265 FN:80,RuleWhitelist._authorizeAddressListRemove -FNDA:264,RuleWhitelist._authorizeAddressListRemove -DA:90,888 +FNDA:265,RuleWhitelist._authorizeAddressListRemove +DA:90,1404 FN:90,RuleWhitelist._msgSender -FNDA:888,RuleWhitelist._msgSender -DA:91,888 +FNDA:1404,RuleWhitelist._msgSender +DA:91,1404 DA:98,1 FN:98,RuleWhitelist._msgData FNDA:1,RuleWhitelist._msgData DA:99,1 -DA:106,889 +DA:106,1405 FN:106,RuleWhitelist._contextSuffixLength -FNDA:889,RuleWhitelist._contextSuffixLength -DA:107,889 +FNDA:1405,RuleWhitelist._contextSuffixLength +DA:107,1405 FNF:8 FNH:8 LF:13 @@ -3550,19 +3241,19 @@ BRH:0 end_of_record TN: SF:src/rules/validation/deployment/RuleWhitelistWrapper.sol -DA:47,49 +DA:47,56 FN:47,RuleWhitelistWrapper.hasRole -FNDA:49,RuleWhitelistWrapper.hasRole -DA:48,159 -DA:56,47 +FNDA:56,RuleWhitelistWrapper.hasRole +DA:48,173 +DA:56,50 FN:56,RuleWhitelistWrapper.supportsInterface -FNDA:47,RuleWhitelistWrapper.supportsInterface -DA:63,47 -DA:64,32 -DA:77,49 +FNDA:50,RuleWhitelistWrapper.supportsInterface +DA:63,50 +DA:64,35 +DA:77,56 FN:77,RuleWhitelistWrapper._grantRole -FNDA:49,RuleWhitelistWrapper._grantRole -DA:78,49 +FNDA:56,RuleWhitelistWrapper._grantRole +DA:78,56 DA:87,1 FN:87,RuleWhitelistWrapper._revokeRole FNDA:1,RuleWhitelistWrapper._revokeRole @@ -3573,24 +3264,24 @@ FNDA:2,RuleWhitelistWrapper._authorizeCheckSpenderManager DA:103,4 FN:103,RuleWhitelistWrapper._authorizeMintBurnManager FNDA:4,RuleWhitelistWrapper._authorizeMintBurnManager -DA:109,98 +DA:109,105 FN:109,RuleWhitelistWrapper._onlyRulesManager -FNDA:98,RuleWhitelistWrapper._onlyRulesManager +FNDA:105,RuleWhitelistWrapper._onlyRulesManager DA:114,2 FN:114,RuleWhitelistWrapper._onlyRulesLimitManager FNDA:2,RuleWhitelistWrapper._onlyRulesLimitManager -DA:120,158 +DA:120,172 FN:120,RuleWhitelistWrapper._msgSender -FNDA:158,RuleWhitelistWrapper._msgSender -DA:121,158 +FNDA:172,RuleWhitelistWrapper._msgSender +DA:121,172 DA:128,1 FN:128,RuleWhitelistWrapper._msgData FNDA:1,RuleWhitelistWrapper._msgData DA:129,1 -DA:136,159 +DA:136,173 FN:136,RuleWhitelistWrapper._contextSuffixLength -FNDA:159,RuleWhitelistWrapper._contextSuffixLength -DA:143,159 +FNDA:173,RuleWhitelistWrapper._contextSuffixLength +DA:143,173 FNF:11 FNH:11 LF:19 @@ -3636,360 +3327,3 @@ LH:13 BRF:0 BRH:0 end_of_record -TN: -SF:test/RuleBlacklist/Ownable/RuleBlacklistOwnable2Step.t.sol -DA:10,2 -FN:10,RuleBlacklistOwnable2StepTest._deployOwnable2Step -FNDA:2,RuleBlacklistOwnable2StepTest._deployOwnable2Step -DA:11,2 -DA:12,2 -DA:13,2 -FNF:1 -FNH:1 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleBlacklist/Ownable/RuleBlacklistOwnableAccessControl.t.sol -DA:9,2 -FN:9,RuleBlacklistOwnable2StepAccessControl._deployAddressList -FNDA:2,RuleBlacklistOwnable2StepAccessControl._deployAddressList -DA:10,2 -DA:11,2 -DA:12,2 -FNF:1 -FNH:1 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleChainlinkPoR/Ownable/RuleChainlinkPoROwnable2Step.t.sol -DA:17,2 -FN:17,RuleChainlinkPoROwnable2StepTest._deployOwnable2Step -FNDA:2,RuleChainlinkPoROwnable2StepTest._deployOwnable2Step -DA:18,2 -DA:19,2 -DA:20,2 -DA:21,2 -DA:24,2 -FNF:1 -FNH:1 -LF:6 -LH:6 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol -DA:627,1 -FN:627,DecimalsOnlyMock.decimals -FNDA:1,DecimalsOnlyMock.decimals -DA:628,1 -FNF:1 -FNH:1 -LF:2 -LH:2 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleERC2980/Ownable/RuleERC2980Ownable2Step.t.sol -DA:10,2 -FN:10,RuleERC2980Ownable2StepTest._deployOwnable2Step -FNDA:2,RuleERC2980Ownable2StepTest._deployOwnable2Step -DA:11,2 -DA:12,2 -DA:13,2 -FNF:1 -FNH:1 -LF:4 -LH:4 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleIdentityRegistry/Ownable/RuleIdentityRegistryOwnable2Step.t.sol -DA:11,2 -FN:11,RuleIdentityRegistryOwnable2StepTest._deployOwnable2Step -FNDA:2,RuleIdentityRegistryOwnable2StepTest._deployOwnable2Step -DA:12,2 -DA:13,2 -DA:14,2 -DA:15,2 -DA:16,2 -FNF:1 -FNH:1 -LF:6 -LH:6 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleMaxTotalSupply/Ownable/RuleMaxTotalSupplyOwnable2Step.t.sol -DA:11,2 -FN:11,RuleMaxTotalSupplyOwnable2StepTest._deployOwnable2Step -FNDA:2,RuleMaxTotalSupplyOwnable2StepTest._deployOwnable2Step -DA:12,2 -DA:13,2 -DA:14,2 -DA:15,2 -FNF:1 -FNH:1 -LF:5 -LH:5 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleMaxTotalSupply/RuleMaxTotalSupplyUnit.t.sol -DA:211,2 -FN:211,RevertingTotalSupplyMock.setRevertOnTotalSupply -FNDA:2,RevertingTotalSupplyMock.setRevertOnTotalSupply -DA:212,2 -DA:215,6 -FN:215,RevertingTotalSupplyMock.totalSupply -FNDA:6,RevertingTotalSupplyMock.totalSupply -DA:216,6 -BRDA:216,0,0,4 -BRDA:216,0,1,2 -DA:217,2 -FNF:2 -FNH:2 -LF:5 -LH:5 -BRF:2 -BRH:2 -end_of_record -TN: -SF:test/RuleSanctionList/Ownable/RuleSanctionsListOwnable2Step.t.sol -DA:13,2 -FN:13,RuleSanctionsListOwnable2StepTest._deployOwnable2Step -FNDA:2,RuleSanctionsListOwnable2StepTest._deployOwnable2Step -DA:14,2 -DA:15,2 -DA:16,2 -DA:17,2 -FNF:1 -FNH:1 -LF:5 -LH:5 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/RuleWhitelist/Ownable/RuleWhitelistOwnable2Step.t.sol -DA:10,2 -FN:10,RuleWhitelistOwnable2StepTest._deployOwnable2Step -FNDA:2,RuleWhitelistOwnable2StepTest._deployOwnable2Step -DA:11,2 -DA:12,2 -DA:13,2 -DA:14,2 -FNF:1 -FNH:1 -LF:5 -LH:5 -BRF:0 -BRH:0 -end_of_record -TN: -SF:test/invariant/ConditionalTransferHandler.sol -DA:61,2 -FN:61,ConditionalTransferHandler.constructor -FNDA:2,ConditionalTransferHandler.constructor -DA:62,2 -DA:72,4152 -FN:72,ConditionalTransferHandler.approve -FNDA:4152,ConditionalTransferHandler.approve -DA:73,4152 -DA:74,4152 -DA:75,4152 -DA:76,4152 -DA:82,4270 -FN:82,ConditionalTransferHandler.cancel -FNDA:4270,ConditionalTransferHandler.cancel -DA:83,4270 -BRDA:83,0,0,4270 -DA:84,4270 -DA:86,4114 -DA:87,4114 -BRDA:87,1,0,2747 -DA:88,2747 -DA:90,1367 -DA:91,1367 -DA:97,4190 -FN:97,ConditionalTransferHandler.execute -FNDA:4190,ConditionalTransferHandler.execute -DA:98,4190 -BRDA:98,2,0,4190 -DA:99,4190 -DA:101,4048 -DA:102,4048 -BRDA:102,3,0,2577 -DA:103,2577 -DA:105,1471 -DA:106,1471 -DA:114,4028 -FN:114,ConditionalTransferHandler.executeMintOrBurn -FNDA:4028,ConditionalTransferHandler.executeMintOrBurn -DA:115,4028 -DA:116,4028 -DA:117,4028 -BRDA:117,4,0,1998 -BRDA:117,4,1,2030 -DA:118,1998 -DA:120,2030 -DA:122,4028 -DA:132,0 -FN:132,ConditionalTransferHandler.sumApprovalCounts -FNDA:0,ConditionalTransferHandler.sumApprovalCounts -DA:133,0 -DA:134,0 -DA:135,0 -DA:142,0 -FN:142,ConditionalTransferHandler.keyCount -FNDA:0,ConditionalTransferHandler.keyCount -DA:143,0 -DA:150,4152 -FN:150,ConditionalTransferHandler._tuple -FNDA:4152,ConditionalTransferHandler._tuple -DA:156,4152 -DA:157,4152 -DA:158,4152 -DA:161,4152 -FN:161,ConditionalTransferHandler._record -FNDA:4152,ConditionalTransferHandler._record -DA:162,4152 -DA:163,4152 -BRDA:163,5,0,2965 -DA:164,2965 -DA:165,2965 -FNF:9 -FNH:7 -LF:45 -LH:39 -BRF:7 -BRH:7 -end_of_record -TN: -SF:test/invariant/MintAllowanceHandler.sol -DA:55,2 -FN:55,MintAllowanceHandler.constructor -FNDA:2,MintAllowanceHandler.constructor -DA:56,2 -DA:66,3454 -FN:66,MintAllowanceHandler.setAllowance -FNDA:3454,MintAllowanceHandler.setAllowance -DA:67,3454 -DA:68,3454 -DA:69,3454 -DA:70,3454 -DA:71,3454 -DA:77,3236 -FN:77,MintAllowanceHandler.increase -FNDA:3236,MintAllowanceHandler.increase -DA:78,3236 -DA:79,3236 -DA:80,3236 -DA:81,3236 -DA:82,3236 -DA:88,3376 -FN:88,MintAllowanceHandler.decrease -FNDA:3376,MintAllowanceHandler.decrease -DA:89,3376 -DA:90,3376 -DA:91,3376 -BRDA:91,0,0,287 -DA:92,287 -DA:94,3089 -DA:95,3089 -DA:96,3089 -DA:102,3304 -FN:102,MintAllowanceHandler.mint -FNDA:3304,MintAllowanceHandler.mint -DA:103,3304 -DA:104,3304 -DA:105,3304 -BRDA:105,1,0,264 -DA:106,264 -DA:108,3040 -DA:109,3040 -DA:111,3040 -DA:113,3040 -DA:114,3040 -DA:121,3270 -FN:121,MintAllowanceHandler.regularTransfer -FNDA:3270,MintAllowanceHandler.regularTransfer -DA:122,3270 -DA:123,3270 -DA:124,3270 -DA:125,3270 -DA:135,0 -FN:135,MintAllowanceHandler.minterAt -FNDA:0,MintAllowanceHandler.minterAt -DA:136,0 -DA:137,0 -DA:143,0 -FN:143,MintAllowanceHandler.minterCount -FNDA:0,MintAllowanceHandler.minterCount -DA:144,0 -DA:151,16640 -FN:151,MintAllowanceHandler._minter -FNDA:16640,MintAllowanceHandler._minter -DA:152,16640 -FNF:9 -FNH:7 -LF:44 -LH:39 -BRF:2 -BRH:2 -end_of_record -TN: -SF:test/utils/AccessControlEnumerableTestBase.sol -DA:22,0 -FN:22,AccessControlEnumerableTestBase._deployAccessControl -FNDA:0,AccessControlEnumerableTestBase._deployAccessControl -DA:24,7 -FN:24,AccessControlEnumerableTestBase.setUp -FNDA:7,AccessControlEnumerableTestBase.setUp -DA:25,7 -DA:28,14 -FN:28,AccessControlEnumerableTestBase._assertRoleMembers -FNDA:14,AccessControlEnumerableTestBase._assertRoleMembers -DA:29,14 -DA:30,14 -DA:31,14 -BRDA:31,0,0,7 -DA:32,7 -DA:34,7 -DA:35,7 -DA:36,7 -FNF:3 -FNH:2 -LF:11 -LH:10 -BRF:1 -BRH:1 -end_of_record -TN: -SF:test/utils/CMTATDeployment.sol -DA:15,78 -FN:15,CMTATDeployment.constructor -FNDA:78,CMTATDeployment.constructor -DA:17,78 -DA:18,78 -DA:19,78 -DA:20,78 -DA:27,78 -DA:28,78 -FNF:1 -FNH:1 -LF:7 -LH:7 -BRF:0 -BRH:0 -end_of_record diff --git a/doc/script/convert_links_for_pdf.sh b/doc/script/convert_links_for_pdf.sh index 5b2a4f6c..4f0734a5 100755 --- a/doc/script/convert_links_for_pdf.sh +++ b/doc/script/convert_links_for_pdf.sh @@ -15,6 +15,23 @@ if [ -z "$1" ]; then fi GITHUB_LINK="${1%/}" # Remove trailing slash if present + +# Base URL for the *parent* of the input file's directory, used by Step 0. +# ".../blob//doc" -> ".../blob/". The input file lives in doc/, so its +# links to repository-root siblings (test/, src/) are written "../path" and can +# only be rewritten against this. Empty when the base URL has no path segment +# after the ref: the input file is then the root README, and "../" from there +# points outside the repository. +GITHUB_LINK_PARENT="" +if [[ "$GITHUB_LINK" =~ ^(.*/blob/[^/]+)/(.+)$ ]]; then + REF_BASE="${BASH_REMATCH[1]}" + DIR_PATH="${BASH_REMATCH[2]}" + if [ "$DIR_PATH" = "${DIR_PATH%/*}" ]; then + GITHUB_LINK_PARENT="$REF_BASE" + else + GITHUB_LINK_PARENT="$REF_BASE/${DIR_PATH%/*}" + fi +fi INPUT_FILE="${2:-../README.md}" # doc/README.md, the full reference (the root README is a short summary) OUTPUT_FILE="${3:-README_UPDATE.md}" @@ -29,6 +46,19 @@ cp "$INPUT_FILE" "$TMP_FILE" # Use a placeholder to avoid sed escaping issues PLACEHOLDER="__GITHUB_LINK__" +PLACEHOLDER_PARENT="__GITHUB_LINK_PARENT__" + +# Step 0: convert parent-relative links [text](../...) before Step 1, which only +# recognizes the "./" form and would leave these relative and dead in the PDF. +if grep -qE '\]\(\.\./[^)]+\)' "$TMP_FILE"; then + if [ -z "$GITHUB_LINK_PARENT" ]; then + echo "Error: '$INPUT_FILE' contains '../' links, but '$GITHUB_LINK' has no parent directory to resolve them against." >&2 + echo "Pass a base URL that includes the input file's own directory, e.g. https://github.com/CMTA/Rules/blob//doc" >&2 + rm -f "$TMP_FILE" + exit 1 + fi + sed -i -E "s|\[([^]]+)\]\(\.\./([^)]+)\)|[\1]($PLACEHOLDER_PARENT/\2)|g" "$TMP_FILE" +fi # Step 1: Convert ALL relative links [text](./...) to placeholder sed -i -E "s|\[([^]]+)\]\(\./([^)]+)\)|[\1]($PLACEHOLDER/\2)|g" "$TMP_FILE" @@ -36,9 +66,11 @@ sed -i -E "s|\[([^]]+)\]\(\./([^)]+)\)|[\1]($PLACEHOLDER/\2)|g" "$TMP_FILE" # Step 2: Restore image links back to relative (images render inline in PDF) for ext in png jpg jpeg gif svg ico webp bmp tiff; do sed -i -E "s|\[([^]]+)\]\($PLACEHOLDER/([^)]+\.$ext)\)|[\1](./\2)|gi" "$TMP_FILE" + sed -i -E "s|\[([^]]+)\]\($PLACEHOLDER_PARENT/([^)]+\.$ext)\)|[\1](../\2)|gi" "$TMP_FILE" done -# Step 3: Replace placeholder with actual GitHub link +# Step 3: Replace placeholders with actual GitHub links (parent first) +sed -i "s|$PLACEHOLDER_PARENT|$GITHUB_LINK_PARENT|g" "$TMP_FILE" sed -i "s|$PLACEHOLDER|$GITHUB_LINK|g" "$TMP_FILE" mv "$TMP_FILE" "$OUTPUT_FILE" diff --git a/doc/security/audits/AUDIT_OVERVIEW.md b/doc/security/audits/AUDIT_OVERVIEW.md index ebc4bd56..68eeaceb 100644 --- a/doc/security/audits/AUDIT_OVERVIEW.md +++ b/doc/security/audits/AUDIT_OVERVIEW.md @@ -3,7 +3,7 @@ > This is a security **overview** (analyses index + triage). It is **not** the vulnerability-reporting policy > (that belongs in a root `SECURITY.md`). -**Current package version:** `v0.5.0` +**Current package version:** `v0.6.0` **Scope:** production contracts under `src/` — mocks/tests (`src/mocks`, `test/`) and dependencies (`lib/`) are excluded from static-analysis runs unless a run is explicitly marked *mocks included*. > ⚠️ This project has **not** undergone a formal third-party security audit. The analyses below are automated @@ -13,6 +13,10 @@ | Date | Type | Tool / Source | Version | Reports | |---|---|---|---|---| +| 2026-08-18 | AI-assisted review | Claude Code (Anthropic) | v0.6.0 | [**CLAUDE_ANALYSIS.md**](./tools/v0.6.0/CLAUDE_ANALYSIS.md) (code quality, `src/`) | +| 2026-08-21 | Static analysis | Slither 0.11.5 | v0.6.0 | [report](./tools/v0.6.0/slither-report.md) · [feedback](./tools/v0.6.0/slither-report-feedback.md) — re-run after the RuleEngine `v3.0.0-rc6` bump, supersedes 2026-08-18 | +| 2026-08-21 | Static analysis | Aderyn 0.6.5 | v0.6.0 | [report](./tools/v0.6.0/aderyn-report.md) · [feedback](./tools/v0.6.0/aderyn-report-feedback.md) — re-run after the RuleEngine `v3.0.0-rc6` bump, supersedes 2026-08-18 | +| 2026-08-17 | AI automated scan | [Nethermind AuditAgent (AI)](https://auditagent.nethermind.io/) | v0.5.0 | [report (PDF)](./tools/v0.5.0/nethermind_audit_agent_report_v0.5.0.pdf) · [feedback](./tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md) | | 2026-08-12 | AI-assisted review | Claude Code (Anthropic) | v0.5.0 | [**CLAUDE_ANALYSIS.md**](./tools/v0.5.0/CLAUDE_ANALYSIS.md) (code quality, `src/`) · [**CLAUDE_ANALYSIS_SCRIPT.md**](./tools/v0.5.0/CLAUDE_ANALYSIS_SCRIPT.md) (deployment scripts) | | 2026-07 | AI-assisted review | Claude (Anthropic) + custom security-audit skills | v0.4.0 | [**CLAUDE_AUDIT.md**](./tools/v0.4.0/claude-audit/CLAUDE_AUDIT.md) | | 2026-08-11 | Static analysis | Slither 0.11.5 | v0.5.0 | [report](./tools/v0.5.0/slither-report.md) · [feedback](./tools/v0.5.0/slither-report-feedback.md) | @@ -22,6 +26,60 @@ | 2026-04-16 | Static analysis | Slither / Aderyn | v0.3.0 | [slither](./tools/v0.3.0/slither-report.md) · [aderyn](./tools/v0.3.0/aderyn-report.md) | | 2026-03-16 | AI-assisted review | Wake Arena (Ackee) | v0.2.0 | [tools/v0.2.0](./tools/v0.2.0/) | +## Static-analysis results (v0.6.0) + +Re-run **2026-08-21** for the `v0.6.0` release, at solc `0.8.36`, with the same tool versions as `v0.5.0` so the +delta is directly comparable. This supersedes the 2026-08-18 run, which was already one commit stale when it +was committed. Scope: production contracts only — mocks excluded, vendored dependencies excluded +via the `lib` filter. + +| Tool | High | Medium | Low | Info | Relevant to fix? | +|---|---|---|---|---|---| +| Slither 0.11.5 | 2 | 11 | 18 | 15 | **No** — both High-impact results are the long-standing false positive on a permissioned path; see [feedback](./tools/v0.6.0/slither-report-feedback.md) | +| Aderyn 0.6.5 | 0 | 0 | 9 categories (346 instances) | 0 | **No** — every Low is by design, environmental or cosmetic; see [feedback](./tools/v0.6.0/aderyn-report-feedback.md) | + +**Nothing to fix in `v0.6.0`.** Both deltas are small and fully attributed: + +- **Slither 44 → 46 (+2).** One `calls-loop` on `RuleWhitelistWrapperBase._checkRule` — the NM-20 polarity guard, + bounded by `maxRules` and reachable only from a `RULES_MANAGEMENT_ROLE` configuration call, never a transfer. + One `dead-code` on `RuleChainlinkPoRBase._detectTransferRestrictionOnNotify`, which is a **false positive worth + reading**: acting on it would delete the seam `RuleChainlinkPoRERC3643` exists to override. It is called twice + in the same file, the contract is at 100% function coverage, and the byte-identical seam in + `RuleMaxTotalSupplyBase` is not flagged — the detector is unreliable for `internal virtual` functions reached + through inheritance. +- **Aderyn 336 → 346 (+10)** on +203 nSLOC, and the +10 is *exactly* the five new production files appearing once + each in `Unspecific Solidity Pragma` and `PUSH0 Opcode`. No new category. + +Two non-results are more informative than the totals. **`Centralization Risk` did not move (80 → 80)** despite +four new deployable contracts: the ERC-3643 variants subclass existing deployables and override one `internal` +hook, adding no privileged external function. **`Empty Block` did not move (70 → 70)** either, so no new +access-control hook was introduced. + +**Re-run 2026-08-18 → 2026-08-21, after the RuleEngine `v3.0.0-rc6` bump: no detector moved in either tool.** +Slither holds 46 results across the same nine detectors, Aderyn holds 346 instances across the same nine +categories, and the only body changes are line numbers plus four renamed snippets. Two commits are covered — the +NatSpec trim (`c1ebe57`, which is what made the 2026-08-18 reports stale) and the rc6 bump (`f920b07`), which +renamed `onlyComplianceManager` to `onlyTokenBindingManager`, renamed `_authorizeComplianceBindingChange` to +`_authorizeTokenBindingChange` and deleted one redundant override. Aderyn's nSLOC moved 4 146 → 4 145. Slither's +**contract count rose 221 → 225 without any change in `src/`**: rc6 split the binding registry out of +`ERC3643ComplianceModule` into five new upstream contracts and removed one, all under `lib/` and all filtered out +of the results — flagged here so a future reader does not mistake it for scope creep. Two stable counts carry +information: `dead-code` staying at 3 confirms the deleted override was reachable and therefore redundant rather +than load-bearing, and `Centralization Risk` staying at 80 confirms the rename re-gated nothing. + +As in `v0.5.0`: a clean static-analysis report means the tools' pattern sets matched nothing. **None of the seven +findings fixed in this release was reachable by either analyser** — they came from the Nethermind AuditAgent scan +and manual review, and are semantic (accounting phase, callback ordering, interface polarity) where these tools +are syntactic. + +Commands used for `v0.6.0` (mocks excluded): + +```bash +slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks" \ + > doc/security/audits/tools/v0.6.0/slither-report.md +aderyn -x mocks --output doc/security/audits/tools/v0.6.0/aderyn-report.md +``` + ## Static-analysis results (v0.5.0) Scope: production contracts only — mocks excluded (`-x mocks` / `mocks` filter) and vendored dependencies @@ -60,6 +118,73 @@ feed-decimals read that prevents a stale-cache over-mint, and the removal of two `IdentityRegistryWhitelist` — were found by **manual review, not by either tool**. A clean static-analysis report means the tools' pattern sets matched nothing; it is not evidence of correctness. +## AI automated scan results — Nethermind AuditAgent (v0.5.0) + +Scan **2026-08-17** (Scan ID `10`, commit `01632da0…951e204c`, 89 contracts / 9 764 LoC) with +[**Nethermind AuditAgent**](https://auditagent.nethermind.io/). + +> ⚠️ **This is an AI-powered automated scan, not a formal human-led audit.** Nethermind's own notice states the +> report "has been generated entirely by AI… does not constitute a full security audit… must be independently +> verified", and that it does not authorise describing the project as "audited by Nethermind". The +> [feedback file](./tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md) is that independent +> verification: every finding was opened against the cited `file:line`. + +| Tool | High | Medium | Low | Info | Relevant to fix? | +|---|---|---|---|---|---| +| [Nethermind AuditAgent (AI)](https://auditagent.nethermind.io/) | 0 | 13 | 11 | 0 | **7 fixed** (NM-3, 6, 10, 11, 17, 18, 20 — `v0.6.0`), 16 accepted as design, 1 declined; **nothing left open** — see [feedback](./tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md) | + +**Nothing exploitable, and no contract change required for the CMTAT path.** There are **no false positives** — +all 24 findings describe real code — but 17 restate positions already reached, documented in-source and recorded +in `CLAUDE_AUDIT.md` (F-4, F-5, F-7 and the accepted-risk rows for a reverting oracle/registry), and the 24 items +collapse to roughly **11 distinct claims** (approval/quota scoping is reported six times, cap-rule token binding +twice, spender-less hooks twice, short ABI return data twice). Every described failure is fail-closed +(over-restriction, a blocked transfer) or inert (a rule that cannot screen an identity it is never given); none +of the 13 Medium ratings survives verification at Medium. + +**NM-11 — fixed in `v0.6.0` for two of the three cap rules.** The three rules assume the token notifies *before* +moving the value; ERC-3643 / T-REX notifies *after*, so the observation already includes the amount and the stock +rule counts it twice, reverting mints that are fully within the cap. `v0.6.0` adds a stateless `CapAccounting` +primitive and a `_detectTransferRestrictionOnNotify` hook on each cap rule — defaulting to today's CMTAT +behaviour — then ships **`RuleChainlinkPoRERC3643`** and **`RuleMaxTotalSupplyERC3643`** (each with an +`Ownable2Step` variant) as one-line overrides of it. Only the write path is re-phased: ERC-3643 calls +`canTransfer` *before* `_mint` and `created` *after*, both in one transaction, so the read views must keep +projecting the pending amount. 49 tests, including two suites driving the **genuine** vendored T-REX token and +four that pin the stock rules failing on it. **`RuleMaxBalance` is deliberately excluded** — a post-update +variant would revert an agent's `forcedTransfer` and, on T-REX ≤ 4.1 where `recoveryAddress` routes through it, +brick wallet recovery; that is a policy decision, not a hook override. Write-ups: +`doc/technical/contracts/RuleChainlinkPoRERC3643.md`, `RuleMaxTotalSupplyERC3643.md`, `RULE_SEMANTICS.md` §5. + +A second ERC-3643 hazard surfaced while testing it and is now pinned: T-REX deploys then initialises, and an +uninitialised `Token` reports `decimals() == 0`, so a PoR rule built before `init` silently caches the wrong +decimals and mis-scales the reserves. Remedy is deployment order, documented on the contract page. + +**Fixed in `v0.6.0` — NM-6.** `RuleNFTAdapter`'s ERC-7943 spender-aware overloads called the delegated hook +unconditionally, while the `ITransferContext` entrypoints normalised `sender == from` to the direct hook. The +three interfaces signal a direct transfer differently — ERC-7943 documents its `spender` as "the address +performing the transfer (**owner**/operator)" and `ctx.sender` is the token's `msg.sender`, so on both an owner +arrives as `spender == from`, whereas CMTAT uses `spender == address(0)` and the 3-arg overload. The adapter now +normalises on a shared `_isDelegated` predicate; the 4-arg CMTAT path is deliberately left alone, so the primary +integration path and every existing restriction code are unchanged. The one behavioural correction is +`RuleSpenderWhitelist`, which had been rejecting owner-initiated ERC-721 `transferFrom` with code 66 despite +documenting that direct transfers are always allowed; the deny-lists blocked such a transfer before and after and +only relabelled the code. Pinned by +[`test/TransferContext/OverloadParity.t.sol`](../../../test/TransferContext/OverloadParity.t.sol) — the suite +already existed for this property but tested only two of the three input shapes, which is why the gap survived; +reverting the fix now fails 6 of its 10 tests across 5 rules. + +**Four findings carry a specified, unimplemented improvement** — NM-5, NM-17, NM-18 and NM-23/24 — +each with the code, its cost and its limit. The two cheapest and clearest wins: assert in +`approveAndTransferIfAllowed` that the approval it created was consumed (NM-17); and ERC-165-check the wrapper's +children in a `_checkRule` override, the pattern `RuleEngineBase` already uses (NM-18). Two carry hard limits +worth knowing before planning work: **NM-5 cannot be fully fixed at the rule level** — the compliance hooks carry +no token identity, so isolating two tokens behind one engine needs an upstream interface change, and only the +"one instance, two engines" half is reachable — and NM-18's read-time containment hits the same uncatchable-decode +problem as NM-23, so only its configuration-time layer is recommended. + +The scan reached a strictly different class of issue than Slither and Aderyn, which found none of these: the +static analysers match syntactic patterns, while every AuditAgent finding is semantic — about which hook is +called, in what order, and with which arguments. + ## Static-analysis results (v0.4.0) Both tools were **re-run on 2026-07-14**, after the security remediation landed. Counts below are from that run. diff --git a/doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md b/doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md new file mode 100644 index 00000000..552b1bca --- /dev/null +++ b/doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0-feedback.md @@ -0,0 +1,1247 @@ +# Nethermind AuditAgent `v0.5.0` — triage + +Tool: **[Nethermind AuditAgent](https://auditagent.nethermind.io/)** — an **AI-powered automated code scanner**. + +> ⚠️ **This is not an audit.** The report carries Nethermind's own *Important Notice*: it "has been generated +> entirely by AI and has not been manually reviewed by Nethermind's security team. It does not constitute a full +> security audit… All findings, observations, and recommendations may contain errors or omissions and must be +> independently verified by a qualified human reviewer before being acted upon." Per Nethermind's terms, this +> scan does **not** authorise anyone to describe the project as "audited by Nethermind". **This document is that +> independent verification**: every one of the 24 findings was opened against the cited `file:line` before a +> disposition was assigned. + +## Scan metadata + +| | | +|---|---| +| Scan ID | `10` | +| Date | 2026-08-17 | +| Organization / Repository | CMTA / `Rules` | +| Branch / Commit | `main` @ `01632da0…951e204c` (`01632da`, the v0.5.0 merge commit — same tree as HEAD at triage time) | +| Contracts scanned | 89 (all of `src/`; mocks, tests and `lib/` out of scope) | +| Lines of code | 9 764 | + +**Tool-reported findings summary — total 24:** + +| High | Medium | Low | Info | Best practices | +|---|---|---|---|---| +| **0** | **13** | **11** | 0 | 0 | + +## Outcome + +**Nothing is exploitable, and nothing required a contract change on the CMTAT deployment path.** Seven findings +were nonetheless fixed in `v0.6.0` — six as hardening and one (NM-11) by shipping ERC-3643 variants of two cap +rules — because each was cheap, verifiable and left the library better than the accepted-as-design disposition +would have. + +| Disposition | Count | IDs | +|---|---|---| +| **Fixed** (in `v0.6.0`) | 7 | **NM-3**, **NM-6**, **NM-10**, **NM-11**, **NM-17**, **NM-18**, **NM-20** | +| Accepted as design (real behaviour, intentional, already documented) | 16 | NM-1, 2, 4, 5, 7, 8, 9, 12, 13, 14, 15, 16, 21, 22, 23, 24 | +| Rejected — false positive | 0 | — | +| Won't do (confirmed, deliberately declined) | 1 | NM-19 — nesting adds no expressive power at multiplicative gas | +| Fix recommended | 0 | — | +| **Total** | **24** | | + +Two observations about the report as a whole: + +1. **No false positives, and no High findings — but heavy duplication.** The 24 items collapse to roughly + **11 distinct claims**. Approval/quota-scoping behind a shared RuleEngine is reported five times + (NM-2, 4, 7, 8, 12, 15); the cap rules' static token binding twice (NM-5, 13); spender-less 3-arg hooks twice + (NM-9, 16); short ABI return data twice (NM-23, 24). Counting each restatement as a separate Medium inflates + the Medium column well past what the underlying set of issues warrants. +2. **The scanner rediscovered, and re-rated as Medium, four positions this project had already reached, + documented in-source, and recorded in a prior audit** — F-4 (multi-token approval scoping), + F-7 (`canTransfer` non-authoritative for `RuleMintAllowance`), F-5 (the wrapper's unchecked children), and + the v0.4.0 accepted-risk row "reverting sanctions oracle / identity registry bricks transfers". It found the + right things; it had no way to see that they were already decided. Re-raising F-5 was useful anyway — it had + been open since v0.4.0 and this scan is what got it closed (NM-18). + +**Eight entries carry an `Improvement` section** — NM-3, NM-5, NM-6, NM-10, NM-11, NM-17, NM-18 and NM-23/24 — +setting out what could be implemented, the code to do it, what it buys, what it costs, and where the limit is. +**Six are implemented in `v0.6.0`**; see the `Resolution` block in each. NM-20, originally dispositioned as +documentation-only, was also fixed once it became clear the marker interface it called for was a single function. + +Two limits are worth reading before planning further work: **NM-5** cannot be fully fixed at the rule level at +all (the compliance hooks carry no token identity, so it needs an upstream interface change), and **NM-18**'s +read-time containment runs into the same uncatchable-decode problem as NM-23, which is why only its +configuration-time layer was implemented. The two improvements that were specified and then **declined** are +**NM-23/24** and **NM-5**; the reasoning is in each entry, and neither is left as an open TODO. + +**The one genuinely new and useful signal** is a theme the scanner keeps circling without naming: +**several rules' guarantees depend on the token's callback shape and ordering, and a real ERC-3643 / T-REX token +supplies neither.** That is developed under NM-11 and NM-9, and it was the only item that warranted new contracts. +It has since been acted on: `v0.6.0` ships ERC-3643 variants of the reserve and supply cap rules, with suites +running against the genuine vendored token. + +--- + +## Per-finding triage + +| ID | Severity (tool → ours) | Finding | Disposition | +|---|---|---|---| +| NM-1 | Medium → **Info** | Mint quotas unenforced via `created` / 3-arg `transferred` | Accepted as design — documented CMTAT ≥ v3.3 requirement | +| NM-2 | Medium → **Low** | Mint quotas shared across tokens behind one RuleEngine | Accepted as design — `bindToken` WARNING | +| NM-3 | Medium → **Info** | Early returns in `_detectTransferRestrictionFrom` skip delegation | ✅ **Fixed** in `v0.6.0` | +| NM-4 | Medium → **Low** | Approvals + quotas not token-scoped across shared engine / rebinding | Accepted as design — duplicate of NM-2 / NM-7 | +| NM-5 | Medium → **Low** | Cap rules read a statically configured token's supply | Accepted as design — documented "one token per instance" | +| NM-6 | Medium → **Info** | `RuleNFTAdapter` context vs ERC-7943 spender handling differ | ✅ **Fixed** in `v0.6.0` | +| NM-7 | Medium → **Low** | Single-token approvals reusable across tokens behind one engine | Accepted as design — `bindRuleEngine` WARNING | +| NM-8 | Medium → **Low** | Mint allowances shared across a multi-token engine | Accepted as design — duplicate of NM-2 | +| NM-9 | Medium → **Info** | 3-arg ERC-3643 hooks carry no spender, so spender rules are inert | Accepted as design — topology requirement; see NM-11 | +| NM-10 | Medium → **Info** | Future-dated PoR `updatedAt` skips the staleness check | ✅ **Fixed** in `v0.6.0` | +| NM-11 | Medium → **Low** | Caps double-count when the token notifies **after** moving value | ✅ **Fixed** in `v0.6.0` — ERC-3643 variants for 2 of 3 rules; `RuleMaxBalance` documented as CMTAT-only | +| NM-12 | Medium → **Low** | Single-token approval consumable by another token | Accepted as design — duplicate of NM-7 | +| NM-13 | Medium → **Low** | Cap rules never bind to the calling token; setters can repoint | Accepted as design — duplicate of NM-5 | +| NM-14 | Low → **Low** | Identity-registry failures revert the read path | Accepted as design — trusted dependency (v0.4.0 audit) | +| NM-15 | Low → **Low** | Conditional approvals not token-scoped | Accepted as design — duplicate of NM-7 | +| NM-16 | Low → **Info** | `RuleSpenderWhitelist` inert on spender-less hooks | Accepted as design — duplicate of NM-9 | +| NM-17 | Low → **Low** | `approveAndTransferIfAllowed` leaves a residual approval if no callback | ✅ **Fixed** in `v0.6.0` — approval-consumed post-condition | +| NM-18 | Low → **Low** | Wrapper bricked by a non-`IAddressList` child | ✅ **Fixed** in `v0.6.0` — ERC-165 guard on a purpose-built sub-interface | +| NM-19 | Low → **Info** | Wrapper does not implement `IAddressList`, so it cannot nest | 🚫 **Won't do** — DoS half fixed by NM-18; nesting declined | +| NM-20 | Low → **Info** | Wrapper reads a `RuleBlacklist` child's membership as eligibility | ✅ **Fixed** in `v0.6.0` — polarity marker interface + ERC-165 | +| NM-21 | Low → **Info** | `RuleMintAllowance` 3-arg pre-flight views fail open | Accepted as design — audit F-7 | +| NM-22 | Low → **Low** | A misbehaving sanctions oracle reverts the read path | Accepted as design — trusted dependency (v0.4.0 audit) | +| NM-23 | Low → **Info** | Short successful return data escapes `try/catch` | Accepted as design — documented in-source; the low-level fix declined, see the entry | +| NM-24 | Low → **Info** | Same, for `balanceOf` / `totalSupply` | Accepted as design — duplicate of NM-23 | + +--- + +### NM-1 — Mint quotas are not enforced via `created` or the 3-arg `transferred` + +**Claim (Medium).** `RuleMintAllowanceBase` deducts only in `_transferredFrom`, reached only from +`transferred(spender, from, to, value)`. `created(address,uint256)` is empty and the 3-arg `transferred` calls an +empty `_transferred`, so a bound integration reporting mints either way completes them without touching +`mintAllowance`. + +**Verdict — accepted as design, correct as written.** The code is exactly as described +(`RuleMintAllowanceBase.sol:63`, `:154-161`, `:258-260`), and confirmed one level up: +`RuleEngineBase.created(to, value)` forwards `_transferred(address(0), to, value)` — the **3-arg** path — so a +token that reports mints through `created` does indeed reach a no-op. + +This is not a gap that can be closed by checking harder: **the 3-arg signature carries no minter identity**, so +there is no address to debit. The rule states the requirement in its own NatSpec ("The rule tracks mints via the +4-arg `transferred(spender, from=0, to, value)` path introduced in CMTAT v3.3. The 3-arg path has no minter +identity and performs no deduction"), in `CLAUDE.md` ("Requires CMTAT ≥ v3.3"), and in +`RULE_SEMANTICS.md` §1. On the supported CMTAT ≥ v3.3 path, `_mintOverride` calls +`_checkTransferred(_msgSender(), address(0), to, value)`, the 4-arg overload runs, and the quota is enforced. + +*Optional hardening, not applied:* `_transferred` could **fail closed** when `from == address(0)` — reverting a +mint reported without a minter identity rather than passing it. It would fire only where the quota is silently +inert today, and would leave plain transfers and burns untouched. Recorded as a deliberate open choice: it turns +a documented "unsupported topology" into a hard revert, which is the safer default for a quota rule but is a +breaking change for any pre-v3.3 integration. + +### NM-2 / NM-4 / NM-8 — Mint quotas shared across tokens behind one RuleEngine + +**Claim (Medium ×3).** `mapping(address minter => uint256 allowance)` has no token dimension. Binding one +RuleEngine authorises a caller, not a token, and an engine may serve several tokens; a quota granted for token A +is spendable minting token B. + +**Verdict — accepted as design, explicitly documented.** True and known. `bindToken` enforces single-target +binding (`RuleMintAllowance_TokenAlreadyBound`), and its NatSpec carries the WARNING that `unbindToken` does not +clear `mintAllowance` and that quotas survive rebinding, with `clearMintAllowances` provided for migration +(v0.4.0 audit F-9). The residual exposure — an operator binding a **multi-tenant** engine — sits inside the +trust model: the compliance manager who chooses the engine is the same role that grants the quotas. This is the +same shape as the documented "one instance protects one token, with no on-chain guard" position taken for +`RuleMaxTotalSupply` / `RuleChainlinkPoR`, and the reasoning is recorded there: adding a binding guard to a +stateless validation rule is a library-wide decision, not a per-rule patch. + +### NM-3 — Early returns in `_detectTransferRestrictionFrom` skip the delegation — ✅ FIXED (`v0.6.0`) + +**Claim (Medium).** `RuleIdentityRegistryBase._detectTransferRestrictionFrom` returns `TRANSFER_OK` directly when +the registry is unset or `to == address(0)`, instead of delegating to `_detectTransferRestriction`. A subclass +adding checks to the latter without also overriding the former would have them silently bypassed — +and `RuleSanctionsListBase` documents having fixed this exact anti-pattern. + +**Verdict — informational; valid observation, no current impact.** The code is as described +(`RuleIdentityRegistryBase.sol:234-241`), and the cross-reference is accurate: `RuleSanctionsListBase.sol:187-190` +carries precisely that warning. But the two early returns here are **duplicates of the delegate's own first two +guards** (`:197-204`): delegating would return `TRANSFER_OK` for the same inputs, so behaviour is identical today. +No subclass of `RuleIdentityRegistryBase` overrides `_detectTransferRestriction` — the only descendants are the +two deployment variants. + +**Improvement — implemented in `v0.6.0`; behaviour-preserving, ~4 lines.** Delegate instead of returning a +literal, exactly as `RuleSanctionsListBase` was changed to do. In +`RuleIdentityRegistryBase._detectTransferRestrictionFrom` (`:234-241`), replace the two early returns: + +```solidity +// before +IIdentityRegistryVerified registry = identityRegistry; +if (address(registry) == address(0)) { + return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); +} +if (to == address(0)) { + return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); +} + +// after — the guards still scope ONLY the spender check; the delegation is unconditional +IIdentityRegistryVerified registry = identityRegistry; +if (address(registry) == address(0) || to == address(0)) { + return _detectTransferRestriction(from, to, value); +} +``` + +Why this is safe: + +- **Identical outputs today.** `_detectTransferRestriction` opens with the same two guards (`:197-204`) and + returns `TRANSFER_OK` for both, so no input changes answer. The existing test suite should pass unmodified — + if any test moves, the change was not behaviour-preserving and must be re-examined rather than re-baselined. +- **Burn stays exempt from the spender check.** The delegate never screens a spender, so routing burn through it + preserves the property the current comment at `:247-249` protects. That comment ("Burn is exempt too, but by + the early return above -- do NOT re-test `to` here") must be rewritten to say the guard now delegates, or it + becomes a stale claim about code that no longer exists. +- **`view` and gas are unchanged** — one extra internal call, no storage access added. + +What it buys: a subclass that overrides only `_detectTransferRestriction` (the natural hook to extend) gets its +check honoured on the `transferFrom`, mint and burn paths instead of silently dropped. That is the trap the +sibling rule already closed, so closing it here also removes an inconsistency between two rules a reader will +compare. + +**Also reviewed at the same time:** every other rule that overrides both hooks. `RuleMaxBalanceBase` (`:149-157`), +`RuleMaxTotalSupplyBase` and `RuleChainlinkPoRBase` already delegate unconditionally and needed no change; +`RuleSpenderWhitelistBase._detectTransferRestrictionFrom` (`:102-115`) deliberately does **not** delegate, because +its `_detectTransferRestriction` is a hardcoded `TRANSFER_OK` — left as is. + +**Resolution — `v0.6.0`.** + +*Changed:* `src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol` — the two early returns become one +guard that delegates, and the `:247-249` comment (which asserted burn was handled "by the early return above") +was rewritten so it describes the code that now exists rather than the code that was removed. + +*Regression tests added:* + +- `src/mocks/harness/IdentityRegistryDelegationHarness.sol` — `IdentityRegistryExtraCheckHarness`, a subclass + that overrides **only** `_detectTransferRestriction` to add a registry-independent check. This is the shape + that exposes the defect, and it mirrors `SanctionsListDelegationHarness` one for one. +- `test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol` — 8 tests: the subclass check must reach + `transferFrom` with no registry configured and on `burnFrom`; the two entrypoints must agree; burn must stay + exempt from the opt-in spender check; the spender check must still short-circuit ahead of the delegated hook; + and base ERC-3643 screening (receiver-only, unverified sender and minter allowed) must be unchanged. + +*Verified, not assumed.* Reverting the source change and re-running the new suite fails 3 of the 8 tests with +exactly the predicted symptoms — `transferFrom must reach the same hook as transfer: 0 != 202`, +`burnFrom must reach the same hook as burn: 0 != 202`, and the two-entrypoint disagreement — and they pass once +the change is restored. The pre-existing 21 `RuleIdentityRegistry` tests pass unmodified, which is the evidence +that the change is behaviour-preserving: had any answer moved, one of them would have. + +*Suites:* 828 tests pass on the default profile and 31 on `FOUNDRY_PROFILE=erc3643`. Coverage on the changed +contract: **100% statements, 100% branches**, 98.41% lines — the single uncovered line is the abstract +`_authorizeIdentityRegistryManager` declaration, which no test can execute because only the override runs. + +### NM-5 / NM-13 — Cap rules evaluate a statically configured token + +**Claim (Medium ×2).** `RuleMaxTotalSupplyBase`, `RuleChainlinkPoRBase` and `RuleMaxBalanceBase` read +`totalSupply()` / `balanceOf()` from a configured address and never verify that `msg.sender` is that token. One +instance behind a shared engine caps the wrong asset; and `setTokenContract` / `setTokenMetadata` / +`setBalanceToken` can repoint the observation target at any callable contract. + +**Verdict — accepted as design, documented verbatim.** Both halves are true and both are already on the record. +`CLAUDE.md` states it as a standing gotcha: *"they read `totalSupply()` from the configured `tokenContract`, never +from the token that triggered the check, and behind a RuleEngine they cannot learn that identity. One instance +added to two RuleEngines evaluates both tokens against the first one's supply and feed… Chainlink's +`SecureMintPolicy` blocks this with `onInstall`/`PolicyAlreadyBound`; adding an equivalent here would mean making +a stateless validation rule bindable, which is a library-wide decision. Documented, not fixed."* The repointing +half was catalogued and dismissed in the v0.4.0 audit ("`RuleMaxTotalSupply.setTokenContract` can repoint the +supply oracle — trusted role"), and every repoint emits `TokenContractUpdated` / `TokenMetadataUpdated` / +`MaxBalanceTokenUpdated` for off-chain monitoring. + +**Improvement — partially implementable; the complete fix is not available at the rule level.** + +*What cannot be done here.* The rule is never told which token triggered a check. `IRule`'s hooks are +`transferred(from, to, value)` and `transferred(spender, from, to, value)` — **no token parameter** — and behind a +RuleEngine `msg.sender` is the engine, so the identity is not recoverable from the call either. `IRule` also has +no `onInstall` hook, so the rule is not even notified when it is added to an engine: `RulesManagementModule._addRule` +only validates and stores the address. A rule that serves two tokens through one engine therefore *cannot* +distinguish them, whatever it stores. Closing that half requires a token argument on the compliance hooks — an +upstream `RuleEngine` / CMTAT interface change, the same conclusion reached for F-4. + +*What can be done — opt-in caller binding, closing the "one instance, two engines" half.* This blocks the +deployment mistake the CLAUDE.md gotcha actually describes, and is `view`-preserving: + +```solidity +// in TotalSupplyCapManager / ChainlinkPoRFeedManager / BalanceCapManager +/// @notice When set, the only address allowed to notify this rule. Zero = unrestricted (legacy behaviour). +address public boundCaller; + +function bindCaller(address caller) public virtual onlyMaxTotalSupplyManager { + require(caller != address(0), RuleMaxTotalSupply_CallerAddressZeroNotAllowed()); + require(boundCaller == address(0), RuleMaxTotalSupply_CallerAlreadyBound(boundCaller)); + boundCaller = caller; + emit CallerBound(caller); +} + +// in the rule's write hooks only -- never on the ERC-1404 read path +function _assertBoundCaller() internal view virtual { + address bound = boundCaller; + require(bound == address(0) || msg.sender == bound, RuleMaxTotalSupply_CallerNotBound(bound, msg.sender)); +} +``` + +Design constraints that make this shape the right one: + +- **Bind at configuration, not on first use.** Pinning the first caller lazily would need an `SSTORE` inside + `_transferred`, which is `internal view` today and whose public `transferred(...)` wrappers are declared `view`. + Making them non-`view` changes the published ABI mutability of four deployable contracts and turns read-only + validation rules into stateful ones. An explicit one-shot setter keeps every hook `view` and costs one warm + `SLOAD` per transfer. +- **Unset must stay permissive**, or the change is breaking for every existing deployment and for the direct + (Topology B) wiring where the token itself calls the rule. +- **Enforce on the write path only.** Adding the check to `detectTransferRestriction` would make a third party's + pre-flight query revert or fail, and those views must not revert. +- **Add `unbindCaller`**, symmetric with `unbindToken` on the operation rules, or a mis-set binding bricks the + rule permanently. Document that unbinding does not reset the observed token. + +*What this does and does not buy.* It stops one instance being wired into two RuleEngines — the silent +over-mint/freeze scenario. It does **not** isolate two tokens served by a single engine; that remains open and +must stay documented. Given it is a partial remedy for a documented, trusted-role misconfiguration, the honest +cost/benefit is: worth doing if the cap rules ever ship an upgradeable variant or a deployment script that wires +engines automatically, and not worth a breaking storage-layout change before then. + +*Zero-cost alternative available today:* `tokenContract` / `balanceToken` are already public and every change +emits an event, so a deployment checklist plus an off-chain assertion that +`rule.tokenContract() == the token whose engine holds this rule` catches both halves — including the one no +on-chain guard can reach. That is the currently recommended control and should be stated in the deployment guide. + +### NM-6 — `RuleNFTAdapter` handles owner-initiated transfers differently across entrypoints — ✅ FIXED (`v0.6.0`) + +**Claim (Medium).** `transferred(FungibleTransferContext)` / `(MultiTokenTransferContext)` normalise +`ctx.sender == ctx.from` to the direct `_transferred` hook, while the ERC-7943 5-arg +`transferred(spender, from, to, tokenId, value)` always calls `_transferredFrom`. For `spender == from`, +`RuleSpenderWhitelist` accepts via the context path and rejects via the ERC-7943 path. + +**Verdict — informational; confirmed divergence, not a bypass.** The asymmetry is real +(`RuleNFTAdapter.sol:46-63` vs `:89-102`). It is not a loosening of policy: **the context path's answer is the +one that matches the rest of the library.** `RuleSpenderWhitelist`'s documented contract is that *direct* +transfers are always allowed and only delegated ones are screened; an owner moving their own tokens is a direct +transfer, and a plain ERC-20 `transfer` produces exactly the same outcome (CMTAT passes `spender == address(0)`, +taking the 3-arg path). The deviant branch is the ERC-7943 5-arg one, which is **stricter** than intended when a +caller elects to pass `spender == from`. Nothing is admitted that a plain transfer would not admit, so there is +no compliance gap — only an inconsistency for an integrator who reaches for both surfaces. + +**Improvement — implemented in `v0.6.0`; contained to one file.** Lift the normalisation the context entrypoints +already perform into a shared helper, and apply it to the ERC-7943 overloads so all six adapter entrypoints agree. + +```solidity +// RuleNFTAdapter -- one predicate, used by every entrypoint that receives a spender +/** + * @notice Returns whether `spender` acts on behalf of `from` rather than as `from` itself. + * @dev An owner moving their own tokens is a direct transfer: a plain ERC-20 `transfer` reaches + * the 3-arg hook with `spender == address(0)`, and the ITransferContext entrypoints already + * normalise `sender == from` the same way. + */ +function _isDelegated(address spender, address from) internal pure virtual returns (bool) { + return spender != address(0) && spender != from; +} +``` + +Then the two `ITransferContext` entrypoints (`:46-63`) become `if (_isDelegated(ctx.sender, ctx.from))`, and the +three spender-aware ERC-7943 overloads gain the same branch: + +```solidity +function transferred(address spender, address from, address to, uint256 /* tokenId */, uint256 value) + public virtual override(IERC7943NonFungibleComplianceExtend) +{ + if (_isDelegated(spender, from)) { + _transferredFrom(spender, from, to, value); + } else { + _transferred(from, to, value); + } +} +// identically for detectTransferRestrictionFrom(...) and canTransferFrom(...) +``` + +Behaviour audit — which rules actually change when `spender == from` on the 5-arg path: + +| Rule | Today | After | Net | +|---|---|---|---| +| `RuleSpenderWhitelist` | rejects an unlisted owner (code 66) | allows | **Fixed** — matches its documented "direct transfers are always allowed" | +| `RuleBlacklist`, `RuleSanctionsList`, `RuleERC2980` | blocks via the spender branch | blocks via the `from` branch | none — still blocked, different code | +| `RuleWhitelist` (`checkSpender`) | needs `from` listed *and* spender listed | needs `from` listed | none — same address | +| `RuleIdentityRegistry` (`checkSpender` on, `checkSender` off) | rejects an unverified owner (code 57) | allows | **Observable change**, and the ERC-3643-conformant answer: the spec screens the receiver only, and the same holder's plain `transfer` already passes | + +So the deny-lists are unaffected, one rule is corrected, and one loosens in the direction the standard requires. +Cost: one `internal pure` call, no storage. Pin it with a test per affected rule asserting that the 5-arg +`spender == from` call and the 3-arg call return the same code. + +*Rejected alternative — normalise the other way* (make the context entrypoints always call `_transferredFrom`, +retaining spender semantics). It would screen an owner as their own spender on every plain transfer relayed +through `ITransferContext`, breaking `RuleSpenderWhitelist`'s documented contract and diverging from what CMTAT +produces for the same transfer. Consistency achieved at the price of the wrong answer. + +*Do not* apply the normalisation inside `_detectTransferRestrictionFrom` / `_transferredFrom` themselves: those +are the generic 4-arg hooks CMTAT and the RuleEngine call, and rewriting `spender == from` there would silently +change every rule on the main integration path, not just the ERC-7943 surface. + +**Resolution — `v0.6.0`.** + +*The principle that fixed the scope.* The three interfaces signal a direct transfer **differently**, and that, +not the entrypoint count, is what decides the routing: + +| Interface | Direct transfer arrives as | Delegated as | +|---|---|---| +| CMTAT 3-arg / 4-arg | the 3-arg overload, or `spender == address(0)` | `spender != address(0)` | +| ERC-7943 `tokenId` overloads | `spender == from` — the interface calls that parameter "the address performing the transfer (**owner**/operator)" | `spender != from` | +| `ITransferContext` | `sender == from` (the token's `msg.sender`), or `0` | `sender != from` | + +The ERC-7943 and `ctx` interfaces share a convention; the CMTAT pair uses a different one that already +distinguishes the two cases. So the fix normalises the **adapter** entrypoints only, and deliberately leaves the +4-arg CMTAT path alone — which also means no change to the primary integration path, no restriction-code +relabelling for existing integrators, and one file touched instead of twelve. + +*Changed:* `src/rules/validation/abstract/core/RuleNFTAdapter.sol` — added +`_isDelegated(spender, from) => spender != address(0) && spender != from`, replaced the duplicated predicate in +both `ctx` entrypoints with it, and routed the three ERC-7943 spender-aware overloads +(`transferred`, `detectTransferRestrictionFrom`, `canTransferFrom`) through it. Contract-level NatSpec records +the table above and warns against "aligning" the 4-arg path. + +*Regression tests* in `test/TransferContext/OverloadParity.t.sol` — the suite already existed for exactly this +property but only ever tested two of the three input shapes (`sender == 0` and `sender != from`), which is why +the gap survived. Added `_assertSelfSpenderIsDirect`, run for every rule in the suite on both an allowed and a +blocked pair, plus two targeted tests: `test_NM6_SelfSpenderIsNotScreenedByTheSpenderWhitelist` (the outcome +that was wrong) and `test_NM6_CmtatFourArgPathKeepsScreeningASelfSpender` (pinning the deliberate asymmetry so +nobody removes it later). The suite's header comment, which asserted flat parity, now states the per-interface +conventions — the loose wording is what made the missing case invisible. + +*Verified, not assumed.* Reverting the three routings fails **6 of 10** tests across **5 rules**, and the failure +messages are the impact analysis: + +``` +RuleBlacklist [self-spender, blocked]: 38 != 36 ← blocked either way, code relabelled +RuleERC2980 [self-spender, blocked]: 62 != 60 ← blocked either way, code relabelled +RuleSanctionsList [self-spender, blocked]: 32 != 30 ← blocked either way, code relabelled +RuleWhitelist [self-spender, blocked]: 23 != 21 ← blocked either way, code relabelled +RuleSpenderWhitelist [self-spender]: 66 != 0 ← THE ONLY OUTCOME CHANGE +``` + +For the deny-lists the transfer was rejected before and after — only which leg reported it changed, because the +owner is screened as `from` instead of as `spender`. `RuleSpenderWhitelist` is the one rule where the answer was +actually wrong: an owner-initiated ERC-721 `transferFrom` was rejected with code 66 despite the rule documenting +that direct transfers are always allowed. A genuine delegated transfer by the same unlisted address is still +rejected — the screen was narrowed to what it always claimed to cover, not removed. + +*Suites:* 835 tests pass on the default profile, 31 on `FOUNDRY_PROFILE=erc3643`. Coverage on `RuleNFTAdapter`: +**100% statements, 100% branches**. + +*Also updated:* `RULE_SEMANTICS.md` §3, which previously described the parity as flat and is now the reference +for the per-interface conventions. + +### NM-7 / NM-12 / NM-15 — Conditional-transfer approvals are not token-scoped + +**Claim (Medium ×2, Low ×1).** `_transferHash(from, to, value)` has no token dimension, while `bindRuleEngine` +authorises an engine that may relay several tokens. An approval recorded for token A is consumable by an +identical transfer of token B, and `unbindToken` clears neither `approvalCounts` nor `ruleEngine`. + +**Verdict — accepted as design; this is the documented reason the multi-token variant exists.** Verified at +`RuleConditionalTransferLightApprovalBase.sol:163-174` and `RuleConditionalTransferLightBase.sol:191-206`. The +`bindRuleEngine` NatSpec states the constraint in the scanner's own terms and then some: + +> **bind ONLY an engine that serves this one token.** Approvals here are keyed `(from, to, value)` with **no +> token dimension**… If the engine serves several tokens, an approval recorded for one is consumable by ANY of +> them — approve 100 for token A, and a 100 transfer of token B consumes it. That is inherent to the single-token +> rule and is why `RuleConditionalTransferLightMultiToken` exists. + +The stale-state half is covered by the `bindToken` WARNING plus `resetApproval` / `unbindRuleEngine` (v0.4.0 +audit F-9). The scanner's closing point — "enforced solely by NatSpec warnings; nothing in the code prevents an +administrator from wiring a multi-tenant engine" — is correct and is the accepted position: the compliance +manager is a trusted role, and the alternative (a per-token approval key) is a different rule that already ships. + +### NM-9 / NM-16 — Spender policy is unenforceable through spender-less hooks + +**Claim (Medium + Low).** Spender screening lives exclusively on the 4-arg path. +`RuleSpenderWhitelistBase.transferred(address,address,uint256)` is a no-op and its 3-arg detector returns +`TRANSFER_OK`; the same context loss disables code 23 (unlisted spender), 38 (blacklisted), 32 (sanctioned) and +62 (frozen). A token routing `transferFrom` through the 3-arg path lets a restricted spender move tokens. + +**Verdict — accepted as design; correct, and a topology requirement rather than a defect.** Verified at +`RuleSpenderWhitelistBase.sol:49`, `:91-93`, `:102-115`. A rule cannot screen an identity it is never given. +`RULE_SEMANTICS.md` §1 already scopes the whole spender column to "the 4-arg `transferred(spender, from, to, +value)` path… (CMTAT v3.3+)". + +The scanner is nonetheless pointing at something worth stating more loudly, and it is the same root cause as +NM-11: **a real ERC-3643 / T-REX token calls `_tokenCompliance.transferred(_from, _to, _amount)` from +`transferFrom` — three arguments, no spender.** On that integration `RuleSpenderWhitelist` is *silently inert* +rather than merely unhelpful, and the spender branches of the blacklist / sanctions / ERC-2980 rules never fire. +Both supply-based cap rules now ship ERC-3643 variants (NM-11), and the per-interface conventions are written up +in `RULE_SEMANTICS.md` §5; the spender-inertness above remains a documentation matter, since no rule can screen +an identity it is never given. + +### NM-10 — Future-dated PoR timestamps skip the freshness check — ✅ FIXED (`v0.6.0`) + +**Claim (Medium).** `ChainlinkPoRFeedManager._maxBackedSupply` flags staleness only when +`block.timestamp > updatedAt`; it does not reject `updatedAt > block.timestamp`. A feed returning an old answer +with a future timestamp is treated as fresh until that timestamp plus the staleness window elapses. + +**Verdict — informational; confirmed code behaviour, the cheapest hardening in the report.** The guard is exactly +as quoted (`ChainlinkPoRFeedManager.sol:215`): + +```solidity +if (staleness != 0 && block.timestamp > updatedAt && block.timestamp - updatedAt > staleness) { +``` + +The `block.timestamp > updatedAt` term exists to keep the subtraction from underflowing on a MUST-NOT-revert +path, and it has the side effect of admitting any future timestamp. Reachability is narrow: a Chainlink +aggregator stamps `updatedAt` with `block.timestamp` at write time on the same chain, so a future value cannot +arise legitimately — it requires a faulty or compromised feed, and the report's own severity note concedes that +"a future timestamp alone does not increase mint headroom". A feed able to forge a timestamp can also simply +overstate `answer`, which the rule trusts by construction. + +**Improvement — implemented in `v0.6.0`; one line, no new restriction code, no new storage.** Treat a future `updatedAt` +as a **malformed answer** rather than as a staleness question. `CODE_RESERVES_ANSWER_INVALID` (77) already means +"the feed responded but the answer cannot be used: a negative reserve, or an incomplete round", and a round +stamped in the future is the same class of defect. Fold it into that existing branch +(`ChainlinkPoRFeedManager.sol:209-217`): + +```solidity +try feed.latestRoundData() returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80) { + // A negative reserve is meaningless, `updatedAt == 0` marks a round that never completed, and a + // round stamped in the future cannot have been written by an aggregator on this chain -- all three + // are malformed answers, not stale ones. + if (answer < 0 || updatedAt == 0 || updatedAt > block.timestamp) { + return (CODE_RESERVES_ANSWER_INVALID, 0); + } + uint256 staleness = maxStalenessSeconds; + // `updatedAt <= block.timestamp` is guaranteed above, so the subtraction cannot underflow and the + // `block.timestamp > updatedAt` guard that used to carry it is no longer needed. + if (staleness != 0 && block.timestamp - updatedAt > staleness) { + return (CODE_RESERVES_FEED_STALE, 0); + } + ... +``` + +Why this framing beats a second staleness branch: + +- **It resolves the `maxStalenessSeconds == 0` ambiguity instead of creating it.** Zero is documented as + *disabling the staleness check*; a future-timestamp rejection gated on `staleness != 0` would be surprising, + and one that ignores the gate would contradict the documented meaning of zero. As an answer-validity check it + is correctly unconditional — a malformed round is malformed whether or not freshness is being policed. +- **No code-range or ABI change.** 77 already exists, is already returned by `canReturnTransferRestrictionCode` + (`RuleChainlinkPoRBase.sol:62-66`), and already has a `messageForTransferRestriction` string. Adding a new code + would touch the invariant storage, the message mapping, `canReturnTransferRestrictionCode`, `CLAUDE.md`'s code + table and the docs, for no diagnostic gain. +- **It removes the underflow guard's side effect** rather than layering a second check on top of it, so the + reason each comparison exists stays legible. +- **It cannot break the revert-free invariant**: the change is one comparison on values already in scope. + +**Resolution — `v0.6.0`.** + +*Changed:* + +- `src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol` — `updatedAt > block.timestamp` folded into the + malformed-answer branch, and the now-redundant `block.timestamp > updatedAt` term dropped from the staleness + comparison (step 3 guarantees the subtraction cannot underflow). The comment states why a future stamp is not + treated as staleness. +- `src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol` — the + `CODE_RESERVES_ANSWER_INVALID` NatSpec now lists all three causes and records the `maxStalenessSeconds == 0` + reasoning. + +*Regression tests added* — 5 in `test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol`: a future-dated round yields +77 from `detectTransferRestriction` and `canTransfer`; it is still rejected with `maxStalenessSeconds == 0` (the +test that pins the design decision); `updatedAt == block.timestamp` still passes (the boundary a just-published +round sits on); `maxBackedSupply()` previews 77 without reverting; and the write hook reverts the mint. + +*Verified, not assumed.* Reverting the source change fails 4 of the 5 with the predicted symptoms — +`assertion failed: 0 != 77` three times, and `next call did not revert as expected` for the enforcement test. The +fifth (the `updatedAt == block.timestamp` boundary) passes either way by construction, which is what makes it a +useful guard against over-correcting into `updatedAt >= block.timestamp`. + +*Suites:* 833 tests pass on the default profile, 31 on `FOUNDRY_PROFILE=erc3643`. Coverage on +`ChainlinkPoRFeedManager`: **100% statements, 100% branches**; `RuleChainlinkPoRBase` 100% across the board. + +*Also updated:* `doc/technical/contracts/RuleChainlinkPoR.md` — the restriction-code table, the numbered +evaluation order, the operator triage table, and the two rows of the Chainlink ACE comparison that described the +old underflow guard. The ACE comparison now records that this rule rejects a future-dated round where ACE +underflow-panics on it, and that the rejection is not gated on `maxStalenessSeconds`. + +Still hardening rather than a fix in impact terms: reaching the branch needs an aggregator already misbehaving +badly enough to forge a timestamp, and such an aggregator can overstate `answer` directly. What it buys is that +the rule no longer has a state in which it treats an impossible timestamp as evidence of freshness. + +### NM-11 — Caps double-count when the token notifies **after** moving the value — ✅ FIXED (`v0.6.0`, 2 of 3 rules) + +**Claim (Medium).** `BalanceCapManager._capExceeded` and `TotalSupplyCapManager._capExceeded` compare the live +balance/supply against `value`. That is correct only if the token calls `transferred(...)` *before* mutating +state. ERC-3643 / T-REX tokens call it *after*, so `balanceOf(to)` already includes `value` and the effective cap +becomes `balance_before + 2 × value <= maxBalance`. The read path (`canTransfer`) answers pre-update and the +write path then reverts on the same parameters — legitimate transfers are blocked and the last chunk of headroom +is unreachable. + +**Verdict — CONFIRMED.** Every step checks out: + +- The assumption is real and already stated in-source (`RuleMaxBalanceBase.sol:21-23`): *"**Assumes the token + calls this BEFORE moving the value**, so `balanceOf(to)` still excludes `value`. CMTAT does; a token notifying + afterwards would halve the effective cap."* It is pinned by + `testMintExactlyToTheCapProvesPreUpdateAccounting`, and `CLAUDE_ANALYSIS_MAXBALANCE.md` H-1 records the + mutation test that proved the guard. +- The vendored T-REX token calls it afterwards. `lib/ERC-3643/contracts/token/Token.sol` — `transfer` (`:532-533`), + `transferFrom` (`:312-313`) and `forcedTransfer` (`:557-558`) each run `_transfer(...)` **then** + `_tokenCompliance.transferred(...)`; `mint` reports through `_tokenCompliance.created(_to, _amount)` (`:572`, + after `_mint`), which `RuleEngineBase.created` forwards as the 3-arg `transferred(address(0), to, value)` — and + both cap rules gate on `from == address(0)`, so the mint path is affected too. +- **This is a configuration the project supports and tests**, not a hypothetical: `test/ERC3643Real/ + ERC3643RealTokenRuleEngine.t.sol` wires `real ERC-3643 Token ── compliance slot ──▶ RuleEngine ──▶ Rule` and + relies on the rule reverting inside the post-state-change notification as the enforcement mechanism + (`testForcedTransfer_StillBlockedByTheRuleViaTransferred`). That suite exercises `RuleWhitelist`, which is + order-independent; **no cap rule is covered there**, which is why this was not caught. + +Direction of failure is **conservative** — over-restriction, never over-issuance. Nothing can be minted or +received above the cap; what breaks is that transfers and mints *within* the cap are rejected, and the pre-flight +view disagrees with enforcement. There is no exploit, and the CMTAT path is unaffected. + +**Enabling structure landed in `v0.6.0` (the fix itself is still a deployment choice).** The three rules now +share [`CapAccounting`](../../../../../src/rules/validation/abstract/core/CapAccounting.sol) and each exposes +`_detectTransferRestrictionOnNotify`, the hook the **write** path enforces through. It defaults to the pre-flight +check — today's CMTAT behaviour, unchanged — and an ERC-3643 variant overrides one line: + +```solidity +function _detectTransferRestrictionOnNotify(address from, address to, uint256) + internal view override returns (uint8) +{ + return _detectTransferRestriction(from, to, 0); // the observation already includes the value +} +``` + +Two design points that came out of building it, both now pinned by tests: + +- **Only the write path may be re-phased.** A single "observation includes the value" flag applied to both paths + was the first shape tried and is wrong: a pre-flight view always runs *before* the movement on either kind of + token, so re-phasing it makes the pre-flight answer disagree with enforcement — the mirror image of this very + finding. `testMaxTotalSupply_PreFlightViewStillCountsTheValue` pins that. +- **`_currentSupply` / `_balanceOf` are the second seam**, letting a rule serve the figure from its own storage + instead of the token. Such a rule controls when it records, so it never has to answer the phase question at + all. Verified feasible for *supply*; **not** for per-address balances, because how `Token.recoveryAddress` + moves a balance changed across T-REX versions — up to 4.1 it routed through the public `forcedTransfer`, which + notifies compliance, while the vendored 4.2.0-beta1 calls `_transfer` directly and notifies nobody. A shadow + ledger would be correct on one minor version and permanently skewed on the next. + +Worked variants of all three rules live in `src/mocks/harness/ERC3643CapHarnesses.sol`, and +`test/CapAccounting/ERC3643CapSeams.t.sol` reproduces this finding on the stock rules while showing the variants +are correct. `RULE_SEMANTICS.md` §5 is the write-up. + +**Resolution — `v0.6.0`. Shipped for two of the three rules; the third is deliberately left.** + +*The arithmetic, concretely.* Reserves 1000, supply 0, an agent mints 1000 on a real T-REX token: + +| Step | `_currentSupply()` | Comparison | Result | +|---|---|---|---| +| 1. `canTransfer(0, to, 1000)` — **before** `_mint` | `0` | `_capExceededBy(0, 1000, 1000)` → `1000 > 1000-0`? no | allowed | +| 2. `_mint(to, 1000)` | — | supply becomes 1000 | — | +| 3a. `created` → **stock rule** | `1000` | `_capExceededBy(1000, 1000, 1000)` → `1000 > 0`? **yes** | **reverts** | +| 3b. `created` → **ERC-3643 variant** | `1000` | `_capExceededBy(1000, 1000, 0)` → `0 > 0`? no | allowed | + +At step 3 the minted amount is already inside `currentSupply`; the stock rule adds the same amount again as +`value` and asks whether 2000 fits under 1000. Row 1 is why the read path must keep projecting `value`: the token +consults compliance on **both** sides of the state change inside one transaction. + +*Contracts added.* + +| Contract | For | +|---|---| +| `RuleChainlinkPoRERC3643` / `…Ownable2Step` | Reserve-backed mint cap on ERC-3643 | +| `RuleMaxTotalSupplyERC3643` / `…Ownable2Step` | Static supply cap on ERC-3643 | + +Each is a subclass overriding `_detectTransferRestrictionOnNotify` and nothing else; reserve/cap logic, +restriction codes, configuration, roles and events are inherited unchanged, and the stock rules are untouched. + +*Tests.* 49 added in total: + +- `test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol` (12) and + `test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol` (10) drive the **genuine** vendored + `lib/ERC-3643/` token, not a mock. Four of them pin the stock rules failing on that same token, so this + finding stays executable rather than becoming prose. +- The supply-cap suite covers **both compositions with the PoR variant** — static cap binding and reserves + binding — which is the pairing the documentation prescribes, since PoR has no margin parameter. +- Unit suites in the default profile for each variant (10 + 10), because `forge coverage` skips + `test/ERC3643Real/**` and the deployables would otherwise report 0%. +- `test/CapAccounting/ERC3643CapSeams.t.sol` (7) covers the seams generically, including `RuleMaxBalance`. + +*A second ERC-3643 hazard found while testing this one, and now pinned.* T-REX deploys the token and +initialises it in two steps, and an uninitialised `Token` reports `decimals() == 0`. `RuleChainlinkPoR`'s +constructor probes `decimals()` and accepts a matching `0`, so a rule built before `init` is configured for a +0-decimals token — and `init(..., 18, ...)` then makes it an 18-decimals token while the rule still believes 0. +Nothing reverts and no event marks it; reserves are scaled by `10 ** 18` too little and every mint is refused. +The same mistake reversed would authorise unbacked minting. The constructor probe cannot catch it — it genuinely +succeeded. The remedy is deployment order (build the rule after `init`, or re-sync with `setTokenMetadata`), +documented on the contract page and pinned by `testRuleBuiltBeforeInitCachesTheWrongDecimals`. + +*Documentation.* New pages `doc/technical/contracts/RuleChainlinkPoRERC3643.md` and +`RuleMaxTotalSupplyERC3643.md`, each leading with the ERC-3643-only warning and a table of what breaks with the +wrong variant **in either direction** — neither mistake reverts at deployment. `RULE_SEMANTICS.md` §5 carries the +seam write-up; `CLAUDE.md` / `AGENTS.md` carry the gotcha; both READMEs list the variants. + +**`RuleMaxBalance` deliberately has no ERC-3643 variant.** It is not the same one-line change, for three reasons +that need a policy decision rather than a hook override: + +- `balanceOf(to)` is **per-address**, so the rule engages on every transfer rather than only on mints — a far + larger interaction surface with T-REX's agent powers than the two supply rules have. +- **`forcedTransfer` does notify compliance**, so a post-update variant would *revert* an agent's forced transfer + that pushes the recipient over the cap. On T-REX ≤ 4.1, where `recoveryAddress` routes through + `forcedTransfer`, that **bricks wallet recovery** whenever the destination wallet already holds tokens. +- On the vendored 4.2.0-beta1 `recoveryAddress` notifies **nobody**, so a recovered wallet can silently sit above + the cap. A token-reading rule self-heals — further receipts are blocked — but the invariant is violated in + state with no event from the rule. + +T-REX's own module library also already ships a `MaxBalanceModule`, so the marginal value is lowest of the three. +`RuleMaxBalance` is therefore documented as CMTAT-path-only until the forced-transfer exemption question is +settled. + +### NM-14 / NM-22 — A reverting identity registry or sanctions oracle reverts the read path + +**Claim (Low ×2).** `setIdentityRegistry` accepts any non-zero address without verifying `isVerified(address)` is +callable, and `RuleSanctionsListBase` calls `oracle.isSanctioned(...)` with no failure handling. A registry or +oracle that reverts, is codeless, or returns malformed data makes `detectTransferRestriction` / `canTransfer` — +which the project documents as never-reverting — revert, and halts every transfer, mint and burn on the bound +token. + +**Verdict — accepted as design; already catalogued and dismissed in the v0.4.0 audit.** The code is as described +(`RuleIdentityRegistryBase.sol:101-105`, `:213`; `RuleSanctionsListBase.sol:161-167`, `:191`), and +`CLAUDE_AUDIT.md`'s "observations considered and dismissed" table carries the row verbatim: *"Reverting sanctions +oracle / identity registry bricks transfers — trusted external dependency; a revert bubbles up with no state +corruption."* Failure is **closed** (nothing is admitted), the state is intact, and recovery is a single +privileged `setSanctionListOracle` / `clearSanctionListOracle` / `setIdentityRegistry` call. + +Fair caveat the scanner earns: the library's "the ERC-1404 views MUST NOT revert" invariant is enforced for the +*supply*, *balance* and *PoR feed* reads (guarded by `try/catch` plus configuration probes) but **not** for these +two. Closing the gap means choosing a fail direction for an unreadable list and minting new restriction codes for +"registry unavailable" / "oracle unavailable" — a deliberate, breaking addition to the code ranges. Recorded as an +open, intentional asymmetry rather than a silent one. + +### NM-17 — `approveAndTransferIfAllowed` can leave a residual approval — ✅ FIXED (`v0.6.0`) + +**Claim (Low).** The helper records the approval *before* `safeTransferFrom` so the callback can consume it, and +never verifies afterwards that it was consumed. If the token does not call back — a plain ERC-20 bound for the +helper, or an engine never bound / since unbound — the transfer succeeds and the approval count stays +incremented, authorising one later unapproved transfer of the same `(from, to, value)`. + +**Verdict — accepted as design; the inversion is deliberate, documented, and previously triaged.** Verified at +`RuleConditionalTransferLightBase.sol:113-129`; the NatSpec states both halves ("This function is only safe for +tokens that call back `transferred()` during transfer" and "CEI is intentionally inverted so the approval exists +for the callback"), and `CLAUDE_AUDIT.md` dismisses the CEI inversion as "deliberate and documented; the rule +custodies no value, and reentrancy could at most consume approvals the operator already granted for the same +tuple". Reaching the residual state requires the operator to run the helper against a binding they configured +incorrectly, and the leftover is visible via `approvedCount` and clearable via `resetApproval` / +`cancelTransferApproval`. + +**Improvement — implementable, ~5 lines plus one error, in both variants.** The helper cannot check the callback +*happened*, but it can check the only thing that matters: that the approval it created was consumed. Snapshot the +count, and require it back afterwards. + +```solidity +// RuleConditionalTransferLightBase.approveAndTransferIfAllowed +function approveAndTransferIfAllowed(address from, address to, uint256 value) + public virtual onlyTransferApprover returns (bool) +{ + address token = getTokenBound(); + require(token != address(0), RuleConditionalTransferLight_TokenNotBound()); + + uint256 approvalsBefore = approvedCount(from, to, value); + approveTransfer(from, to, value); + + uint256 allowed = IERC20(token).allowance(from, address(this)); + require(allowed >= value, RuleConditionalTransferLight_InsufficientAllowance(token, from, allowed, value)); + + IERC20(token).safeTransferFrom(from, to, value); + + // The approval above exists ONLY for the token's compliance callback to consume. If the count did + // not come back down, no callback reached this rule -- the binding is wrong -- and leaving the + // surplus would authorise a later, never-approved transfer of the same tuple. + require( + approvedCount(from, to, value) == approvalsBefore, + RuleConditionalTransferLight_ApprovalNotConsumed(token, from, to, value) + ); + return true; +} +``` + +with one addition to `RuleConditionalTransferLightInvariantStorage`: + +```solidity +error RuleConditionalTransferLight_ApprovalNotConsumed(address token, address from, address to, uint256 value); +``` + +The multi-token variant needs the identical change in `RuleConditionalTransferLightMultiTokenBase` +(`:131`), using its token-keyed accessor `approvedCount(token, from, to, value)` (`:216`) and its own error +namespace. + +Correctness of the post-condition: + +- **Holds in both supported topologies.** `_transferred` decrements by exactly one and returns early only when an + endpoint is `address(0)` — impossible here, since `safeTransferFrom` would have reverted on a zero `from` and + the helper is not a mint/burn path. Direct binding: the token calls `transferred`. Engine binding: the engine + relays it. Either way the count returns to `approvalsBefore`. +- **Fires exactly where the finding is.** A plain ERC-20 bound with `bindToken` but no callback, or an engine + never bound / since unbound, now reverts the whole call — including the ERC-20 transfer — instead of completing + it and leaving a spendable approval behind. That is a behaviour change worth calling out in the release notes: + a deployment relying on the helper against a non-callback token stops working, which is the point. +- **Reentrancy-safe by construction.** It reads state *after* the external call, so a hostile token can only make + the check fail, never pass spuriously. Any path that consumed more than one approval also fails, which is the + desired direction. +- **Cost:** two warm `SLOAD`s (~200 gas) on an operator-only path. + +**Resolution — `v0.6.0`. Implemented in both variants**, with the error declared in each rule's own invariant +storage (`RuleConditionalTransferLight_ApprovalNotConsumed` / +`RuleConditionalTransferLightMultiToken_ApprovalNotConsumed`). + +*Tests — 5 added, and the existing mock made the awkward case easy.* `MockERC20WithTransferContext` is a no-op +notifier when no rule is set, so leaving `setRule` uncalled produces a token that moves value and tells nobody — +exactly the shape of the finding, with no new mock needed. Single-token: the silent token reverts with +`..._ApprovalNotConsumed`, leaving no residual approval and no moved value; an operator's pre-existing approvals +for the same tuple survive the helper; the ordinary direct-binding flow still consumes exactly one per call. +Multi-token: the same silent-token case, plus a check that the count stays per-token. + +*Verified, not assumed.* Removing the two `require`s makes both silent-token tests fail with *"next call did not +revert as expected"*, and nothing else moves. + +*Note on the pre-existing suite.* All 871 tests passed unchanged the moment the post-condition was added, because +every existing test uses a token that does call back. That is simultaneously the reassurance that this is not a +regression and the evidence that the non-callback path had **no coverage at all** before these tests — which is +how the hole survived. + +*Coverage:* `RuleConditionalTransferLightBase` at 100% statements, branches and functions. + +*Documented* in both contract pages and in the `CLAUDE.md` / `AGENTS.md` gotchas, including the behaviour change: +a deployment running the helper against a non-callback token now reverts instead of completing. That is the fix +rather than a side effect — the transfer was leaving a compliance hole behind. + +### NM-18 — The wrapper can be bricked by a non-`IAddressList` child — ✅ FIXED (`v0.6.0`) + +**Claim (Low).** `RuleWhitelistWrapperBase._detectTransferRestrictionForTargets` casts every child to +`IAddressList` without checking. A rules manager can add a valid `IRule` that is not an address list (e.g. +`RuleMaxTotalSupply`); once it sits before a later whitelist child, any check that has not already resolved every +target reverts on the blind `areAddressesListed` call — read path *and* `transferred`. + +**Verdict — informational; confirmed, and a known open item.** The unchecked cast is at +`RuleWhitelistWrapperBase.sol:237`, and `CLAUDE.md` lists it as a standing gotcha ("`RuleWhitelistWrapper` does +not ERC-165-check its child rules… a non-`IAddressList` child bricks the scan"). It is the still-open half of +v0.4.0 audit **F-5**, recorded there as "partially fixed — `IAddressList` now advertised; the wrapper guard +remains open". The scanner's detail about the short-circuit is accurate and matches the documented behaviour of +`_detectTransferRestrictionForTargets` (early exit once every target resolves), which is exactly why the failure +is *order-dependent* and can appear only for some address pairs. + +**Improvement — implementable now; two complementary layers, and only the first is cheap.** + +*Layer 1 — reject at configuration (recommended).* Override `_checkRule`, **not** `addRule`: it is +`internal view virtual` and both public entrypoints route through it (`addRule` → `_addRule` → `_checkRule`, and +`setRules` → `_addRule` → `_checkRule`), so one override covers every path and stays `view`. **`RuleEngineBase` +already does exactly this** for its own children (`RuleEngineBase.sol:228-233`), so the pattern is established +in the dependency the wrapper inherits from: + +```solidity +// RuleWhitelistWrapperBase -- mirrors RuleEngineBase._checkRule +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; + +function _checkRule(address rule_) internal view virtual override { + RulesManagementModule._checkRule(rule_); // zero-address and duplicate checks + require( + ERC165Checker.supportsInterface(rule_, AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID), + RuleWhitelistWrapper_ChildIsNotAnAddressList(rule_) + ); +} +``` + +`ERC165Checker.supportsInterface` is itself non-reverting — it uses a bounded, gas-capped `staticcall` and +returns `false` for a codeless address, a missing selector or malformed return data — so a hostile candidate +cannot brick the setter it is being screened by. The four intended children already advertise the ID +(`0x5d10e182`): verified on `RuleWhitelistBase:62`, `RuleReceiverWhitelistBase:95`, `RuleSpenderWhitelistBase:79` +and `RuleBlacklistBase:100`, so no legitimate configuration is rejected. + +What layer 1 buys and what it misses: + +- ✅ Closes **NM-18** — the `RuleMaxTotalSupply`-as-child case is rejected at `addRule` instead of bricking + transfers later. +- ✅ Closes **NM-19**'s failure mode — a nested wrapper is refused up front with a named error rather than + bricking every transfer through the parent. It does not *enable* nesting: that needs the wrapper to implement + and advertise `IAddressList` — declined under NM-19, because an OR nested in an OR is algebraically flat. +- ❌ Does **not** close **NM-20** — `RuleBlacklist` advertises the same interface ID, because `IAddressList` + expresses *membership*, not *polarity*. Detecting that needs a separate marker interface (e.g. an `IAllowList` + advertised only by the whitelist rules) or documentation; see NM-20. +- ❌ Does not help a child that is valid at add time and breaks later. EIP-6780 means a deployed child cannot + become codeless, but a child behind a proxy can still be upgraded into something that reverts. + +*Layer 2 — contain at read time (optional, and genuinely harder than it looks).* To stop an already-installed bad +child from reverting the MUST-NOT-revert views, the blind call at `:237` would have to tolerate failure and treat +the child as listing nobody — which is fail-closed for an OR-composition of whitelists. The obstacle is that +`areAddressesListed` returns a **dynamic `bool[]`**, and `abi.decode` of malformed return data reverts *in this +frame*, outside any `catch` — the same uncatchable-decode problem documented in `TokenSupplyReader` and raised by +NM-23. The workable technique is to push the decode into a callee frame so the failure becomes catchable: + +```solidity +function decodeListed(bytes calldata data, uint256 n) external pure returns (bool[] memory listed) { + listed = abi.decode(data, (bool[])); + require(listed.length == n, ...); +} + +// in the scan loop +(bool ok, bytes memory data) = rule(i).staticcall( + abi.encodeCall(IAddressList.areAddressesListed, (targetAddress)) +); +bool[] memory isListed = new bool[](targetsLength); // default: lists nobody +if (ok) { + try this.decodeListed(data, targetsLength) returns (bool[] memory decoded) { isListed = decoded; } + catch { /* keep the all-false default */ } +} +``` + +This adds a public helper to the ABI, an external self-call per child per check, and a silent-degradation path +where a broken child stops contributing without any signal. That is a real cost against a scenario layer 1 +already prevents at configuration, so **layer 1 alone is the recommendation**; layer 2 only earns its place if +the wrapper is ever expected to hold children it does not control. + +**Resolution — `v0.6.0`.** Layer 1 implemented; layer 2 deliberately not. + +*The interface question, answered.* The wrapper calls **one** function on its children — +`areAddressesListed(address[])`, at `RuleWhitelistWrapperBase.sol:245`. `IAddressList` declares **eight** +(four writes, three reads, plus `contains` inherited from `IIdentityRegistryContains`). Guarding on the full id +would demand seven functions the wrapper never touches, including every write function, and reject a read-only +child that works perfectly. So the guard asks for a purpose-built sub-interface instead: + +```solidity +interface IAddressListBatchQuery { + function areAddressesListed(address[] memory targetAddresses) external view returns (bool[] memory results); +} + +interface IAddressList is IIdentityRegistryContains, IAddressListBatchQuery { /* the other seven */ } +``` + +| Constant | Value | Covers | +|---|---|---| +| `IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID` | `0x20e8e17a` | the one selector the wrapper requires | +| `IADDRESS_LIST_INTERFACE_ID` | `0x5d10e182` | the full eight-selector hierarchy, unchanged | + +Factoring the selector into a parent left the **flattened set unchanged**, so `0x5d10e182` keeps its value and +no rule's advertised id moves; all four address-list rules now advertise both. The sub-interface id is safe to +state as a literal — it declares one function and inherits nothing, so the omitted-parent trap that forces the +flattened-helper pattern for `IAddressList` does not apply. Asserted in +`test/InterfaceId/AddressListInterfaceId.t.sol`. + +*The guard.* `_checkRule` is overridden exactly as `RuleEngineBase` does it, so one override covers both +`addRule` and `setRules` and stays `view`: + +```solidity +function _checkRule(address rule_) internal view virtual override { + RulesManagementModule._checkRule(rule_); + require( + ERC165Checker.supportsInterface(rule_, AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID), + RuleWhitelistWrapper_ChildIsNotAnAddressList(rule_) + ); +} +``` + +*Tests.* The WW-2 proof-of-concept in `test/ThreatModel/ThreatModelTests.t.sol` was named +`..._CurrentBehaviour` precisely because it asserted the broken behaviour, and the fix duly made it fail — the +signal the project's convention describes. It is renamed `test_WW2_NonAddressListChildRuleIsRejectedAtAddRule` +and now asserts the rejection, that the wrapper is left intact, and that the bad child was never added. Two +tests were added beside it: a **nested wrapper** is also refused (it does not implement `areAddressesListed`, so +it would have bricked the parent — the NM-19 failure mode, though nesting itself remains unsupported), and +`test_WW2_GuardCannotRejectAnInvertedPolarityChild_CurrentBehaviour` pins the guard's **limit**, since a +`RuleBlacklist` passes it and still inverts the wrapper (NM-20). Four assertions were added to the interface-id +suite; `RuleWhitelistWrapperBase` is at 100% statements, branches and functions. + +*Layer 2 (read-time containment) not implemented*, as recommended above: it needs an external self-call to make +the dynamic `bool[]` decode catchable, adds a public helper to the ABI, and introduces silent degradation — all +against a scenario layer 1 now prevents at configuration time. + +*The earlier "why it has stayed open" reasoning was wrong and is corrected.* `RulesManagementModule._checkRule` +does test only non-zero and duplicate, but `RuleEngineBase` **overrides** it to add an `IRule` ERC-165 check +(`RuleEngineBase.sol:228-233`), so the engine was already guarded and the wrapper was the outlier. The +dependency supplied the template rather than an argument against it. + +### NM-19 — The wrapper does not implement `IAddressList`, so wrappers cannot nest — 🚫 WON'T DO + +**Claim (Low).** `RuleWhitelistWrapperBase` implements `IIdentityRegistryVerified` but omits `areAddressesListed` +/ `isAddressListed`. A wrapper therefore cannot be a child of another wrapper: the parent's blind +`areAddressesListed` STATICCALL hits a missing selector with no fallback and reverts every transfer. + +**Verdict — confirmed as described; the harm is fixed, the feature is declined.** Two halves, decided +differently: + +- **The DoS half is closed.** A nested wrapper used to be accepted and then bricked every transfer through the + parent. Since NM-18 it is **refused at `addRule`** with `RuleWhitelistWrapper_ChildIsNotAnAddressList`, pinned + by `test_WW2_NestedWrapperIsRejectedAtAddRule`. Nothing silently breaks any more. +- **The feature half — actually enabling nesting — is declined.** What remains is a feature request, and it does + not earn its cost. + +**Why nesting is not worth enabling.** + +*It buys zero expressive power.* The wrapper is an OR, and `OR(OR(a,b), OR(c,d))` ≡ `OR(a,b,c,d)`. Nesting an OR +inside an OR flattens algebraically: there is no policy a nested wrapper can express that a flat child list +cannot express identically. The composition an integrator might actually want from nesting is already available +one level up — `RuleEngineBase._detectTransferRestriction` returns the **first non-zero** code, so rules in an +engine compose with **AND**: + +| Composition wanted | How to get it today | +|---|---| +| OR of lists | one wrapper, flat children | +| AND of ORs | several wrappers in the `RuleEngine` | +| OR of ORs | identical to a flat wrapper — nesting adds nothing | + +*The gas is multiplicative on exactly the path that matters.* The measured scan is **~8.8k gas per child**, and +this page's own analysis notes that the worst case is the common case: a **rejected** transfer never early-exits, +because the exit only fires once every target address is resolved. A 10 × 10 nest therefore costs **~880k gas per +transfer** where the equivalent flat wrapper costs **~90k** — the same policy at roughly ten times the price, +paid by the transferring user on every transfer, forever. The operator guidance is to stay at or below ten +children; nesting is a way to blow past that budget without it looking like a cap change. + +*It introduces a cycle class nothing can prevent cheaply.* A wrapper added to itself, or A → B → A, recurses +until out-of-gas. That bricks transfers **and** `isVerified`, which sits on the ERC-3643 identity path. Detecting +cycles on-chain means traversing the whole child graph on every `addRule`, itself unbounded. This matters +particularly now: NM-18 and NM-20 moved this wrapper *away* from documentation-only discipline, and a feature +whose only defence is "do not do that" would reverse that direction. + +**Disposition: won't do.** The current behaviour is best read as *nesting depth limited to 1, enforced by +construction* — which, given the algebra above, is the same expressive power without the cost or the cycle +hazard. Reopen it only if the wrapper's semantics ever stop being a plain OR, since that is the assumption the +whole argument rests on. + +Delegated administration, the one real motivation, already works **flat**: this page's usage scenario is exactly +three operators each managing their own `RuleWhitelist`, all held by one wrapper. Nesting would only add +groups-of-groups with delegated *group* management, which nobody has asked for. + +### NM-20 — The wrapper reads a `RuleBlacklist` child's membership as eligibility — ✅ FIXED (`v0.6.0`) + +**Claim (Low).** The wrapper ORs raw `areAddressesListed` answers and treats `true` as eligible. `RuleBlacklist` +is a valid `IRule` exposing the same interface with the *opposite* polarity, so adding one as a child makes +blacklisted addresses whitelisted, and `isVerified` returns `true` for them. + +**Verdict — informational; confirmed, a trusted-role misconfiguration.** The polarity inversion is real — the +wrapper cannot distinguish an allow-list from a deny-list through `IAddressList`, and nothing in `addRule` +constrains child semantics. It requires the rules manager to add a blacklist to a *whitelist* wrapper, which is a +category error rather than an attack: the same role can already remove every whitelist child outright. Related to +the accepted v0.4.0 row "wrapper cross-rule OR (`from` in child A, `to` in child B) — documented design; the +wrapper's stated semantics are 'listed in **any** child'". **Resolution — `v0.6.0`. Enforced, not merely documented.** + +The earlier disposition said no code fix was possible, because an ERC-165 guard cannot distinguish an allow-list +from a deny-list when the interface genuinely is the same. That was correct about `IAddressList` and wrong as a +conclusion: it named the remedy — *"distinguishing polarity would need a separate marker interface"* — and then +treated it as out of proportion. It is one function. + +```solidity +interface IAddressListPolarity { + /// @return allowed True when listed addresses are the permitted ones; false for a deny-list. + function isAllowList() external view returns (bool allowed); +} +``` + +`RuleWhitelistWrapperBase._checkRule` now asks **two** questions, because membership and meaning are two +questions: + +| Requirement | Interface | Failure | +|---|---|---| +| Can you answer "is this address listed?" | `IAddressListBatchQuery` (`0x20e8e17a`) | `..._ChildIsNotAnAddressList` | +| Do you declare what membership *means*? | `IAddressListPolarity` (`0xdc4efe10`) | `..._ChildDoesNotDeclarePolarity` | +| Does it mean **allowed**? | `isAllowList() == true` | `..._ChildIsNotAnAllowList` | + +**Absence of the polarity declaration is a refusal, never an assumed allow-list.** That is the only reading that +fails closed for a contract predating the interface — and it is what makes the abstention below work. + +| Rule | `isAllowList()` | As a child | +|---|---|---| +| `RuleWhitelist`, `RuleReceiverWhitelist` | `true` | accepted | +| `RuleBlacklist` | `false` | **rejected** — the finding | +| `RuleSpenderWhitelist` | *declines the interface* | **rejected** — see below | +| nested `RuleWhitelistWrapper` | *no `areAddressesListed`* | rejected at the first check | + +*A second wrong-child class, closed by the same mechanism.* `RuleSpenderWhitelist` deliberately does **not** +implement the interface, and the contract's NatSpec says it must not be changed to. Its set genuinely is an +allow-list, so declaring `true` would be honest about polarity and still wrong: the listed addresses are +permitted **spenders**, not permitted **holders**, and the wrapper would read them as eligible transfer +participants. Polarity is only half the question; the other half is what the addresses *are*. Withholding the +declaration is what makes the fail-closed check refuse it — a cheap way to enforce a caveat that was previously +prose only, noted when the NM-20 documentation pass first tabulated it as "not a child". + +*Tests.* `test_WW2_GuardCannotRejectAnInvertedPolarityChild_CurrentBehaviour` — added one round earlier to pin +the guard's *limit* — duly failed the moment the limit was removed, which is the `_CurrentBehaviour` convention +working. It is renamed `test_WW2_DenyListChildIsRejectedAtAddRule` and now asserts the rejection, that the child +was never added, and that `isVerified(blacklistedAddress)` is false. Two more beside it: the abstaining spender +rule is refused, and genuine allow-lists are still accepted so the guard is not simply refusing everything. +Three assertions added to the interface-id suite, including that each rule declares the polarity it actually has +and that `RuleSpenderWhitelist` does not advertise the interface. + +*Coverage.* `RuleWhitelistWrapperBase`, `RuleWhitelistBase`, `RuleReceiverWhitelistBase` and `RuleBlacklistBase` +all at 100% statements, branches and functions. + +### NM-21 — `RuleMintAllowance` pre-flight views fail open + +**Claim (Low).** `detectTransferRestriction` and `canTransfer` are hardcoded to "allowed" while enforcement +happens on the 4-arg path, so a token-level pre-flight reports success for a zero-quota minter and the mint then +reverts. + +**Verdict — accepted as design; this is v0.4.0 audit finding F-7, closed as documented.** Verified at +`RuleMintAllowanceBase.sol:200-208` and `:227-235`. The 3-arg signature has no minter identity, so a truthful +answer is impossible; returning `TRANSFER_OK` and directing callers to the authoritative view is the documented +resolution. It is stated in the contract NatSpec ("use `detectTransferRestrictionFrom(minter, address(0), to, +amount)` to query allowance"), in `CLAUDE.md` ("`canTransfer` is **not** authoritative for this rule — use +`canTransferFrom(minter, address(0), to, value)`"), in `RULE_SEMANTICS.md` §2, and in the audit's disposition +table. + +### NM-23 / NM-24 — Short successful return data escapes `try/catch` + +**Claim (Low ×2).** `BalanceCapManager._balanceOf`, `TokenSupplyReader._currentSupply` and +`ChainlinkPoRFeedManager._maxBackedSupply` use high-level typed calls in `try/catch`. If a code-bearing +dependency later returns fewer bytes than the declared return type — e.g. after a proxy implementation change — +ABI decoding fails in the *caller's* frame, outside `catch`, so the read path reverts instead of returning codes +83 / 51 / 78 / 79. + +**Verdict — accepted as design; correct Solidity semantics, already documented in the same files.** The claim is +right about the language: a `try` does not catch a decode failure of the return data. It is also already written +down at `TokenSupplyReader.sol:58-61`: *"A `try` call to a codeless address reverts uncatchably — the ABI decoder +fails in the caller's frame, outside `catch`'s reach — and this probe cannot contain it. **Note code alone is not +sufficient either: a contract that returns 0 bytes fails the same way.**"* The same reasoning appears in +`BalanceCapManager` and `ChainlinkPoRFeedManager`. + +Reaching it requires a dependency that **passed** the configuration probe (`_probeTotalSupplyCallable`, +`balanceOf`, `decimals`) and later changed behaviour — the proxy-upgrade case, the same precondition already +documented for the code-length guards. Failure is closed and the state is intact. + +**Improvement — fully implementable, and it retires a documented deployment precondition as a bonus.** Replace +the typed `try/catch` with a low-level `staticcall` plus an explicit length check, so decoding only happens on +data that is known to be long enough. + +```solidity +// TokenSupplyReader +function _currentSupply() internal view virtual returns (bool available, uint256 supply) { + (bool ok, bytes memory data) = + address(_supplyToken()).staticcall(abi.encodeCall(ITotalSupply.totalSupply, ())); + if (!ok || data.length < 32) { + return (false, 0); + } + return (true, abi.decode(data, (uint256))); +} + +// BalanceCapManager +function _balanceOf(address account) internal view virtual returns (bool available, uint256 balance) { + (bool ok, bytes memory data) = + address(balanceToken).staticcall(abi.encodeCall(IBalanceOf.balanceOf, (account))); + if (!ok || data.length < 32) { + return (false, 0); + } + return (true, abi.decode(data, (uint256))); +} +``` + +The same shape applies to `ChainlinkPoRFeedManager._maxBackedSupply`'s two feed reads: `decimals()` needs +`data.length >= 32` (a `uint8` is ABI-encoded as a full word), `latestRoundData()` needs `>= 160` for its five +return values. `_probeTotalSupplyCallable` should be converted too, or configuration would accept a token the +read path then rejects. + +What this buys, beyond the finding itself: + +- **A `staticcall` to a codeless address returns `ok == true` with empty data**, which the length check catches. + That makes the read path safe without any code-length guarantee — so the **"assumes a Cancun-or-later chain" + deployment precondition documented in `TokenSupplyReader`, `BalanceCapManager` and `ChainlinkPoRFeedManager` + can be dropped**, and with it the reasoning about EIP-6780 that three contracts currently carry. That is a + meaningful simplification of the invariant surface, not just a bug guard. +- Failure stays closed and keeps returning the documented codes (51 / 78 / 79 / 83) instead of reverting. +- Gas is a wash: `staticcall` + `abi.decode` costs about the same as the compiler's own `try` sequence. + +Costs, stated honestly: + +- **Loses the typed call.** `abi.encodeCall` keeps argument type-checking against the interface, but the return + type is asserted by the `abi.decode`, not by the compiler — a signature change in `ITotalSupply` would no + longer be caught at the call site. Keep the interfaces as the single source of truth and use `abi.encodeCall` + (never a hand-written `abi.encodeWithSignature`) so the selector cannot drift. +- **Touches four files on the enforcement path of every cap rule**, so it needs the existing suites plus new + cases: a mock returning 0 bytes, one returning 31 bytes, one reverting, and a codeless address (which should + now yield the unavailable code rather than reverting — the assertion that pins the retired precondition). +- The three long `@dev` blocks explaining the uncatchable decode would have to be rewritten, not deleted: they + become the explanation of *why* the reads are low-level. + +**Decision — declined.** Written up as "worth doing" above; re-examined and rejected, because the benefit does +not survive scrutiny. + +*The headline benefit was overstated.* "Retires the Cancun / EIP-6780 precondition" reads as a safety gain and is +not one: `foundry.toml` targets `evm_version = 'prague'`, so the precondition is **already satisfied**, and +trivially so for any realistic deployment. Removing it deletes three NatSpec paragraphs, not a risk. + +*The behavioural delta is one error message on a token that has already failed.* The hole is real — a callee +that succeeds while returning fewer bytes than the declared type fails ABI decoding in the **caller's** frame, +outside `catch` — but work the consequence through: + +| Token state | Today | After the change | +|---|---|---| +| healthy | code `0` | code `0` | +| reverts | code 51 / 78 / 83 | code 51 / 78 / 83 | +| **returns short data** | **the view reverts** | code 51 / 78 / 83 | + +Only the last row moves, and it is **fail-closed in both columns**: the transfer is blocked either way. What +improves is that a pre-flight query returns a diagnostic code instead of reverting, on a dependency that has +already stopped honouring its own interface. + +*The cost is real and larger than first stated.* Not four files — **eight `try` blocks across three**, including +`latestRoundData` with five return values needing a `>= 160` length check. Each becomes hand-rolled ABI handling +in a compliance library that is otherwise high-level Solidity: `abi.decode(data, (uint256))` is an *assertion* +rather than a compiler check, so a signature drift in `ITotalSupply` that the typed call catches at the call site +would pass silently; eight bespoke `data.length` constants are eight chances to write `<` for `!=` or the wrong +`N`, in the very code whose purpose is robustness; and a well-understood idiom is replaced by one every future +reader must re-verify. + +*What would change the answer.* Two conditions, either of which makes it worth revisiting: + +1. **A concrete proxy-upgrade expectation.** Standard T-REX puts the token behind a `TokenProxy` with a swappable + implementation, so a deployment that actually expects implementation churn makes "returns short data" real + rather than hypothetical. +2. **A pre-Cancun target chain**, where the code-length precondition genuinely is not satisfied. + +If either arrives, the implementation should be **one small internal library** (`tryReadUint256(address, bytes)`) +that the eight sites delegate to — written once and tested once — not eight hand-rolled call sites. That is the +difference between a helper and a hand-rolled workaround. + +The limitation itself stays documented in-source in all three contracts, as it already is; this entry records +why it is not closed, so it does not read as unacknowledged debt. + +--- + +## Delta from previous analyses + +This is the **first** Nethermind AuditAgent scan of this repository, so there is no previous AuditAgent run to +diff. Against the other `v0.5.0` analyses: + +| Source | New findings this scan added | Overlap | +|---|---|---| +| Slither 0.11.5 / Aderyn 0.6.5 (`v0.5.0`) | All 24 — no pattern-based detector reached any of them | None | +| `CLAUDE_AUDIT.md` (`v0.4.0`) | NM-3, NM-6, NM-10, NM-11, NM-19, NM-20, NM-23/24 | NM-7/12/15 ≈ F-4; NM-21 ≈ F-7; NM-18 ≈ F-5; NM-14/22 = accepted-risk rows | +| `CLAUDE_ANALYSIS_MAXBALANCE.md` (`v0.5.0`) | NM-11's ERC-3643 consequence | NM-11's premise = H-1 (pre-update accounting) | + +The scan reached a strictly different class of issue than the static analysers, which is the point of running +both: Slither and Aderyn match syntactic patterns, and every finding here is semantic — about who calls a hook, +in what order, and with which arguments. + +## Executive triage + +**Nothing found by this scan is exploitable, and none of the 13 Medium ratings survives verification at Medium.** +There is no path to unauthorised issuance, no way to move tokens past a rule, and no state corruption. The +failures the report describes are, without exception, either **fail-closed** (an over-restrictive cap, a +transfer blocked by a broken oracle) or **inert** (a rule that cannot screen an identity it is never given). + +Every one of the 24 findings describes real code — there are no false positives — but 17 restate positions the +project had already reached and written down, and the 24 items collapse to about 11 distinct claims. + +**The one item that warranted new contracts was NM-11, and it has been acted on.** `RuleMaxBalance`, `RuleMaxTotalSupply` and `RuleChainlinkPoR` +assume the token calls the compliance hook *before* moving value; the vendored ERC-3643 / T-REX token calls it +*after*, and that integration is one this repository supports and tests. The consequence is over-restriction, not +over-issuance. **It has since been fixed** for the two supply-based cap rules, which now ship ERC-3643 variants +(`RuleChainlinkPoRERC3643`, `RuleMaxTotalSupplyERC3643`) verified against the genuine vendored T-REX token; +`RuleMaxBalance` is deliberately left as CMTAT-path-only, because a post-update variant would revert an agent's +forced transfer and, on T-REX <= 4.1, brick wallet recovery — a policy decision rather than a hook override. + +**Nine improvements are specified**, each with its code, its cost and its limit. Seven are done; the other two +are listed in rough order of value per unit of risk: + +| Improvement | Where | Size | Status / verdict | +|---|---|---|---| +| ERC-3643 cap-rule variants (`CapAccounting` + the notify seam) | NM-11 | 2 rules × 2 variants | ✅ **Done in `v0.6.0`** — 49 tests, incl. suites against the genuine T-REX token; `RuleMaxBalance` deliberately excluded | +| Delegate instead of returning early | NM-3 | ~4 lines | ✅ **Done in `v0.6.0`** — behaviour-preserving, 8 regression tests, mutation-verified | +| Future-dated PoR answer → code 77 | NM-10 | 1 line | ✅ **Done in `v0.6.0`** — 5 regression tests, mutation-verified | +| Approval post-condition in `approveAndTransferIfAllowed` | NM-17 | ~5 lines + 1 error, ×2 variants | ✅ **Done in `v0.6.0`** — 5 regression tests, mutation-verified | +| ERC-165 guard on wrapper children | NM-18 | `_checkRule` override + sub-interface | ✅ **Done in `v0.6.0`** — requires only the one selector the wrapper calls | +| Normalise `spender == from` on the ERC-7943 overloads | NM-6 | 1 helper + 3 branches | ✅ **Done in `v0.6.0`** — 1 file, corrects one rule, changes no deny-list outcome | +| Polarity marker interface + guard | NM-20 | 1 interface + 2 checks | ✅ **Done in `v0.6.0`** — makes allow/deny expressible; also closes a second wrong-child class | +| `staticcall` + length check on the cap reads | NM-23/24 | 8 `try` blocks, 3 files | 🚫 **Declined** — trades a known idiom for hand-rolled ABI plumbing to improve an error message on an already-broken token | +| Opt-in caller binding on the cap rules | NM-5 | 1 slot + setter, ×3 | **Partial only** — cannot isolate two tokens behind one engine; document and monitor instead for now | + +**Status.** The triage itself modified no contract; the seven fixes recorded above were made afterwards through +the normal fix workflow and are described in each finding's `Resolution` block. Two improvements were specified +and then **declined with reasons recorded** (**NM-23/24**, **NM-5**) rather than left as open TODOs, and one +decision is outstanding rather than blocked on effort: +whether an ERC-3643 agent's `forcedTransfer` should be exempt from `RuleMaxBalance`, which is what a variant of +that rule waits on. **No finding is left open**: 7 fixed, 16 accepted as design, 1 declined. diff --git a/doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0.pdf b/doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0.pdf new file mode 100644 index 00000000..3ef612dd Binary files /dev/null and b/doc/security/audits/tools/v0.5.0/nethermind_audit_agent_report_v0.5.0.pdf differ diff --git a/doc/security/audits/tools/v0.6.0/CLAUDE_ANALYSIS.md b/doc/security/audits/tools/v0.6.0/CLAUDE_ANALYSIS.md new file mode 100644 index 00000000..56e4d074 --- /dev/null +++ b/doc/security/audits/tools/v0.6.0/CLAUDE_ANALYSIS.md @@ -0,0 +1,251 @@ +# Rules `v0.6.0` — Code Quality Review + +Scope: production contracts under `src/` (mocks excluded from the metrics, included where a finding concerns +them). Compiler solc `0.8.36`, EVM `prague`. Reviewed **2026-08-18** against the `v0.6.0` tree. +Produced with Claude Code. + +**Nothing in this report is a vulnerability.** Nothing found here lets an unauthorised party move value, bypass +a restriction, or brick a contract. It is a quality review: convention drift, documentation that outgrew its +code, and one structural inconsistency. Where a check passed, that is recorded too — a "keep this as it is" +verdict is a result, not an absence of one. + +This review deliberately concentrates on **code added in `v0.6.0`**, since `v0.5.0`'s review +(`CLAUDE_ANALYSIS.md`, 28 findings) covered the pre-existing surface and its dispositions still hold. + +## Disposition summary + +| ID | Finding | Outcome | +|---|---|---| +| A-1 | External calls inside a loop in the wrapper's new `_checkRule` | ⬜ Left — bounded, configuration-only, and the point of the guard | +| B-1 | No repeated storage reads in the new code | ✅ Checked, nothing to do | +| C-1 | `RuleIdentityRegistryBase` writes + emits inline in 4 places; every sibling rule uses a `_setX` helper | ⬜ **Decide** — actionable, with a trap; see the entry | +| D-1 | The notification-seam NatSpec was byte-identical in 3 files, 25 lines each | ✅ Fixed — shortened to 13, derivation already in the docs | +| E-1 | Two `internal` functions missing `virtual`, against the project's own convention | ✅ Fixed + regression test | +| F-1 | Interface IDs and ERC-165 advertisement | ✅ Checked, correct | +| G-1 | 7 NatSpec blocks over the project's stated 20-line ceiling, all added this release | ✅ Fixed — max block now 19 | +| G-2 | No documentation-path pointers in production contracts | ✅ Checked, convention holds | +| H-1 | `CapAccounting` members both used; no dead code introduced | ✅ Checked | +| I-1 | The wrapper requires exactly the one function it calls | ✅ Checked, correct by construction | + +**7 checked-and-correct · 3 fixed · 1 left · 1 to decide.** (Rows counted, not estimated.) + +## Outstanding + +| ID | Item | Why it is still open | +|---|---|---| +| C-1 | Extract `_setIdentityRegistry` / `_setCheckSender` / `_setCheckSpender` | Needs a decision: the constructor and the setter have *different* zero-address semantics, so a naive extraction changes behaviour | + +--- + +## A. Loops and iteration + +### A-1. `RuleWhitelistWrapperBase._checkRule` makes external calls reachable from a loop — leave + +`_checkRule` now performs two `ERC165Checker.supportsInterface` staticcalls plus one `isAllowList()` call. It is +reached from `setRules`, which loops over the submitted array, so Slither reports `calls-loop`. + +**Verdict: leave.** The cost is bounded by `maxRules` (default 10, and `setRules` rejects a longer array), it is +paid once at configuration by `RULES_MANAGEMENT_ROLE`, and **no holder pays it on a transfer**. Validating each +candidate requires calling each candidate; hoisting the calls out of the loop would mean not validating them, +which is the finding the guard exists to close. Recorded so it is not re-opened. + +`++i` was checked and is already correct throughout; the pragma is `^0.8.20` and the project compiles at 0.8.36, +where the bounded-loop overflow check is elided automatically — `unchecked { ++i }` would buy nothing and is +correctly absent. + +## B. Storage reads + +### B-1. Nothing to hoist in the new code — checked + +`CapAccounting` is `pure` throughout and declares no storage. `_checkRule` reads no storage. The new +`_detectTransferRestrictionOnNotify` overrides delegate immediately. The cap managers' existing single-read +pattern (`uint256 cap = maxBalance;`) is unchanged. + +No finding. Recorded because "we looked" is worth more than silence. + +## C. Events + +### C-1. `RuleIdentityRegistryBase` is the only configurable rule that writes and emits inline — decide + +`identityRegistry`, `checkSender` and `checkSpender` are each written in more than one place, and every write +site carries its own `emit`: + +| Field | Write sites | Emits | +|---|---|---| +| `identityRegistry` | constructor `:61`, `setIdentityRegistry:103`, `clearIdentityRegistry:132` | 3, all inline | +| `checkSender` | constructor `:64`, `setCheckSender:114` | 2, all inline | +| `checkSpender` | constructor `:65`, `setCheckSpender:124` | 2, all inline | + +So "every write emits" is held **by convention rather than structurally**: nothing forces the next person adding +a write path to emit, and nothing forces them to validate. + +**The evidence that this is the exception, not the style, is the siblings.** Nine `_setX` helpers already exist +across five contracts, each owning validation + write + event: + +``` +RuleWhitelistShared._setCheckSpender / _setAllowMintBurn +BalanceCapManager._setMaxBalance / _setBalanceToken +TotalSupplyCapManager._setMaxTotalSupply / _setTokenContract +ChainlinkPoRFeedManager._setReservesFeed / _setTokenMetadata / _setMaxStalenessSeconds +``` + +`RuleWhitelistShared` already has a `_setCheckSpender(bool)` — the **same field name and type** that +`RuleIdentityRegistryBase` writes inline. That names the helper and settles what the house style is. + +**The trap, and why this is a decision rather than a fix.** The constructor and the setter have deliberately +*different* zero-address semantics: + +- `setIdentityRegistry(address(0))` **reverts** (`RuleIdentityRegistry_RegistryAddressZeroNotAllowed`). +- The constructor treats `address(0)` as "leave unset, emit nothing" — that is how a rule is deployed with checks + disabled. +- `clearIdentityRegistry()` writes `address(0)` **and emits**. + +A naive `_setIdentityRegistry` that hoists the `require` would make the three-argument constructor revert on the +documented "no registry" deployment, and would break `clearIdentityRegistry`. The extraction is still worth +doing — moving validation into the helper is the *feature*, because it then guards every path — but the helper +has to model three cases, not one. Suggested shape: `_setIdentityRegistry(address, bool allowZero)`, or a +separate `_clearIdentityRegistry()`. + +**Verdict: decide.** Real inconsistency with a real payoff, but it changes constructor behaviour if done +carelessly, and `RuleIdentityRegistry` is on the ERC-3643 identity path. Not folded into this release. + +## D. Duplication + +### D-1. The notification-seam NatSpec was byte-identical across three files — fixed + +`_detectTransferRestrictionOnNotify` carried a **25-line** NatSpec block in `RuleChainlinkPoRBase`, +`RuleMaxTotalSupplyBase` and `RuleMaxBalanceBase`. All three hashed identically: 75 lines of documentation, one +copy of the information. + +**Fixed.** Shortened to 13 lines each, keeping the two things a reader of the source must have — *this is the +seam an ERC-3643 variant overrides*, and *the read path is deliberately not routed through it* — and dropping +the worked example and the failure narrative, which `RULE_SEMANTICS.md` §5 already carries in full. No +cross-reference was added in either direction, per the project's convention. + +The four deployment variants (`…ERC3643`, `…ERC3643Ownable2Step`) are near-identical to their pairs, differing +only in the base they extend. That is the established house pattern for every rule in the library, so it is +**not** reported as duplication. + +## E. `virtual` / override convention + +### E-1. Two `internal` functions missing `virtual` — fixed + +`CLAUDE.md`: *"All `internal` functions should be marked `virtual`."* Two did not comply: + +- `RuleAddressSetInternal._requireNotZeroAddress` (`:64`) +- `RuleERC2980Internal._requireNotZeroAddress` (`:142`) + +Both are the batch zero-address guard, and both are **passed to `AddressSetBatchLib.addBatch` as an internal +function pointer** — which is also why Slither's `dead-code` detector reports them as unused (it does not trace +function pointers). Two findings about the same two lines. + +**Verified before changing, not assumed:** + +| Question | Method | Result | +|---|---|---| +| Is `virtual` legal on a function used as a pointer? | compile | yes | +| Does dispatch actually reach an override *through the pointer*? | harness that overrides it and reverts | **yes** — override reached | +| Does it cost gas? | `--gas-report`, same test, toggled in place | **identical**: `addAddress` 92 220, `addAddresses` 140 637 both ways | + +The middle row is the one that mattered. Solidity resolves an internal function pointer at the point of +assignment, so it was not obvious the override would be the implementation `addBatch` ends up calling. It is — +but a compile-only check would have passed either way and left the guard *looking* extensible while the base +implementation kept running. + +**Fixed**, with `test/VirtualHooks/BatchGuardPointerVirtual.t.sol` pinning both properties. Removing `virtual` +fails the build with *"Trying to override non-virtual function"* — confirmed by mutation, so the guard is not a +test that has never failed. + +## F. ERC / specification conformance + +### F-1. Interface IDs and advertisement — checked, correct + +- `IADDRESS_LIST_INTERFACE_ID` (`0x5d10e182`) is still computed from the flattened helper interface, and is + **unchanged** despite two selectors being factored into parent interfaces this release — the flattened set did + not move, which is the property that matters and is asserted in `AddressListInterfaceId.t.sol`. +- The two new ids are single-function interfaces that inherit nothing, so stating them as literals is safe: + `IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID` = `0x20e8e17a`, `IADDRESS_LIST_POLARITY_INTERFACE_ID` = `0xdc4efe10`. + Both are asserted equal to `type(I…).interfaceId` and to the function selector. +- Every implementer advertises the new ids — the step most often missed when splitting an interface, and the one + that would otherwise make the new guard reject contracts that were previously fine. +- `address(0)` sentinel handling is unchanged and still correct: it can never enter an address set, and + `isVerified(address(0))` is `false`. + +## G. Code / documentation mismatch + +### G-1. Seven NatSpec blocks over the project's own ceiling — fixed + +`CLAUDE.md` states the ceiling plainly: *"Keep NatSpec blocks short — 20 lines is the ceiling."* Measured across +`src/` excluding mocks, **824 blocks**: + +| | Before | After | +|---|---|---| +| median | 4 | 4 | +| p90 | 9 | 9 | +| max | **26** | **19** | +| blocks ≥ 20 lines | **7** | **0** | + +All seven were added in this release — the four ERC-3643 variant headers (26 lines each) and the three seam +blocks from D-1 (25 each). Against a median of 4, a 26-line contract header is not thorough documentation; it is +a document that happens to live in a comment, and it is the first thing a reader of the contract meets. + +**Fixed.** Each keeps its conclusion and its warning — *ERC-3643 only*, *picking the wrong variant breaks the cap +silently and nothing reverts*, *only the write path is re-phased* — and drops the worked tables and derivations, +which `doc/technical/contracts/RuleChainlinkPoRERC3643.md`, `RuleMaxTotalSupplyERC3643.md` and +`RULE_SEMANTICS.md` §5–§6 already carry. Nothing was deleted outright; it was moved to where it already existed. + +### G-2. No documentation-path pointers in production contracts — checked, convention holds + +`CLAUDE.md` forbids citing a `doc/technical/**` page from contract source, because documentation moves and +deployed verified source cannot be edited to follow it. Grepping `src/` for `.md`, `doc/` and `docs/` outside +`src/mocks/` returns **nothing**, and the only remaining citations are audit reports by bare filename +(`CLAUDE_AUDIT.md`, `CLAUDE_ANALYSIS.md`), which the convention explicitly permits — they are immutable records +and the bare filename survives a move. + +Worth stating so a future reviewer does not propose removing those: **the audit-report citations are correct and +must stay.** + +## H. Weird behaviour + +### H-1. No dead or vestigial code introduced — checked + +Both `CapAccounting` members are used (`_capExceededBy` 4 references, `_capHeadroom` 2). The new +`_detectTransferRestrictionOnNotify` is called from both write hooks in each of the three cap rules, and +`RuleChainlinkPoRBase` is at 100% function coverage — which is the empirical refutation of Slither's `dead-code` +report on it, triaged separately in `slither-report-feedback.md`. + +No fail-open/fail-closed inconsistency was found in the new code: the cap rules fail closed, the wrapper guard +fails closed (absence of a polarity declaration is a refusal), and the `approveAndTransferIfAllowed` +post-condition fails closed. + +## I. Interface granularity + +### I-1. The wrapper requires exactly what it calls — checked, correct + +`RuleWhitelistWrapperBase` calls one function on its children, `areAddressesListed`, and its ERC-165 guard +requires `IAddressListBatchQuery` — that one selector — rather than the eight-selector `IAddressList`. Requiring +the full id would demand four write functions a read-only child has no reason to expose. + +The polarity check is a **separate** interface deliberately, and the limit is stated honestly in both the source +and the docs: ERC-165 expresses shape, never semantics, so the guard cannot distinguish an allow-list from a +deny-list by interface alone — hence `IAddressListPolarity` carrying the answer explicitly, and +`RuleSpenderWhitelist` declining to implement it because its set is spenders rather than holders. + +No finding. This check is recorded because the correct outcome here is easy to mistake for an omission. + +--- + +## What was measured, and what was reasoned + +Measured: NatSpec block distribution (824 blocks, before and after); gas for E-1 (`--gas-report`, same harness +toggled in place); the E-1 override-dispatch behaviour (executing harness); the mutation check that E-1's test +fails without the fix; storage layouts before and after. + +Reasoned without executing: A-1's bound (read from `maxRules` and `setRules`), and C-1's proposed helper shape, +which is a design sketch rather than an implemented change. + +## Verification + +`forge fmt --check` clean. **882 tests pass** on the default profile (881 + the new E-1 regression) and **53** on +`FOUNDRY_PROFILE=erc3643`. Storage layouts unchanged for every affected contract — E-1 and G-1 touch only a +keyword and comments. diff --git a/doc/security/audits/tools/v0.6.0/aderyn-report-feedback.md b/doc/security/audits/tools/v0.6.0/aderyn-report-feedback.md new file mode 100644 index 00000000..20f95e2c --- /dev/null +++ b/doc/security/audits/tools/v0.6.0/aderyn-report-feedback.md @@ -0,0 +1,109 @@ +# Aderyn `v0.6.0` — triage + +```bash +aderyn -x mocks --output doc/security/audits/tools/v0.6.0/aderyn-report.md +``` + +Tool: **Aderyn 0.6.5** · Compiler: solc `0.8.36` · Run date: **2026-08-21** (re-run after the RuleEngine +`v3.0.0-rc6` bump; supersedes the 2026-08-18 run) +Scope: production contracts only, mocks excluded via `-x mocks`. 94 source files, 87 detectors. **4 145 nSLOC.** +**0 High · 9 Low categories, 346 instances.** + +**Executive triage: nothing to fix.** Aderyn reports no High or Medium finding. Every Low category is by design, +environmental or cosmetic, and **no new category appeared** in a release that added five production contracts and +two interfaces. + +### Scope check + +`lib/`, `test/` and `src/mocks/` citations are all **0**. Aderyn reads the Foundry config and scopes to `src/` +by itself, so it needs no equivalent of Slither's `--filter-paths`; `-x mocks` is the only exclusion. + +## Summary + +| ID | Finding | Instances | Disposition | Why | +|---|---|---|---|---| +| L-1 | Centralization Risk | 80 | **By design** | The regulated-issuer model. Roles gating configuration *are* the product; the trust model is documented in `CLAUDE_AUDIT.md` | +| L-2 | Unspecific Solidity Pragma | 92 | **By design** | `^0.8.20` is deliberate — this is a library consumed by projects that pin their own compiler | +| L-3 | Address State Variable Set Without Checks | 3 | **False positive** | Each setter validates: non-zero, `code.length != 0`, and a probe call that must not revert | +| L-4 | Literal Instead of Constant | 2 | Cosmetic | — | +| L-5 | PUSH0 Opcode | 94 | **Environment** | solc `0.8.36` targeting `prague`. Relevant only to a chain without PUSH0, which this library does not target | +| L-6 | Modifier Invoked Only Once | 1 | **By design** | The template-method access-control hook: one modifier per capability, by construction | +| L-7 | Empty Block | 70 | **By design** | Mostly `_authorize*()` overrides whose entire body is the `onlyRole(...)` / `onlyOwner` modifier — an empty body is the idiom, not an oversight — plus intentional no-op hooks (`RuleSpenderWhitelistBase._transferred`) | +| L-8 | Costly operations inside loop | 3 | **By design** | Bounded batch operations over an operator-supplied array | +| L-9 | Unchecked Return | 1 | **False positive** | A configuration probe: the call is made to learn whether it reverts, so discarding the value is the point | + +## Delta from `v0.5.0` + +**336 → 346 instances (+10)** on **3 942 → 4 145 nSLOC (+203)**. Categories unchanged at 9 — none added, none +removed. + +| ID | v0.5.0 | v0.6.0 | Δ | +|---|---|---|---| +| L-2 Unspecific Solidity Pragma | 87 | 92 | **+5** | +| L-5 PUSH0 Opcode | 89 | 94 | **+5** | +| L-1, L-3, L-4, L-6, L-7, L-8, L-9 | 160 | 160 | — | + +**The delta is exactly the new files, once each in the two per-file categories.** This release added seven files +under `src/`, of which two are mock harnesses excluded by `-x mocks`, leaving **five production files**: + +- `CapAccounting.sol` +- `RuleChainlinkPoRERC3643.sol` / `RuleChainlinkPoRERC3643Ownable2Step.sol` +- `RuleMaxTotalSupplyERC3643.sol` / `RuleMaxTotalSupplyERC3643Ownable2Step.sol` + +5 files × (1 pragma + 1 PUSH0) = +10. Nothing else moved. + +That is a stronger result than the raw number suggests, and worth stating explicitly: + +- **`L-1 Centralization Risk` did not grow (80 → 80)** even though four new deployable contracts landed. The + ERC-3643 variants subclass the existing deployables and override one `internal` hook, adding **no new + privileged external function**. The centralisation surface is unchanged. +- **`L-7 Empty Block` did not grow (70 → 70)**. The new contracts' `_detectTransferRestrictionOnNotify` + overrides have real bodies, and no new `_authorize*()` hook was introduced. +- **`L-9 Unchecked Return` did not grow**, despite `RuleWhitelistWrapperBase._checkRule` gaining two + `ERC165Checker.supportsInterface` calls and one `isAllowList()` — all three are consumed by a `require`. + +## Re-run within `v0.6.0` (2026-08-18 → 2026-08-21) + +**No detector moved.** All 9 categories hold their exact instance counts, so the summary table above is +unchanged. Two commits landed between the runs: + +- `c1ebe57` — trimmed NatSpec to the 20-line ceiling and marked two pointer-passed guards `virtual`. +- `f920b07` — RuleEngine `v3.0.0-rc6`: `onlyComplianceManager` renamed to `onlyTokenBindingManager`, + `_authorizeComplianceBindingChange` renamed to `_authorizeTokenBindingChange`, and the redundant + `RuleConditionalTransferLightMultiTokenBase` binding-authorization override deleted. + +The entire body diff is line numbers plus four renamed `L-7` snippets, which is the expected shape: renaming a +hook cannot change how many empty blocks exist, and the deleted override was **not** an empty block — it had a +body — so `L-7` correctly stays at 70. nSLOC moved **4 146 → 4 145**: −3 for the deleted override, −12 for +`forge fmt` re-flowing two multi-line signatures onto one line, +14 for two added imports and two signatures +that `forge fmt` expanded the other way. + +Worth stating for the same reason as the `v0.5.0` delta below: + +- **`L-1 Centralization Risk` did not grow (80 → 80).** The rename touched two access-control hooks and one + modifier; no privileged external function was added, removed or re-gated. That the count is stable is the + cheap confirmation that a rename really was a rename. +- **`L-6 Modifier Invoked Only Once` did not grow (1 → 1)**, and still points at + `RuleWhitelistShared.onlyCheckSpenderManager`. `onlyTokenBindingManager` is invoked four times in + `RuleConditionalTransferLightBase` alone, so it correctly does not appear. + +## Notes on the two large categories + +`L-2` and `L-5` together are **186 of 346 instances (54%)**, and both are one-per-file: + +- **`L-2 Unspecific Solidity Pragma`** — Aderyn wants a pinned pragma. For an application this is good advice; + for a **library** it is not. Every contract here is `^0.8.20` so consumers can compile against their own + pinned version. Pinning would force downstream projects onto this repo's exact compiler. +- **`L-5 PUSH0 Opcode`** — flags that bytecode from solc ≥ 0.8.20 contains `PUSH0`, which pre-Shanghai chains + reject. `foundry.toml` targets `prague`; the deployment targets are all post-Shanghai. If that ever changes, + this becomes a real finding — it is environmental, not wrong. + +Both will grow by one per file on every future release. **Treat a jump that is not a multiple of the new-file +count as the signal**, not the totals themselves. + +## What a clean report does and does not mean + +Aderyn's detectors matched nothing actionable. As with Slither, that is not evidence of correctness: the seven +findings fixed in this release came from the Nethermind AuditAgent scan and manual review, and **neither static +analyser reached any of them**. They are semantic — accounting phase, callback ordering, interface polarity — +and these tools match syntactic patterns. diff --git a/doc/security/audits/tools/v0.6.0/aderyn-report.md b/doc/security/audits/tools/v0.6.0/aderyn-report.md new file mode 100644 index 00000000..c895af9c --- /dev/null +++ b/doc/security/audits/tools/v0.6.0/aderyn-report.md @@ -0,0 +1,2359 @@ +# Aderyn Report — `v0.6.0` + +```bash +aderyn -x mocks --output doc/security/audits/tools/v0.6.0/aderyn-report.md +``` + +Tool: **Aderyn 0.6.5** · Compiler: solc `0.8.36` · Run date: **2026-08-21** (re-run after the RuleEngine +`v3.0.0-rc6` bump; supersedes the 2026-08-18 run) +Scope: production contracts only — **mocks excluded** via `-x mocks`. 94 source files, 87 detectors, +**4 145 nSLOC**. + +**0 High · 9 Low categories, 346 instances.** + +| ID | Finding | Severity | Instances | Assessment | +|---|---|---|---|---| +| L-1 | Centralization Risk | Low | 80 | By design — the regulated-issuer model; roles are the feature | +| L-2 | Unspecific Solidity Pragma | Low | 92 | By design — `^0.8.20` is deliberate for a library consumed by other projects | +| L-3 | Address State Variable Set Without Checks | Low | 3 | False positive — each setter validates (non-zero, has code, probe call) | +| L-4 | Literal Instead of Constant | Low | 2 | Cosmetic | +| L-5 | PUSH0 Opcode | Low | 94 | Environment — solc `0.8.36` targeting `prague`; irrelevant on any chain this deploys to | +| L-6 | Modifier Invoked Only Once | Low | 1 | By design — the template-method access-control hook | +| L-7 | Empty Block | Low | 70 | By design — `_authorize*()` overrides carrying `onlyRole(...)` / `onlyOwner`, and intentional no-op hooks | +| L-8 | Costly operations inside loop | Low | 3 | By design — bounded batch operations | +| L-9 | Unchecked Return | Low | 1 | False positive — the discarded value is the point of a configuration probe | + +**Nothing to fix.** No High or Medium finding, and every Low category is by design, environmental or cosmetic. +The delta from `v0.5.0` is **+10 instances** (336 → 346) on **+203 nSLOC**, entirely the five source files added +this release appearing once each in `L-2` and `L-5`. **No new category, and no existing category grew for any +other reason.** + +**Re-run delta (2026-08-18 → 2026-08-21): no detector moved.** Two commits landed in between — the NatSpec +trim and `virtual` fixes, and the RuleEngine `v3.0.0-rc6` bump that renamed `onlyComplianceManager` to +`onlyTokenBindingManager`, renamed `_authorizeComplianceBindingChange` to `_authorizeTokenBindingChange` and +deleted the multi-token binding-authorization override. All 9 categories hold their exact instance counts; +the only changes in the body are line numbers and the four renamed `L-7` snippets. nSLOC moved **4 146 → 4 145**, +the one line of the deleted override net of `forge fmt` re-flowing four signatures. + +Triage: [`aderyn-report-feedback.md`](./aderyn-report-feedback.md) · +Overview: [`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: Address State Variable Set Without Checks](#l-3-address-state-variable-set-without-checks) + - [L-4: Literal Instead of Constant](#l-4-literal-instead-of-constant) + - [L-5: PUSH0 Opcode](#l-5-push0-opcode) + - [L-6: Modifier Invoked Only Once](#l-6-modifier-invoked-only-once) + - [L-7: Empty Block](#l-7-empty-block) + - [L-8: Costly operations inside loop](#l-8-costly-operations-inside-loop) + - [L-9: Unchecked Return](#l-9-unchecked-return) + + +# Summary + +## Files Summary + +| Key | Value | +| --- | --- | +| .sol Files | 94 | +| Total nSLOC | 4145 | + + +## Files Details + +| Filepath | nSLOC | +| --- | --- | +| src/modules/AccessControlModuleStandalone.sol | 24 | +| src/modules/MetaTxModuleStandalone.sol | 6 | +| src/modules/Ownable2StepERC165Module.sol | 11 | +| src/modules/VersionModule.sol | 8 | +| src/registry/IdentityRegistryWhitelist.sol | 7 | +| src/registry/abstract/IdentityRegistryWhitelistBase.sol | 52 | +| src/registry/abstract/IdentityRegistryWhitelistInvariantStorage.sol | 6 | +| src/registry/interfaces/IIdentityRegistryERC3643.sol | 10 | +| src/rules/interfaces/AggregatorV3Interface.sol | 14 | +| src/rules/interfaces/IAddressList.sol | 20 | +| src/rules/interfaces/IBalanceOf.sol | 4 | +| src/rules/interfaces/IDecimals.sol | 4 | +| src/rules/interfaces/IERC2980.sol | 5 | +| src/rules/interfaces/IERC7943NonFungibleCompliance.sol | 19 | +| src/rules/interfaces/IIdentityRegistry.sol | 7 | +| src/rules/interfaces/ISanctionsList.sol | 4 | +| src/rules/interfaces/ITotalSupply.sol | 4 | +| src/rules/interfaces/ITransferContext.sol | 22 | +| src/rules/interfaces/library/AddressListInterfaceId.sol | 6 | +| src/rules/operation/RuleConditionalTransferLight.sol | 34 | +| src/rules/operation/RuleConditionalTransferLightMultiToken.sol | 33 | +| src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol | 32 | +| src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol | 33 | +| src/rules/operation/RuleMintAllowance.sol | 28 | +| src/rules/operation/RuleMintAllowanceOwnable2Step.sol | 27 | +| src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol | 70 | +| src/rules/operation/abstract/RuleConditionalTransferLightBase.sol | 156 | +| src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol | 25 | +| src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol | 244 | +| src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol | 30 | +| src/rules/operation/abstract/RuleMintAllowanceBase.sol | 149 | +| src/rules/operation/abstract/RuleMintAllowanceInvariantStorage.sol | 14 | +| src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol | 31 | +| src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol | 67 | +| src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol | 38 | +| src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetInvariantStorage.sol | 6 | +| src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetRolesStorage.sol | 5 | +| src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol | 14 | +| src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol | 24 | +| src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol | 65 | +| src/rules/validation/abstract/RuleERC2980/invariantStorage/RuleERC2980InvariantStorage.sol | 39 | +| src/rules/validation/abstract/base/RuleBlacklistBase.sol | 115 | +| src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol | 107 | +| src/rules/validation/abstract/base/RuleERC2980Base.sol | 247 | +| src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol | 125 | +| src/rules/validation/abstract/base/RuleMaxBalanceBase.sol | 92 | +| src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol | 87 | +| src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol | 76 | +| src/rules/validation/abstract/base/RuleSanctionsListBase.sol | 108 | +| src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol | 59 | +| src/rules/validation/abstract/base/RuleWhitelistBase.sol | 75 | +| src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol | 163 | +| src/rules/validation/abstract/core/BalanceCapManager.sol | 97 | +| src/rules/validation/abstract/core/CapAccounting.sol | 12 | +| src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol | 112 | +| src/rules/validation/abstract/core/RuleNFTAdapter.sol | 127 | +| src/rules/validation/abstract/core/RuleTransferValidation.sol | 70 | +| src/rules/validation/abstract/core/RuleWhitelistShared.sol | 96 | +| src/rules/validation/abstract/core/TokenSupplyReader.sol | 19 | +| src/rules/validation/abstract/core/TotalSupplyCapManager.sol | 45 | +| src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol | 32 | +| src/rules/validation/abstract/invariant/RuleIdentityRegistryInvariantStorage.sol | 18 | +| src/rules/validation/abstract/invariant/RuleMaxBalanceInvariantStorage.sol | 22 | +| src/rules/validation/abstract/invariant/RuleMaxTotalSupplyInvariantStorage.sol | 17 | +| src/rules/validation/abstract/invariant/RuleReceiverWhitelistInvariantStorage.sol | 12 | +| src/rules/validation/abstract/invariant/RuleSanctionsListInvariantStorage.sol | 18 | +| src/rules/validation/abstract/invariant/RuleSharedInvariantStorage.sol | 4 | +| src/rules/validation/abstract/invariant/RuleSpenderWhitelistInvariantStorage.sol | 9 | +| src/rules/validation/deployment/RuleBlacklist.sol | 33 | +| src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol | 31 | +| src/rules/validation/deployment/RuleChainlinkPoR.sol | 29 | +| src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol | 25 | +| src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol | 25 | +| src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol | 27 | +| src/rules/validation/deployment/RuleERC2980.sol | 34 | +| src/rules/validation/deployment/RuleERC2980Ownable2Step.sol | 35 | +| src/rules/validation/deployment/RuleIdentityRegistry.sol | 22 | +| src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol | 23 | +| src/rules/validation/deployment/RuleMaxBalance.sol | 22 | +| src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol | 23 | +| src/rules/validation/deployment/RuleMaxTotalSupply.sol | 22 | +| src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol | 20 | +| src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol | 20 | +| src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol | 23 | +| src/rules/validation/deployment/RuleReceiverWhitelist.sol | 33 | +| src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol | 34 | +| src/rules/validation/deployment/RuleSanctionsList.sol | 34 | +| src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol | 35 | +| src/rules/validation/deployment/RuleSpenderWhitelist.sol | 33 | +| src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol | 34 | +| src/rules/validation/deployment/RuleWhitelist.sol | 36 | +| src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol | 36 | +| src/rules/validation/deployment/RuleWhitelistWrapper.sol | 54 | +| src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol | 41 | +| **Total** | **4145** | + + +## Issue Summary + +| Category | No. of Issues | +| --- | --- | +| High | 0 | +| Low | 9 | + + +# 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. + +
80 Found Instances + + +- Found in src/modules/AccessControlModuleStandalone.sol [Line: 13](../../../../../src/modules/AccessControlModuleStandalone.sol#L13) + + ```solidity + abstract contract AccessControlModuleStandalone is AccessControlEnumerable { + ``` + +- Found in src/registry/IdentityRegistryWhitelist.sol [Line: 34](../../../../../src/registry/IdentityRegistryWhitelist.sol#L34) + + ```solidity + function _authorizeIdentityRegistrar() internal view virtual override onlyRole(IDENTITY_REGISTRAR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 62](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L62) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 67](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L67) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 72](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L72) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiToken.sol [Line: 50](../../../../../src/rules/operation/RuleConditionalTransferLightMultiToken.sol#L50) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiToken.sol [Line: 55](../../../../../src/rules/operation/RuleConditionalTransferLightMultiToken.sol#L55) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 22](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L22) + + ```solidity + Ownable2Step, + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 49](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L49) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 54](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L54) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 21](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L21) + + ```solidity + Ownable2Step, + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 60](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L60) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 65](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L65) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 70](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L70) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 60](../../../../../src/rules/operation/RuleMintAllowance.sol#L60) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 65](../../../../../src/rules/operation/RuleMintAllowance.sol#L65) + + ```solidity + function _authorizeSetMintAllowance() internal view virtual override onlyRole(ALLOWANCE_OPERATOR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 70](../../../../../src/rules/operation/RuleMintAllowance.sol#L70) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 19](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L19) + + ```solidity + contract RuleMintAllowanceOwnable2Step is RuleMintAllowanceBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 57](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L57) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 62](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L62) + + ```solidity + function _authorizeSetMintAllowance() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 67](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L67) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklist.sol [Line: 55](../../../../../src/rules/validation/deployment/RuleBlacklist.sol#L55) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklist.sol [Line: 60](../../../../../src/rules/validation/deployment/RuleBlacklist.sol#L60) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 15](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L15) + + ```solidity + contract RuleBlacklistOwnable2Step is RuleBlacklistBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 54](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L54) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 59](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L59) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoR.sol [Line: 65](../../../../../src/rules/validation/deployment/RuleChainlinkPoR.sol#L65) + + ```solidity + function _authorizeChainlinkPoRManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol [Line: 15](../../../../../src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol#L15) + + ```solidity + contract RuleChainlinkPoROwnable2Step is RuleChainlinkPoRBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol [Line: 62](../../../../../src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol#L62) + + ```solidity + function _authorizeChainlinkPoRManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L63) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 68](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L68) + + ```solidity + function _authorizeWhitelistAdd() internal view virtual override onlyRole(WHITELIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 73](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L73) + + ```solidity + function _authorizeWhitelistRemove() internal view virtual override onlyRole(WHITELIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 78](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L78) + + ```solidity + function _authorizeFrozenlistAdd() internal view virtual override onlyRole(FROZENLIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 83](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L83) + + ```solidity + function _authorizeFrozenlistRemove() internal view virtual override onlyRole(FROZENLIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 15](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L15) + + ```solidity + contract RuleERC2980Ownable2Step is RuleERC2980Base, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 56](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L56) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 61](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L61) + + ```solidity + function _authorizeWhitelistAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 66](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L66) + + ```solidity + function _authorizeWhitelistRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 71](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L71) + + ```solidity + function _authorizeFrozenlistAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 76](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L76) + + ```solidity + function _authorizeFrozenlistRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistry.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleIdentityRegistry.sol#L63) + + ```solidity + function _authorizeIdentityRegistryManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol [Line: 14](../../../../../src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L14) + + ```solidity + contract RuleIdentityRegistryOwnable2Step is RuleIdentityRegistryBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L63) + + ```solidity + function _authorizeIdentityRegistryManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalance.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleMaxBalance.sol#L58) + + ```solidity + function _authorizeMaxBalanceManager() internal view virtual override onlyRole(MAX_BALANCE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol [Line: 16](../../../../../src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol#L16) + + ```solidity + contract RuleMaxBalanceOwnable2Step is RuleMaxBalanceBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol [Line: 59](../../../../../src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol#L59) + + ```solidity + function _authorizeMaxBalanceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupply.sol [Line: 55](../../../../../src/rules/validation/deployment/RuleMaxTotalSupply.sol#L55) + + ```solidity + function _authorizeMaxTotalSupplyManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol [Line: 14](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol#L14) + + ```solidity + contract RuleMaxTotalSupplyOwnable2Step is RuleMaxTotalSupplyBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol [Line: 57](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol#L57) + + ```solidity + function _authorizeMaxTotalSupplyManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelist.sol [Line: 56](../../../../../src/rules/validation/deployment/RuleReceiverWhitelist.sol#L56) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelist.sol [Line: 61](../../../../../src/rules/validation/deployment/RuleReceiverWhitelist.sol#L61) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 15](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L15) + + ```solidity + contract RuleReceiverWhitelistOwnable2Step is RuleReceiverWhitelistBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 57](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L57) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 62](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L62) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsList.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleSanctionsList.sol#L58) + + ```solidity + function _authorizeSanctionListManager() internal view virtual override onlyRole(SANCTIONLIST_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol [Line: 17](../../../../../src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol#L17) + + ```solidity + contract RuleSanctionsListOwnable2Step is RuleSanctionsListBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol [Line: 60](../../../../../src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol#L60) + + ```solidity + function _authorizeSanctionListManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelist.sol [Line: 56](../../../../../src/rules/validation/deployment/RuleSpenderWhitelist.sol#L56) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelist.sol [Line: 61](../../../../../src/rules/validation/deployment/RuleSpenderWhitelist.sol#L61) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 15](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L15) + + ```solidity + contract RuleSpenderWhitelistOwnable2Step is RuleSpenderWhitelistBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 57](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L57) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 62](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L62) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 65](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L65) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 70](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L70) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 75](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L75) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 80](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L80) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 15](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L15) + + ```solidity + contract RuleWhitelistOwnable2Step is RuleWhitelistBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L58) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L63) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 68](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L68) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 73](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L73) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 98](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L98) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 103](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L103) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 109](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L109) + + ```solidity + function _onlyRulesManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 114](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L114) + + ```solidity + function _onlyRulesLimitManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 16](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L16) + + ```solidity + contract RuleWhitelistWrapperOwnable2Step is RuleWhitelistWrapperBase, Ownable2Step, Ownable2StepERC165Module { + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L58) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L63) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 69](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L69) + + ```solidity + function _onlyRulesManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 74](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L74) + + ```solidity + function _onlyRulesLimitManager() internal view 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;` + +
92 Found Instances + + +- Found in src/modules/AccessControlModuleStandalone.sol [Line: 3](../../../../../src/modules/AccessControlModuleStandalone.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/MetaTxModuleStandalone.sol [Line: 3](../../../../../src/modules/MetaTxModuleStandalone.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/Ownable2StepERC165Module.sol [Line: 2](../../../../../src/modules/Ownable2StepERC165Module.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/VersionModule.sol [Line: 2](../../../../../src/modules/VersionModule.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/IdentityRegistryWhitelist.sol [Line: 2](../../../../../src/registry/IdentityRegistryWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/abstract/IdentityRegistryWhitelistBase.sol [Line: 2](../../../../../src/registry/abstract/IdentityRegistryWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/abstract/IdentityRegistryWhitelistInvariantStorage.sol [Line: 2](../../../../../src/registry/abstract/IdentityRegistryWhitelistInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/interfaces/IIdentityRegistryERC3643.sol [Line: 2](../../../../../src/registry/interfaces/IIdentityRegistryERC3643.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/AggregatorV3Interface.sol [Line: 2](../../../../../src/rules/interfaces/AggregatorV3Interface.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IAddressList.sol [Line: 2](../../../../../src/rules/interfaces/IAddressList.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IBalanceOf.sol [Line: 2](../../../../../src/rules/interfaces/IBalanceOf.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IDecimals.sol [Line: 2](../../../../../src/rules/interfaces/IDecimals.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IERC2980.sol [Line: 2](../../../../../src/rules/interfaces/IERC2980.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IERC7943NonFungibleCompliance.sol [Line: 3](../../../../../src/rules/interfaces/IERC7943NonFungibleCompliance.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IIdentityRegistry.sol [Line: 3](../../../../../src/rules/interfaces/IIdentityRegistry.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/ISanctionsList.sol [Line: 3](../../../../../src/rules/interfaces/ISanctionsList.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/ITotalSupply.sol [Line: 2](../../../../../src/rules/interfaces/ITotalSupply.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/ITransferContext.sol [Line: 2](../../../../../src/rules/interfaces/ITransferContext.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiToken.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLightMultiToken.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 2](../../../../../src/rules/operation/RuleMintAllowance.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 2](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleMintAllowanceBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceInvariantStorage.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleMintAllowanceInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetRolesStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetRolesStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleERC2980/invariantStorage/RuleERC2980InvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleERC2980/invariantStorage/RuleERC2980InvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleBlacklistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleBlacklistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleERC2980Base.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleERC2980Base.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleMaxBalanceBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleMaxBalanceBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleSanctionsListBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleSanctionsListBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleWhitelistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol [Line: 3](../../../../../src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/BalanceCapManager.sol [Line: 2](../../../../../src/rules/validation/abstract/core/BalanceCapManager.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/CapAccounting.sol [Line: 2](../../../../../src/rules/validation/abstract/core/CapAccounting.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol [Line: 2](../../../../../src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/RuleNFTAdapter.sol [Line: 2](../../../../../src/rules/validation/abstract/core/RuleNFTAdapter.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/RuleTransferValidation.sol [Line: 3](../../../../../src/rules/validation/abstract/core/RuleTransferValidation.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/RuleWhitelistShared.sol [Line: 2](../../../../../src/rules/validation/abstract/core/RuleWhitelistShared.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/TokenSupplyReader.sol [Line: 2](../../../../../src/rules/validation/abstract/core/TokenSupplyReader.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/TotalSupplyCapManager.sol [Line: 2](../../../../../src/rules/validation/abstract/core/TotalSupplyCapManager.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleIdentityRegistryInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleIdentityRegistryInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleMaxBalanceInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleMaxBalanceInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleMaxTotalSupplyInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleMaxTotalSupplyInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleReceiverWhitelistInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleReceiverWhitelistInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleSanctionsListInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/invariant/RuleSanctionsListInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleSharedInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleSharedInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleSpenderWhitelistInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleSpenderWhitelistInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleBlacklist.sol [Line: 3](../../../../../src/rules/validation/deployment/RuleBlacklist.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoR.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoR.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistry.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleIdentityRegistry.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalance.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxBalance.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupply.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupply.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelist.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleReceiverWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsList.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSanctionsList.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelist.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSpenderWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 3](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 3](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +
+ + + +## L-3: Address State Variable Set Without Checks + +Check for `address(0)` when assigning values to address state variables. + +
3 Found Instances + + +- Found in src/rules/validation/abstract/base/RuleSanctionsListBase.sol [Line: 125](../../../../../src/rules/validation/abstract/base/RuleSanctionsListBase.sol#L125) + + ```solidity + sanctionsList = sanctionContractOracle_; + ``` + +- Found in src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol [Line: 137](../../../../../src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L137) + + ```solidity + reservesFeed = newReservesFeed; + ``` + +- Found in src/rules/validation/abstract/core/TotalSupplyCapManager.sol [Line: 85](../../../../../src/rules/validation/abstract/core/TotalSupplyCapManager.sol#L85) + + ```solidity + tokenContract = ITotalSupply(newTokenContract); + ``` + +
+ + + +## L-4: Literal Instead of Constant + +Define and use `constant` variables instead of using literals. If the same constant literal value is used multiple times, create a constant state variable and reference it throughout the contract. + +
2 Found Instances + + +- Found in src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol [Line: 256](../../../../../src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L256) + + ```solidity + uint256 factor = 10 ** uint256(to - from); + ``` + +- Found in src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol [Line: 263](../../../../../src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L263) + + ```solidity + return answer / (10 ** uint256(from - to)); + ``` + +
+ + + +## L-5: 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. + +
94 Found Instances + + +- Found in src/modules/AccessControlModuleStandalone.sol [Line: 3](../../../../../src/modules/AccessControlModuleStandalone.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/MetaTxModuleStandalone.sol [Line: 3](../../../../../src/modules/MetaTxModuleStandalone.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/Ownable2StepERC165Module.sol [Line: 2](../../../../../src/modules/Ownable2StepERC165Module.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/VersionModule.sol [Line: 2](../../../../../src/modules/VersionModule.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/IdentityRegistryWhitelist.sol [Line: 2](../../../../../src/registry/IdentityRegistryWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/abstract/IdentityRegistryWhitelistBase.sol [Line: 2](../../../../../src/registry/abstract/IdentityRegistryWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/abstract/IdentityRegistryWhitelistInvariantStorage.sol [Line: 2](../../../../../src/registry/abstract/IdentityRegistryWhitelistInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/registry/interfaces/IIdentityRegistryERC3643.sol [Line: 2](../../../../../src/registry/interfaces/IIdentityRegistryERC3643.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/AggregatorV3Interface.sol [Line: 2](../../../../../src/rules/interfaces/AggregatorV3Interface.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IAddressList.sol [Line: 2](../../../../../src/rules/interfaces/IAddressList.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IBalanceOf.sol [Line: 2](../../../../../src/rules/interfaces/IBalanceOf.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IDecimals.sol [Line: 2](../../../../../src/rules/interfaces/IDecimals.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IERC2980.sol [Line: 2](../../../../../src/rules/interfaces/IERC2980.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IERC7943NonFungibleCompliance.sol [Line: 3](../../../../../src/rules/interfaces/IERC7943NonFungibleCompliance.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/IIdentityRegistry.sol [Line: 3](../../../../../src/rules/interfaces/IIdentityRegistry.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/ISanctionsList.sol [Line: 3](../../../../../src/rules/interfaces/ISanctionsList.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/ITotalSupply.sol [Line: 2](../../../../../src/rules/interfaces/ITotalSupply.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/ITransferContext.sol [Line: 2](../../../../../src/rules/interfaces/ITransferContext.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/interfaces/library/AddressListInterfaceId.sol [Line: 3](../../../../../src/rules/interfaces/library/AddressListInterfaceId.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiToken.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLightMultiToken.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 2](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 2](../../../../../src/rules/operation/RuleMintAllowance.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 2](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceBase.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleMintAllowanceBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceInvariantStorage.sol [Line: 2](../../../../../src/rules/operation/abstract/RuleMintAllowanceInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleAddressSet/RuleAddressSet.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetRolesStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleAddressSetRolesStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/RuleERC2980/invariantStorage/RuleERC2980InvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/RuleERC2980/invariantStorage/RuleERC2980InvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleBlacklistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleBlacklistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleERC2980Base.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleERC2980Base.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleMaxBalanceBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleMaxBalanceBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleSanctionsListBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleSanctionsListBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleWhitelistBase.sol [Line: 2](../../../../../src/rules/validation/abstract/base/RuleWhitelistBase.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol [Line: 3](../../../../../src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/BalanceCapManager.sol [Line: 2](../../../../../src/rules/validation/abstract/core/BalanceCapManager.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/CapAccounting.sol [Line: 2](../../../../../src/rules/validation/abstract/core/CapAccounting.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol [Line: 2](../../../../../src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/RuleNFTAdapter.sol [Line: 2](../../../../../src/rules/validation/abstract/core/RuleNFTAdapter.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/RuleTransferValidation.sol [Line: 3](../../../../../src/rules/validation/abstract/core/RuleTransferValidation.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/RuleWhitelistShared.sol [Line: 2](../../../../../src/rules/validation/abstract/core/RuleWhitelistShared.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/TokenSupplyReader.sol [Line: 2](../../../../../src/rules/validation/abstract/core/TokenSupplyReader.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/core/TotalSupplyCapManager.sol [Line: 2](../../../../../src/rules/validation/abstract/core/TotalSupplyCapManager.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleIdentityRegistryInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleIdentityRegistryInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleMaxBalanceInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleMaxBalanceInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleMaxTotalSupplyInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleMaxTotalSupplyInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleReceiverWhitelistInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleReceiverWhitelistInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleSanctionsListInvariantStorage.sol [Line: 3](../../../../../src/rules/validation/abstract/invariant/RuleSanctionsListInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleSharedInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleSharedInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/abstract/invariant/RuleSpenderWhitelistInvariantStorage.sol [Line: 2](../../../../../src/rules/validation/abstract/invariant/RuleSpenderWhitelistInvariantStorage.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleBlacklist.sol [Line: 3](../../../../../src/rules/validation/deployment/RuleBlacklist.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoR.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoR.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistry.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleIdentityRegistry.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalance.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxBalance.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupply.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupply.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelist.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleReceiverWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsList.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSanctionsList.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelist.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSpenderWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 2](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 3](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 3](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +
+ + + +## L-6: Modifier Invoked Only Once + +Consider removing the modifier or inlining the logic into the calling function. + +
1 Found Instances + + +- Found in src/rules/validation/abstract/core/RuleWhitelistShared.sol [Line: 51](../../../../../src/rules/validation/abstract/core/RuleWhitelistShared.sol#L51) + + ```solidity + modifier onlyCheckSpenderManager() { + ``` + +
+ + + +## L-7: Empty Block + +Consider removing empty blocks. + +
70 Found Instances + + +- Found in src/registry/IdentityRegistryWhitelist.sol [Line: 34](../../../../../src/registry/IdentityRegistryWhitelist.sol#L34) + + ```solidity + function _authorizeIdentityRegistrar() internal view virtual override onlyRole(IDENTITY_REGISTRAR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 62](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L62) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 67](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L67) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLight.sol [Line: 72](../../../../../src/rules/operation/RuleConditionalTransferLight.sol#L72) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiToken.sol [Line: 50](../../../../../src/rules/operation/RuleConditionalTransferLightMultiToken.sol#L50) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiToken.sol [Line: 55](../../../../../src/rules/operation/RuleConditionalTransferLightMultiToken.sol#L55) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyRole(OPERATOR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 49](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L49) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol [Line: 54](../../../../../src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol#L54) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 60](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L60) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 65](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L65) + + ```solidity + function _authorizeTransferApproval() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol [Line: 70](../../../../../src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol#L70) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 60](../../../../../src/rules/operation/RuleMintAllowance.sol#L60) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 65](../../../../../src/rules/operation/RuleMintAllowance.sol#L65) + + ```solidity + function _authorizeSetMintAllowance() internal view virtual override onlyRole(ALLOWANCE_OPERATOR_ROLE) {} + ``` + +- Found in src/rules/operation/RuleMintAllowance.sol [Line: 70](../../../../../src/rules/operation/RuleMintAllowance.sol#L70) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 57](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L57) + + ```solidity + function _onlyComplianceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 62](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L62) + + ```solidity + function _authorizeSetMintAllowance() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/RuleMintAllowanceOwnable2Step.sol [Line: 67](../../../../../src/rules/operation/RuleMintAllowanceOwnable2Step.sol#L67) + + ```solidity + function _authorizeTokenBindingChange(address) internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceBase.sol [Line: 65](../../../../../src/rules/operation/abstract/RuleMintAllowanceBase.sol#L65) + + ```solidity + function created(address, uint256) external virtual override onlyBoundToken {} + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceBase.sol [Line: 70](../../../../../src/rules/operation/abstract/RuleMintAllowanceBase.sol#L70) + + ```solidity + function destroyed(address, uint256) external virtual override onlyBoundToken {} + ``` + +- Found in src/rules/operation/abstract/RuleMintAllowanceBase.sol [Line: 265](../../../../../src/rules/operation/abstract/RuleMintAllowanceBase.sol#L265) + + ```solidity + function _transferred(address, address, uint256) internal virtual { + ``` + +- Found in src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol [Line: 56](../../../../../src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol#L56) + + ```solidity + function transferred(address, address, uint256) public view override(IERC3643IComplianceContract) {} + ``` + +- Found in src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol [Line: 128](../../../../../src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol#L128) + + ```solidity + function _transferred(address, address, uint256) internal view virtual override { + ``` + +- Found in src/rules/validation/deployment/RuleBlacklist.sol [Line: 55](../../../../../src/rules/validation/deployment/RuleBlacklist.sol#L55) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklist.sol [Line: 60](../../../../../src/rules/validation/deployment/RuleBlacklist.sol#L60) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 54](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L54) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol [Line: 59](../../../../../src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol#L59) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoR.sol [Line: 65](../../../../../src/rules/validation/deployment/RuleChainlinkPoR.sol#L65) + + ```solidity + function _authorizeChainlinkPoRManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol [Line: 62](../../../../../src/rules/validation/deployment/RuleChainlinkPoROwnable2Step.sol#L62) + + ```solidity + function _authorizeChainlinkPoRManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L63) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 68](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L68) + + ```solidity + function _authorizeWhitelistAdd() internal view virtual override onlyRole(WHITELIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 73](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L73) + + ```solidity + function _authorizeWhitelistRemove() internal view virtual override onlyRole(WHITELIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 78](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L78) + + ```solidity + function _authorizeFrozenlistAdd() internal view virtual override onlyRole(FROZENLIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980.sol [Line: 83](../../../../../src/rules/validation/deployment/RuleERC2980.sol#L83) + + ```solidity + function _authorizeFrozenlistRemove() internal view virtual override onlyRole(FROZENLIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 56](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L56) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 61](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L61) + + ```solidity + function _authorizeWhitelistAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 66](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L66) + + ```solidity + function _authorizeWhitelistRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 71](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L71) + + ```solidity + function _authorizeFrozenlistAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleERC2980Ownable2Step.sol [Line: 76](../../../../../src/rules/validation/deployment/RuleERC2980Ownable2Step.sol#L76) + + ```solidity + function _authorizeFrozenlistRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistry.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleIdentityRegistry.sol#L63) + + ```solidity + function _authorizeIdentityRegistryManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L63) + + ```solidity + function _authorizeIdentityRegistryManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalance.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleMaxBalance.sol#L58) + + ```solidity + function _authorizeMaxBalanceManager() internal view virtual override onlyRole(MAX_BALANCE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol [Line: 59](../../../../../src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol#L59) + + ```solidity + function _authorizeMaxBalanceManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupply.sol [Line: 55](../../../../../src/rules/validation/deployment/RuleMaxTotalSupply.sol#L55) + + ```solidity + function _authorizeMaxTotalSupplyManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol [Line: 57](../../../../../src/rules/validation/deployment/RuleMaxTotalSupplyOwnable2Step.sol#L57) + + ```solidity + function _authorizeMaxTotalSupplyManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelist.sol [Line: 56](../../../../../src/rules/validation/deployment/RuleReceiverWhitelist.sol#L56) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelist.sol [Line: 61](../../../../../src/rules/validation/deployment/RuleReceiverWhitelist.sol#L61) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 57](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L57) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol [Line: 62](../../../../../src/rules/validation/deployment/RuleReceiverWhitelistOwnable2Step.sol#L62) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsList.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleSanctionsList.sol#L58) + + ```solidity + function _authorizeSanctionListManager() internal view virtual override onlyRole(SANCTIONLIST_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol [Line: 60](../../../../../src/rules/validation/deployment/RuleSanctionsListOwnable2Step.sol#L60) + + ```solidity + function _authorizeSanctionListManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelist.sol [Line: 56](../../../../../src/rules/validation/deployment/RuleSpenderWhitelist.sol#L56) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelist.sol [Line: 61](../../../../../src/rules/validation/deployment/RuleSpenderWhitelist.sol#L61) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 57](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L57) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol [Line: 62](../../../../../src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol#L62) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 65](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L65) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 70](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L70) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 75](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L75) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyRole(ADDRESS_LIST_ADD_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelist.sol [Line: 80](../../../../../src/rules/validation/deployment/RuleWhitelist.sol#L80) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyRole(ADDRESS_LIST_REMOVE_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L58) + + ```solidity + function _authorizeAddressListAdd() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L63) + + ```solidity + function _authorizeAddressListRemove() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 68](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L68) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol [Line: 73](../../../../../src/rules/validation/deployment/RuleWhitelistOwnable2Step.sol#L73) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 98](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L98) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 103](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L103) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 109](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L109) + + ```solidity + function _onlyRulesManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapper.sol [Line: 114](../../../../../src/rules/validation/deployment/RuleWhitelistWrapper.sol#L114) + + ```solidity + function _onlyRulesLimitManager() internal view virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 58](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L58) + + ```solidity + function _authorizeCheckSpenderManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 63](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L63) + + ```solidity + function _authorizeMintBurnManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 69](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L69) + + ```solidity + function _onlyRulesManager() internal view virtual override onlyOwner {} + ``` + +- Found in src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol [Line: 74](../../../../../src/rules/validation/deployment/RuleWhitelistWrapperOwnable2Step.sol#L74) + + ```solidity + function _onlyRulesLimitManager() internal view virtual override onlyOwner {} + ``` + +
+ + + +## L-8: Costly operations inside loop + +Invoking `SSTORE` operations in loops may waste gas. Use a local variable to hold the loop computation result. + +
3 Found Instances + + +- Found in src/rules/operation/abstract/RuleMintAllowanceBase.sol [Line: 127](../../../../../src/rules/operation/abstract/RuleMintAllowanceBase.sol#L127) + + ```solidity + for (uint256 i = 0; i < minters.length; ++i) { + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol [Line: 44](../../../../../src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol#L44) + + ```solidity + for (uint256 i = 0; i < addressesToAdd.length; ++i) { + ``` + +- Found in src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol [Line: 67](../../../../../src/rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol#L67) + + ```solidity + for (uint256 i = 0; i < addressesToRemove.length; ++i) { + ``` + +
+ + + +## L-9: Unchecked Return + +Function returns a value but it is ignored. Consider checking the return value. + +
1 Found Instances + + +- Found in src/modules/AccessControlModuleStandalone.sol [Line: 35](../../../../../src/modules/AccessControlModuleStandalone.sol#L35) + + ```solidity + _grantRole(DEFAULT_ADMIN_ROLE, admin); + ``` + +
+ + + diff --git a/doc/security/audits/tools/v0.6.0/slither-report-feedback.md b/doc/security/audits/tools/v0.6.0/slither-report-feedback.md new file mode 100644 index 00000000..738811b5 --- /dev/null +++ b/doc/security/audits/tools/v0.6.0/slither-report-feedback.md @@ -0,0 +1,162 @@ +# Slither `v0.6.0` — triage + +```bash +slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks" \ + > doc/security/audits/tools/v0.6.0/slither-report.md +``` + +Tool: **Slither 0.11.5** · Compiler: solc `0.8.36` · Run date: **2026-08-21** (re-run after the RuleEngine +`v3.0.0-rc6` bump; supersedes the 2026-08-18 run) +Scope: production contracts only. Mocks excluded via the `mocks` filter, vendored dependencies via `lib`. +225 contracts, 101 detectors, **46 results**. + +**Executive triage: nothing to fix.** No finding is exploitable. Both High-impact results are the same +false positive dismissed in `v0.4.0` and `v0.5.0`, on a permissioned path. The two results new since `v0.5.0` +were each opened against the source and dismissed; one of them is worth reading, because the natural reaction to +it would be to delete working code. + +### Scope check + +Both assertions pass — the filter matched, so nothing outside the project is in scope: + +``` +grep -c 'lib/\|node_modules/' slither-report.md → 0 +grep -c 'test/\|src/mocks/' slither-report.md → 0 +``` + +The filter list must name **`lib`**: this is a Foundry project, and an entry that matches nothing fails open, +pulling the whole vendored dependency tree in. A previous run with a generic `submodules` filter returned 170 +results, 351 of them citing `lib/openzeppelin-contracts/`. + +## Summary + +| Detector | Impact | Instances | Disposition | +|---|---|---|---| +| `arbitrary-send-erc20` | High | 2 | **False positive** | +| `uninitialized-local` | Medium | 2 | False positive | +| `unused-return` | Medium | 9 | False positive | +| `calls-loop` | Low | 17 | By design | +| `timestamp` | Low | 1 | By design | +| `assembly` | Informational | 2 | By design | +| `dead-code` | Informational | 3 | False positive | +| `naming-convention` | Informational | 6 | By design | +| `unused-state` | Informational | 4 | Cosmetic | + +## Delta from `v0.5.0` + +**44 → 46 results (+2).** Every other detector is unchanged, instance for instance. + +| Detector | v0.5.0 | v0.6.0 | Δ | +|---|---|---|---| +| `calls-loop` | 16 | 17 | **+1** | +| `dead-code` | 2 | 3 | **+1** | +| *(all others)* | 42 | 42 | — | + +A delta this small on a release that added five production contracts is the expected shape. Both new results +are in code added for the Nethermind AuditAgent fixes. + +### +1 `calls-loop` — `RuleWhitelistWrapperBase._checkRule` + +> `_checkRule(address)` has external calls inside a loop: `require(IAddressListPolarity(rule_).isAllowList(), …)` + +**By design.** This is the NM-20 polarity guard. Slither reaches it through `setRules`, which loops over the +submitted array calling `_addRule` → `_checkRule`, so each candidate costs two `ERC165Checker` staticcalls plus +one `isAllowList()`. That is: + +- **bounded** — `maxRules` defaults to 10 and `setRules` rejects an array longer than it; +- **configuration-time only** — `RULES_MANAGEMENT_ROLE`, never a transfer path, so no holder pays for it; +- **the point of the guard** — checking a child's interface and polarity requires calling the child. + +The alternative (validating outside the loop) would mean not validating each candidate, which is the finding +NM-18 and NM-20 exist to close. + +### +1 `dead-code` — `RuleChainlinkPoRBase._detectTransferRestrictionOnNotify` + +> `_detectTransferRestrictionOnNotify(address,address,uint256)` is never used and should be removed + +**False positive, and acting on it would break the ERC-3643 Proof-of-Reserve variant.** The function is the +notification-phase seam added for NM-11; `RuleChainlinkPoRERC3643` exists solely to override it. Three +independent confirmations that it is live: + +1. **It is called twice in the same file** — `RuleChainlinkPoRBase.sol:202` and `:217`, from `_transferred` and + `_transferredFrom`. +2. **Coverage is 100% of functions** on `RuleChainlinkPoRBase` (10/10). An unreachable function cannot be + executed by the test suite. +3. **`testStockRuleRevertsAFullyBackedMint` and `testMintUpToTheReservesSucceeds`** (real-T-REX suite) differ + *only* by which override of this hook is installed. If the seam were dead, both would behave identically and + the pair would fail. + +Slither's `dead-code` is unreliable for `internal virtual` functions reached through inheritance: the same +detector already produced the two pre-existing hits below, both dismissed on the same grounds. Note also its +inconsistency — `RuleMaxTotalSupplyBase` has the byte-identical seam, overridden by +`RuleMaxTotalSupplyERC3643`, and is **not** flagged. + +The two pre-existing instances are unchanged: `RuleERC2980Internal._requireNotZeroAddress` and +`RuleAddressSetInternal._requireNotZeroAddress`, both internal guards reached from the public layer. + +## Re-run within `v0.6.0` (2026-08-18 → 2026-08-21) + +**No detector moved.** All nine hold their exact result counts, so the summary table above is unchanged. The +entire body diff is **line numbers in three files** — `RuleConditionalTransferLightBase`, +`RuleConditionalTransferLightMultiTokenBase` and `RuleChainlinkPoRBase`. Two commits landed between the runs: + +- `c1ebe57` — trimmed NatSpec to the 20-line ceiling and marked two pointer-passed guards `virtual`. This is + what moved `RuleChainlinkPoRBase`, a file the rc6 bump never touched, and it means the 2026-08-18 report was + **already one commit stale when it was committed**. +- `f920b07` — RuleEngine `v3.0.0-rc6`: two access-control hooks renamed and one redundant override deleted. + +### Contract count 221 → 225 — not this repository's code + +Slither walks the full inheritance graph, including the vendored dependencies it then filters out of the +*results*. `v3.0.0-rc6` split the token-binding registry out of `ERC3643ComplianceModule`, adding +`TokenBindingModule`, `TokenBindingExtendedModule`, `ITokenBinding`, `ITokenBindingExtended` and +`TokenBindingModuleInvariantStorage`, and removing `ERC3643ComplianceModuleInvariantStorage` — net **+4**. +Every one of them is under `lib/` and contributes zero results. This is the one number in the report that +changed without a corresponding change in `src/`, and it is worth naming explicitly so a future reader does not +read it as scope creep. + +### What did *not* move, and why that is the useful check + +- **`dead-code` stayed at 3.** Deleting `RuleConditionalTransferLightMultiTokenBase._authorizeComplianceBindingChange` + removed an `internal` function that Slither did *not* consider dead — it was reached through the + `bindToken` / `unbindToken` path. Had the count dropped to 2, that would have meant the deletion removed a + live authorization check rather than a redundant one. +- **`arbitrary-send-erc20` stayed at 2**, still on `approveAndTransferIfAllowed` in both conditional-transfer + bases. The rename changed which modifier gates `bindToken`, not the gating of the transfer helper. +- **`unused-return` stayed at 9.** `_bindToken` still consumes the `EnumerableSet.add` return value through a + `require`; the registry moved upstream, the call site did not change. + +### Scope check re-verified + +`grep -c 'lib/\|node_modules/'` on the fresh report is **0**. + +## Findings carried over from `v0.5.0` + +Unchanged in count and disposition; verified again against the source this run. + +- **`arbitrary-send-erc20` (High, 2)** — `approveAndTransferIfAllowed` in the light and multi-token conditional + rules. Gated by `onlyTransferApprover`, a recorded approval, an explicit allowance check and a bound token. + This release **tightened** the path further (NM-17: the helper now reverts unless the approval it created was + consumed), so the detector's premise is weaker than before, not stronger. +- **`uninitialized-local` (Medium, 2)** — variables assigned inside a `try` whose `catch` returns or reverts. +- **`unused-return` (Medium, 9)** — `EnumerableSet` add/remove return values deliberately discarded by the batch + helpers, and the configuration probes (`totalSupply()`, `balanceOf()`, `decimals()`) whose discarded value is + precisely the point: the call is made to learn whether it reverts. +- **`calls-loop` (Low, 16 of 17)** — the wrapper's child scan. Bounded by `maxRules`, measured at ~8.8k gas per + child, and documented with operator guidance. +- **`timestamp` (Low, 1)** — the Proof-of-Reserve staleness comparison. The feature is a freshness check; it + cannot be written without reading `block.timestamp`. +- **`assembly` (Informational, 2)** — the `_transferHash` preimage, whose exact layout is documented and pinned + by `testDocumentedPreimageMatchesTheStorageKey`. +- **`naming-convention` (Informational, 6)** — parameter names matching the ERC text they implement. +- **`unused-state` (Informational, 4)** — the four `TRANSFERRED_SELECTOR_*` constants in `RuleNFTAdapter`. Still + genuinely unreferenced, as corrected in the `v0.5.0` triage (they had previously been dismissed as a false + positive, wrongly). Impact is nil — `internal constant`, so no storage and nothing emitted into bytecode — so + the disposition stays cosmetic rather than a fix. + +## What a clean report does and does not mean + +Slither's pattern set matched nothing actionable. That is not evidence of correctness: every substantive issue +addressed in this release came from the Nethermind AuditAgent scan and from manual review, and **not one of the +seven fixed findings was reachable by either static analyser** — they are semantic (who calls a hook, in what +order, with which arguments), and Slither and Aderyn match syntax. diff --git a/doc/security/audits/tools/v0.6.0/slither-report.md b/doc/security/audits/tools/v0.6.0/slither-report.md new file mode 100644 index 00000000..9575b48d --- /dev/null +++ b/doc/security/audits/tools/v0.6.0/slither-report.md @@ -0,0 +1,451 @@ +# Slither Report — `v0.6.0` + +```bash +slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks" \ + > doc/security/audits/tools/v0.6.0/slither-report.md +``` + +Tool: **Slither 0.11.5** · Compiler: solc `0.8.36` · Run date: **2026-08-21** (re-run after the RuleEngine +`v3.0.0-rc6` bump; supersedes the 2026-08-18 run) +Scope: production contracts only — **mocks excluded**, vendored dependencies excluded via the `lib` filter. +225 contracts, 101 detectors, **46 results**. + +**0 High\* · 2 High-impact (both false positives) · 11 Medium · 18 Low · 15 Informational.** + +| Detector | Impact | Instances | Assessment | +|---|---|---|---| +| `arbitrary-send-erc20` | High | 2 | **False positive** — `approveAndTransferIfAllowed` is gated by `onlyTransferApprover`, a recorded approval, an allowance check and a bound token | +| `uninitialized-local` | Medium | 2 | False positive — assigned inside a `try` whose `catch` returns or reverts | +| `unused-return` | Medium | 9 | False positive — `EnumerableSet` return values deliberately discarded, or configuration probes whose discarded value is the point | +| `calls-loop` | Low | 17 | By design — the wrapper's bounded child scan and its `_checkRule` guard | +| `timestamp` | Low | 1 | By design — the Proof-of-Reserve staleness comparison *is* the feature | +| `assembly` | Informational | 2 | By design — the documented `_transferHash` preimage | +| `dead-code` | Informational | 3 | False positive — all three are reachable; see the feedback file | +| `naming-convention` | Informational | 6 | By design — spec-aligned parameter names | +| `unused-state` | Informational | 4 | Cosmetic — the four `TRANSFERRED_SELECTOR_*` constants are genuinely unreferenced | + +\* Slither has no "High severity" column as such; the two `arbitrary-send-erc20` results carry High *impact* +and are verified false positives. + +**Nothing to fix.** No finding is exploitable. The delta from `v0.5.0` is **+2** (44 → 46), both traceable to +code added in this release and both dismissed against the source. + +**Re-run delta (2026-08-18 → 2026-08-21): no detector moved.** Every one of the nine detectors holds its exact +result count; the entire diff is **line numbers in three files** (`RuleConditionalTransferLightBase`, +`RuleConditionalTransferLightMultiTokenBase`, `RuleChainlinkPoRBase`). The contract count rose **221 → 225**, +which is not this repository's code: RuleEngine `v3.0.0-rc6` split the binding registry out of +`ERC3643ComplianceModule`, adding `TokenBindingModule`, `TokenBindingExtendedModule`, `ITokenBinding`, +`ITokenBindingExtended` and `TokenBindingModuleInvariantStorage` while removing +`ERC3643ComplianceModuleInvariantStorage` — net +4 contracts in the inheritance graph Slither walks, all of +them filtered out of the results by the `lib` path filter. + +Triage: [`slither-report-feedback.md`](./slither-report-feedback.md) · +Overview: [`AUDIT_OVERVIEW.md`](../../AUDIT_OVERVIEW.md) + +--- + +**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results. +Summary + - [arbitrary-send-erc20](#arbitrary-send-erc20) (2 results) (High) + - [uninitialized-local](#uninitialized-local) (2 results) (Medium) + - [unused-return](#unused-return) (9 results) (Medium) + - [calls-loop](#calls-loop) (17 results) (Low) + - [timestamp](#timestamp) (1 results) (Low) + - [assembly](#assembly) (2 results) (Informational) + - [dead-code](#dead-code) (3 results) (Informational) + - [naming-convention](#naming-convention) (6 results) (Informational) + - [unused-state](#unused-state) (4 results) (Informational) +## arbitrary-send-erc20 +Impact: High +Confidence: High + - [ ] ID-0 +[RuleConditionalTransferLightBase.approveAndTransferIfAllowed(address,address,uint256)](src/rules/operation/abstract/RuleConditionalTransferLightBase.sol#L115-L141) uses arbitrary from in transferFrom: [IERC20(token).safeTransferFrom(from,to,value)](src/rules/operation/abstract/RuleConditionalTransferLightBase.sol#L130) + +src/rules/operation/abstract/RuleConditionalTransferLightBase.sol#L115-L141 + + + - [ ] ID-1 +[RuleConditionalTransferLightMultiTokenBase.approveAndTransferIfAllowed(address,address,address,uint256)](src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L131-L157) uses arbitrary from in transferFrom: [IERC20(token).safeTransferFrom(from,to,value)](src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L147) + +src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L131-L157 + + +## uninitialized-local +Impact: Medium +Confidence: Medium + - [ ] ID-2 +[ChainlinkPoRFeedManager._maxBackedSupply().currentFeedDecimals](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L199) is a local variable never initialized + +src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L199 + + + - [ ] ID-3 +[ChainlinkPoRFeedManager._setReservesFeed(AggregatorV3Interface).newFeedDecimals](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L130) is a local variable never initialized + +src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L130 + + +## unused-return +Impact: Medium +Confidence: Medium + - [ ] ID-4 +[RuleERC2980Internal._removeFrozenlistAddresses(address[])](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L110-L116) ignores return value by [_frozenlist.removeBatch(addressesToRemove)](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L115) + +src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L110-L116 + + + - [ ] ID-5 +[BalanceCapManager._setBalanceToken(address)](src/rules/validation/abstract/core/BalanceCapManager.sol#L176-L189) ignores return value by [IBalanceOf(newBalanceToken).balanceOf(address(this))](src/rules/validation/abstract/core/BalanceCapManager.sol#L181-L186) + +src/rules/validation/abstract/core/BalanceCapManager.sol#L176-L189 + + + - [ ] ID-6 +[RuleAddressSetInternal._removeAddresses(address[])](src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L77-L83) ignores return value by [_listedAddresses.removeBatch(addressesToRemove)](src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L82) + +src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L77-L83 + + + - [ ] ID-7 +[RuleERC2980Internal._removeWhitelistAddresses(address[])](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L61-L67) ignores return value by [_whitelist.removeBatch(addressesToRemove)](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L66) + +src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L61-L67 + + + - [ ] ID-8 +[RuleERC2980Internal._addWhitelistAddresses(address[])](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L47-L53) ignores return value by [_whitelist.addBatch(addressesToAdd,_requireNotZeroAddress)](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L52) + +src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L47-L53 + + + - [ ] ID-9 +[RuleERC2980Internal._addFrozenlistAddresses(address[])](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L96-L102) ignores return value by [_frozenlist.addBatch(addressesToAdd,_requireNotZeroAddress)](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L101) + +src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L96-L102 + + + - [ ] ID-10 +[RuleAddressSetInternal._addAddresses(address[])](src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L44-L50) ignores return value by [_listedAddresses.addBatch(addressesToAdd,_requireNotZeroAddress)](src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L49) + +src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L44-L50 + + + - [ ] ID-11 +[TokenSupplyReader._probeTotalSupplyCallable(address)](src/rules/validation/abstract/core/TokenSupplyReader.sol#L65-L71) ignores return value by [ITotalSupply(candidate).totalSupply()](src/rules/validation/abstract/core/TokenSupplyReader.sol#L66-L70) + +src/rules/validation/abstract/core/TokenSupplyReader.sol#L65-L71 + + + - [ ] ID-12 +[ChainlinkPoRFeedManager._maxBackedSupply()](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L195-L231) ignores return value by [(answer,updatedAt) = feed.latestRoundData()](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L210-L230) + +src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L195-L231 + + +## calls-loop +Impact: Low +Confidence: Medium + - [ ] ID-13 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleWhitelistWrapperHarnessInternal.exposedTransferredSpenderInternal(address,address,address,uint256) + RuleWhitelistWrapperBase._transferred(address,address,address,uint256) + RuleWhitelistShared._transferredFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-14 +[RuleWhitelistWrapperBase._checkRule(address)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L243-L256) has external calls inside a loop: [require(bool,error)(IAddressListPolarity(rule_).isAllowList(),revert RuleWhitelistWrapper_ChildIsNotAnAllowList(address)(rule_))](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L255) + Calls stack containing the loop: + RulesManagementModule.setRules(IRule[]) + RulesManagementModule._addRule(IRule) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L243-L256 + + + - [ ] ID-15 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.detectTransferRestriction(address,address,uint256,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-16 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.detectTransferRestrictionFrom(address,address,address,uint256,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-17 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleWhitelistShared.transferred(address,address,uint256) + RuleWhitelistWrapperBase._transferred(address,address,uint256) + RuleWhitelistShared._transferred(address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-18 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.canTransfer(address,address,uint256,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-19 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.transferred(ITransferContext.FungibleTransferContext) + RuleWhitelistShared._transferredFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-20 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleWhitelistWrapperBase.isVerified(address) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-21 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.transferred(ITransferContext.MultiTokenTransferContext) + RuleWhitelistShared._transferredFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-22 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleTransferValidation.detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-23 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.transferred(address,address,address,uint256,uint256) + RuleWhitelistShared._transferredFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-24 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleTransferValidation.canTransferFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-25 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleWhitelistShared.transferred(address,address,address,uint256) + RuleWhitelistShared._transferredFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-26 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.transferred(address,address,uint256,uint256) + RuleWhitelistWrapperBase._transferred(address,address,uint256) + RuleWhitelistShared._transferred(address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-27 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleNFTAdapter.canTransferFrom(address,address,address,uint256,uint256) + RuleNFTAdapter.detectTransferRestrictionFrom(address,address,address,uint256,uint256) + RuleWhitelistWrapperBase._detectTransferRestrictionFrom(address,address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-28 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleTransferValidation.canTransfer(address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + + - [ ] ID-29 +[RuleWhitelistWrapperBase._detectTransferRestrictionForTargets(address[])](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293) has external calls inside a loop: [isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress)](src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L279) + Calls stack containing the loop: + RuleTransferValidation.detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._detectTransferRestriction(address,address,uint256) + RuleWhitelistWrapperBase._isListedInAnyChild(address) + +src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol#L263-L293 + + +## timestamp +Impact: Low +Confidence: Medium + - [ ] ID-30 +[ChainlinkPoRFeedManager._maxBackedSupply()](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L195-L231) uses timestamp for comparisons + Dangerous comparisons: + - [answer < 0 || updatedAt == 0 || updatedAt > block.timestamp](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L216) + - [staleness != 0 && block.timestamp - updatedAt > staleness](src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L221) + +src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol#L195-L231 + + +## assembly +Impact: Informational +Confidence: High + - [ ] ID-31 +[RuleConditionalTransferLightApprovalBase._transferHash(address,address,uint256)](src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol#L163-L174) uses assembly + - [INLINE ASM](src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol#L167-L173) + +src/rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol#L163-L174 + + + - [ ] ID-32 +[RuleConditionalTransferLightMultiTokenBase._transferHash(address,address,address,uint256)](src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L450-L464) uses assembly + - [INLINE ASM](src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L456-L463) + +src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol#L450-L464 + + +## dead-code +Impact: Informational +Confidence: Medium + - [ ] ID-33 +[RuleERC2980Internal._requireNotZeroAddress(address)](src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L142-L144) is never used and should be removed + +src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol#L142-L144 + + + - [ ] ID-34 +[RuleChainlinkPoRBase._detectTransferRestrictionOnNotify(address,address,uint256)](src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol#L175-L182) is never used and should be removed + +src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol#L175-L182 + + + - [ ] ID-35 +[RuleAddressSetInternal._requireNotZeroAddress(address)](src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L64-L66) is never used and should be removed + +src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol#L64-L66 + + +## naming-convention +Impact: Informational +Confidence: High + - [ ] ID-36 +Parameter [RuleERC2980Base.frozenlist(address)._operator](src/rules/validation/abstract/base/RuleERC2980Base.sol#L374) is not in mixedCase + +src/rules/validation/abstract/base/RuleERC2980Base.sol#L374 + + + - [ ] ID-37 +Parameter [IdentityRegistryWhitelistBase.isVerified(address)._userAddress](src/registry/abstract/IdentityRegistryWhitelistBase.sol#L92) is not in mixedCase + +src/registry/abstract/IdentityRegistryWhitelistBase.sol#L92 + + + - [ ] ID-38 +Parameter [IdentityRegistryWhitelistBase.deleteIdentity(address)._userAddress](src/registry/abstract/IdentityRegistryWhitelistBase.sol#L67) is not in mixedCase + +src/registry/abstract/IdentityRegistryWhitelistBase.sol#L67 + + + - [ ] ID-39 +Parameter [RuleERC2980Base.whitelist(address)._operator](src/rules/validation/abstract/base/RuleERC2980Base.sol#L325) is not in mixedCase + +src/rules/validation/abstract/base/RuleERC2980Base.sol#L325 + + + - [ ] ID-40 +Parameter [IdentityRegistryWhitelistBase.registerIdentity(address,address,uint16)._identity](src/registry/abstract/IdentityRegistryWhitelistBase.sol#L48) is not in mixedCase + +src/registry/abstract/IdentityRegistryWhitelistBase.sol#L48 + + + - [ ] ID-41 +Parameter [IdentityRegistryWhitelistBase.registerIdentity(address,address,uint16)._userAddress](src/registry/abstract/IdentityRegistryWhitelistBase.sol#L47) is not in mixedCase + +src/registry/abstract/IdentityRegistryWhitelistBase.sol#L47 + + +## unused-state +Impact: Informational +Confidence: High + - [ ] ID-42 +[RuleNFTAdapter.TRANSFERRED_SELECTOR_RULE_ENGINE](src/rules/validation/abstract/core/RuleNFTAdapter.sol#L37) is never used in [RuleIdentityRegistryOwnable2Step](src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L14-L64) + +src/rules/validation/abstract/core/RuleNFTAdapter.sol#L37 + + + - [ ] ID-43 +[RuleNFTAdapter.TRANSFERRED_SELECTOR_ERC7943](src/rules/validation/abstract/core/RuleNFTAdapter.sol#L41-L42) is never used in [RuleIdentityRegistryOwnable2Step](src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L14-L64) + +src/rules/validation/abstract/core/RuleNFTAdapter.sol#L41-L42 + + + - [ ] ID-44 +[RuleNFTAdapter.TRANSFERRED_SELECTOR_ERC7943_FROM](src/rules/validation/abstract/core/RuleNFTAdapter.sol#L46-L47) is never used in [RuleIdentityRegistryOwnable2Step](src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L14-L64) + +src/rules/validation/abstract/core/RuleNFTAdapter.sol#L46-L47 + + + - [ ] ID-45 +[RuleNFTAdapter.TRANSFERRED_SELECTOR_ERC3643](src/rules/validation/abstract/core/RuleNFTAdapter.sol#L33) is never used in [RuleIdentityRegistryOwnable2Step](src/rules/validation/deployment/RuleIdentityRegistryOwnable2Step.sol#L14-L64) + +src/rules/validation/abstract/core/RuleNFTAdapter.sol#L33 + + diff --git a/doc/surya/surya_graph/surya_graph_BalanceCapManager.sol.png b/doc/surya/surya_graph/surya_graph_BalanceCapManager.sol.png index e3c22d68..62811435 100644 Binary files a/doc/surya/surya_graph/surya_graph_BalanceCapManager.sol.png and b/doc/surya/surya_graph/surya_graph_BalanceCapManager.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_CapAccounting.sol.png b/doc/surya/surya_graph/surya_graph_CapAccounting.sol.png new file mode 100644 index 00000000..c595eda0 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_CapAccounting.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_ERC3643CapHarnesses.sol.png b/doc/surya/surya_graph/surya_graph_ERC3643CapHarnesses.sol.png new file mode 100644 index 00000000..388f3dc1 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_ERC3643CapHarnesses.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_IAddressList.sol.png b/doc/surya/surya_graph/surya_graph_IAddressList.sol.png index 0e08167b..4a4f7b6a 100644 Binary files a/doc/surya/surya_graph/surya_graph_IAddressList.sol.png and b/doc/surya/surya_graph/surya_graph_IAddressList.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_IdentityRegistryDelegationHarness.sol.png b/doc/surya/surya_graph/surya_graph_IdentityRegistryDelegationHarness.sol.png new file mode 100644 index 00000000..8293c469 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_IdentityRegistryDelegationHarness.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleBlacklistBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleBlacklistBase.sol.png index a9c55680..803094cc 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleBlacklistBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleBlacklistBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRBase.sol.png index c298df5c..0d7b5315 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRERC3643.sol.png b/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRERC3643.sol.png new file mode 100644 index 00000000..385ea297 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRERC3643.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRERC3643Ownable2Step.sol.png b/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRERC3643Ownable2Step.sol.png new file mode 100644 index 00000000..27f69cad Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_RuleChainlinkPoRERC3643Ownable2Step.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLight.sol.png b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLight.sol.png index 6319530e..6f6f14ee 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLight.sol.png and b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLight.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightBase.sol.png index 04a9b436..6574b2fd 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiToken.sol.png b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiToken.sol.png index 98c2d188..4b0f6052 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiToken.sol.png and b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiToken.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenBase.sol.png index 0cae2e5a..81c14345 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.png b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.png index 9c84a20b..4a795e7d 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.png and b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightOwnable2Step.sol.png b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightOwnable2Step.sol.png index 6ded68fc..7d8307f6 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightOwnable2Step.sol.png and b/doc/surya/surya_graph/surya_graph_RuleConditionalTransferLightOwnable2Step.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleIdentityRegistryBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleIdentityRegistryBase.sol.png index 746ac67d..1890ef49 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleIdentityRegistryBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleIdentityRegistryBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleMaxBalanceBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleMaxBalanceBase.sol.png index 962f223d..89b3b5e1 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleMaxBalanceBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleMaxBalanceBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyBase.sol.png index 9f4ebebd..b0e2cc6e 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyERC3643.sol.png b/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyERC3643.sol.png new file mode 100644 index 00000000..5510370e Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyERC3643.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyERC3643Ownable2Step.sol.png b/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyERC3643Ownable2Step.sol.png new file mode 100644 index 00000000..b01a2226 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_RuleMaxTotalSupplyERC3643Ownable2Step.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleMintAllowance.sol.png b/doc/surya/surya_graph/surya_graph_RuleMintAllowance.sol.png index 0e88e49a..0d67288e 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleMintAllowance.sol.png and b/doc/surya/surya_graph/surya_graph_RuleMintAllowance.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleMintAllowanceOwnable2Step.sol.png b/doc/surya/surya_graph/surya_graph_RuleMintAllowanceOwnable2Step.sol.png index 2a2ffc65..b2c7c644 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleMintAllowanceOwnable2Step.sol.png and b/doc/surya/surya_graph/surya_graph_RuleMintAllowanceOwnable2Step.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleNFTAdapter.sol.png b/doc/surya/surya_graph/surya_graph_RuleNFTAdapter.sol.png index 68f2165e..e00c3e9b 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleNFTAdapter.sol.png and b/doc/surya/surya_graph/surya_graph_RuleNFTAdapter.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleReceiverWhitelistBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleReceiverWhitelistBase.sol.png index 268f51d6..a50c980b 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleReceiverWhitelistBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleReceiverWhitelistBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleWhitelistBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleWhitelistBase.sol.png index d0514cd8..94331336 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleWhitelistBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleWhitelistBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_RuleWhitelistWrapperBase.sol.png b/doc/surya/surya_graph/surya_graph_RuleWhitelistWrapperBase.sol.png index 26293b2f..abf2fcf4 100644 Binary files a/doc/surya/surya_graph/surya_graph_RuleWhitelistWrapperBase.sol.png and b/doc/surya/surya_graph/surya_graph_RuleWhitelistWrapperBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_TotalSupplyCapManager.sol.png b/doc/surya/surya_graph/surya_graph_TotalSupplyCapManager.sol.png index 163bdd1e..d2c30923 100644 Binary files a/doc/surya/surya_graph/surya_graph_TotalSupplyCapManager.sol.png and b/doc/surya/surya_graph/surya_graph_TotalSupplyCapManager.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_BalanceCapManager.sol.png b/doc/surya/surya_inheritance/surya_inheritance_BalanceCapManager.sol.png index 4d48bc9b..dc3f2b0f 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_BalanceCapManager.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_BalanceCapManager.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_CapAccounting.sol.png b/doc/surya/surya_inheritance/surya_inheritance_CapAccounting.sol.png new file mode 100644 index 00000000..979b6b1f Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_CapAccounting.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_ChainlinkPoRFeedManager.sol.png b/doc/surya/surya_inheritance/surya_inheritance_ChainlinkPoRFeedManager.sol.png index a2c417ec..868ddfdd 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_ChainlinkPoRFeedManager.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_ChainlinkPoRFeedManager.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_ERC3643CapHarnesses.sol.png b/doc/surya/surya_inheritance/surya_inheritance_ERC3643CapHarnesses.sol.png new file mode 100644 index 00000000..ae42f76e Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_ERC3643CapHarnesses.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_IAddressList.sol.png b/doc/surya/surya_inheritance/surya_inheritance_IAddressList.sol.png index 908d7bfe..2c7cc60f 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_IAddressList.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_IAddressList.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_IdentityRegistryDelegationHarness.sol.png b/doc/surya/surya_inheritance/surya_inheritance_IdentityRegistryDelegationHarness.sol.png new file mode 100644 index 00000000..6468a471 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_IdentityRegistryDelegationHarness.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleBlacklistBase.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleBlacklistBase.sol.png index dc3d48a2..790d37c7 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_RuleBlacklistBase.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_RuleBlacklistBase.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleChainlinkPoRERC3643.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleChainlinkPoRERC3643.sol.png new file mode 100644 index 00000000..3ab8f6de Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_RuleChainlinkPoRERC3643.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleChainlinkPoRERC3643Ownable2Step.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleChainlinkPoRERC3643Ownable2Step.sol.png new file mode 100644 index 00000000..0a28ac49 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_RuleChainlinkPoRERC3643Ownable2Step.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleMaxTotalSupplyERC3643.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleMaxTotalSupplyERC3643.sol.png new file mode 100644 index 00000000..ffa18066 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_RuleMaxTotalSupplyERC3643.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleMaxTotalSupplyERC3643Ownable2Step.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleMaxTotalSupplyERC3643Ownable2Step.sol.png new file mode 100644 index 00000000..e4c7d50e Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_RuleMaxTotalSupplyERC3643Ownable2Step.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleReceiverWhitelistBase.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleReceiverWhitelistBase.sol.png index 86ba2abc..953839dd 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_RuleReceiverWhitelistBase.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_RuleReceiverWhitelistBase.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_RuleWhitelistBase.sol.png b/doc/surya/surya_inheritance/surya_inheritance_RuleWhitelistBase.sol.png index 2a2357a6..62dd11be 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_RuleWhitelistBase.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_RuleWhitelistBase.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_TotalSupplyCapManager.sol.png b/doc/surya/surya_inheritance/surya_inheritance_TotalSupplyCapManager.sol.png index 1b655520..f6a4273d 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_TotalSupplyCapManager.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_TotalSupplyCapManager.sol.png differ diff --git a/doc/surya/surya_report/surya_report_AddressListInterfaceId.sol.md b/doc/surya/surya_report/surya_report_AddressListInterfaceId.sol.md index 6c2aa2a1..bb97ea63 100644 --- a/doc/surya/surya_report/surya_report_AddressListInterfaceId.sol.md +++ b/doc/surya/surya_report/surya_report_AddressListInterfaceId.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/interfaces/library/AddressListInterfaceId.sol | 8b08df55a6b20867989fffca8059fb09e3f5c39d | +| ./rules/interfaces/library/AddressListInterfaceId.sol | fbd91ecbc9b1b60315ed8e497dabbf9b0e80f593 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_AddressSetBatchLib.sol.md b/doc/surya/surya_report/surya_report_AddressSetBatchLib.sol.md index 2b63c2aa..e2e2ff3d 100644 --- a/doc/surya/surya_report/surya_report_AddressSetBatchLib.sol.md +++ b/doc/surya/surya_report/surya_report_AddressSetBatchLib.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol | 1b09ddf8af7c9b32fc572b329b1786122a132dad | +| ./rules/validation/abstract/RuleAddressSet/AddressSetBatchLib.sol | 720f435fb0932fd60fc43f50f93cc0e1c2711db6 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_BalanceCapManager.sol.md b/doc/surya/surya_report/surya_report_BalanceCapManager.sol.md index de244d81..605ad41c 100644 --- a/doc/surya/surya_report/surya_report_BalanceCapManager.sol.md +++ b/doc/surya/surya_report/surya_report_BalanceCapManager.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/core/BalanceCapManager.sol | e9fc2e355458aed8576d8aa0c26daaa9b3b88650 | +| ./rules/validation/abstract/core/BalanceCapManager.sol | 8d23cbb921630627842b86c40e4941ae4ef676f6 | ### Contracts Description Table @@ -15,7 +15,7 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **BalanceCapManager** | Implementation | RuleAddressSetInternal, RuleMaxBalanceInvariantStorage ||| +| **BalanceCapManager** | Implementation | CapAccounting, RuleAddressSetInternal, RuleMaxBalanceInvariantStorage ||| | └ | setMaxBalance | Public ❗️ | 🛑 | onlyMaxBalanceManager | | └ | setBalanceToken | Public ❗️ | 🛑 | onlyMaxBalanceManager | | └ | addExemptAddress | Public ❗️ | 🛑 | onlyMaxBalanceManager | diff --git a/doc/surya/surya_report/surya_report_CapAccounting.sol.md b/doc/surya/surya_report/surya_report_CapAccounting.sol.md new file mode 100644 index 00000000..a268e4b8 --- /dev/null +++ b/doc/surya/surya_report/surya_report_CapAccounting.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./rules/validation/abstract/core/CapAccounting.sol | 449269c90e1cf8a1bdcd01a8024cd8c8711e534a | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **CapAccounting** | Implementation | ||| +| └ | _capExceededBy | Internal 🔒 | | | +| └ | _capHeadroom | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_ChainlinkPoRFeedManager.sol.md b/doc/surya/surya_report/surya_report_ChainlinkPoRFeedManager.sol.md index b48f64af..a9bb50e6 100644 --- a/doc/surya/surya_report/surya_report_ChainlinkPoRFeedManager.sol.md +++ b/doc/surya/surya_report/surya_report_ChainlinkPoRFeedManager.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/core/ChainlinkPoRFeedManager.sol | 4c296843d471b9cc60a86ef8253205dabbc45558 | +| ./rules/validation/abstract/core/ChainlinkPoRFeedManager.sol | 4d917002775897247442a336203c5436d3f444e3 | ### Contracts Description Table @@ -15,7 +15,7 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **ChainlinkPoRFeedManager** | Implementation | TokenSupplyReader, RuleChainlinkPoRInvariantStorage ||| +| **ChainlinkPoRFeedManager** | Implementation | CapAccounting, TokenSupplyReader, RuleChainlinkPoRInvariantStorage ||| | └ | setReservesFeed | Public ❗️ | 🛑 | onlyChainlinkPoRManager | | └ | setTokenMetadata | Public ❗️ | 🛑 | onlyChainlinkPoRManager | | └ | setMaxStalenessSeconds | Public ❗️ | 🛑 | onlyChainlinkPoRManager | diff --git a/doc/surya/surya_report/surya_report_ERC3643CapHarnesses.sol.md b/doc/surya/surya_report/surya_report_ERC3643CapHarnesses.sol.md new file mode 100644 index 00000000..a57aad19 --- /dev/null +++ b/doc/surya/surya_report/surya_report_ERC3643CapHarnesses.sol.md @@ -0,0 +1,42 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./mocks/harness/ERC3643CapHarnesses.sol | 1ee6bd8daa637884f5fb5a851f708640f146d380 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **ERC3643MaxTotalSupplyHarness** | Implementation | RuleMaxTotalSupply ||| +| └ | | Public ❗️ | 🛑 | RuleMaxTotalSupply | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | +|||||| +| **ERC3643MaxBalanceHarness** | Implementation | RuleMaxBalance ||| +| └ | | Public ❗️ | 🛑 | RuleMaxBalance | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | +|||||| +| **ERC3643ChainlinkPoRHarness** | Implementation | RuleChainlinkPoR ||| +| └ | | Public ❗️ | 🛑 | RuleChainlinkPoR | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | +|||||| +| **TrackedSupplyHarness** | Implementation | RuleMaxTotalSupply ||| +| └ | | Public ❗️ | 🛑 | RuleMaxTotalSupply | +| └ | setTrackedSupply | External ❗️ | 🛑 |NO❗️ | +| └ | _currentSupply | Internal 🔒 | | | +| └ | _supplyToken | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_ERC3643TokenMock.sol.md b/doc/surya/surya_report/surya_report_ERC3643TokenMock.sol.md index 65981209..14c3c84d 100644 --- a/doc/surya/surya_report/surya_report_ERC3643TokenMock.sol.md +++ b/doc/surya/surya_report/surya_report_ERC3643TokenMock.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./mocks/ERC3643TokenMock.sol | 09e3ec529557577b366c9e48ba1be2ed6fda0d4f | +| ./mocks/ERC3643TokenMock.sol | 1ed75c6e22c5677378835a73273381ce42e7b7aa | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_IAddressList.sol.md b/doc/surya/surya_report/surya_report_IAddressList.sol.md index 4282f5c6..d0189f7e 100644 --- a/doc/surya/surya_report/surya_report_IAddressList.sol.md +++ b/doc/surya/surya_report/surya_report_IAddressList.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/interfaces/IAddressList.sol | e043af3e25afec3f5015f668979e5b32cc36f490 | +| ./rules/interfaces/IAddressList.sol | 2986fa01ee3211e35276e0ef8970a939c1c9f050 | ### Contracts Description Table @@ -15,14 +15,19 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **IAddressList** | Interface | IIdentityRegistryContains ||| +| **IAddressListBatchQuery** | Interface | ||| +| └ | areAddressesListed | External ❗️ | |NO❗️ | +|||||| +| **IAddressListPolarity** | Interface | ||| +| └ | isAllowList | External ❗️ | |NO❗️ | +|||||| +| **IAddressList** | Interface | IIdentityRegistryContains, IAddressListBatchQuery ||| | └ | addAddresses | External ❗️ | 🛑 |NO❗️ | | └ | removeAddresses | External ❗️ | 🛑 |NO❗️ | | └ | addAddress | External ❗️ | 🛑 |NO❗️ | | └ | removeAddress | External ❗️ | 🛑 |NO❗️ | | └ | listedAddressCount | External ❗️ | |NO❗️ | | └ | isAddressListed | External ❗️ | |NO❗️ | -| └ | areAddressesListed | External ❗️ | |NO❗️ | ### Legend diff --git a/doc/surya/surya_report/surya_report_IERC3643ComplianceFull.sol.md b/doc/surya/surya_report/surya_report_IERC3643ComplianceFull.sol.md index 60e22cd3..335de6fb 100644 --- a/doc/surya/surya_report/surya_report_IERC3643ComplianceFull.sol.md +++ b/doc/surya/surya_report/surya_report_IERC3643ComplianceFull.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./mocks/IERC3643ComplianceFull.sol | 341ca7a53aeacd897ee359d5e80c1ec7f1fcf6fa | +| ./mocks/IERC3643ComplianceFull.sol | a636d4fc9ee8540016e54d4e8d43e15d8a3e7302 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_IdentityRegistryDelegationHarness.sol.md b/doc/surya/surya_report/surya_report_IdentityRegistryDelegationHarness.sol.md new file mode 100644 index 00000000..f5469fc5 --- /dev/null +++ b/doc/surya/surya_report/surya_report_IdentityRegistryDelegationHarness.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./mocks/harness/IdentityRegistryDelegationHarness.sol | 07ae2614113053b786bd43f4961c735f2e34ec0c | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **IdentityRegistryExtraCheckHarness** | Implementation | RuleIdentityRegistry ||| +| └ | | Public ❗️ | 🛑 | RuleIdentityRegistry | +| └ | _detectTransferRestriction | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_IdentityRegistryWhitelistBase.sol.md b/doc/surya/surya_report/surya_report_IdentityRegistryWhitelistBase.sol.md index 84ba3fba..44da9a57 100644 --- a/doc/surya/surya_report/surya_report_IdentityRegistryWhitelistBase.sol.md +++ b/doc/surya/surya_report/surya_report_IdentityRegistryWhitelistBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./registry/abstract/IdentityRegistryWhitelistBase.sol | 40c79099b5974626719f5083aaa0afe824dd7b1e | +| ./registry/abstract/IdentityRegistryWhitelistBase.sol | a829964c7d939f9ee5aab8bebf1ae471320606ad | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleAddressSetInternal.sol.md b/doc/surya/surya_report/surya_report_RuleAddressSetInternal.sol.md index 3438c7ab..fca15c0c 100644 --- a/doc/surya/surya_report/surya_report_RuleAddressSetInternal.sol.md +++ b/doc/surya/surya_report/surya_report_RuleAddressSetInternal.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol | eddc744d1e6c3c8a68aa267774d77536d986146f | +| ./rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol | 2432a32f5051d3ca4b0c158ae32a31add67a84db | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleBlacklistBase.sol.md b/doc/surya/surya_report/surya_report_RuleBlacklistBase.sol.md index 29db00e9..a4b1c887 100644 --- a/doc/surya/surya_report/surya_report_RuleBlacklistBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleBlacklistBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleBlacklistBase.sol | 8abdb2e4e56f45a1ee470fa4b4951df84280a2d8 | +| ./rules/validation/abstract/base/RuleBlacklistBase.sol | 164a28f65e9b2baa95956289185ce05ca9ca0137 | ### Contracts Description Table @@ -15,13 +15,14 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **RuleBlacklistBase** | Implementation | RuleAddressSet, RuleNFTAdapter, RuleBlacklistInvariantStorage ||| +| **RuleBlacklistBase** | Implementation | RuleAddressSet, RuleNFTAdapter, RuleBlacklistInvariantStorage, IAddressListPolarity ||| | └ | | Public ❗️ | 🛑 | RuleAddressSet | | └ | transferred | Public ❗️ | |NO❗️ | | └ | transferred | Public ❗️ | |NO❗️ | | └ | canReturnTransferRestrictionCode | Public ❗️ | |NO❗️ | | └ | messageForTransferRestriction | Public ❗️ | |NO❗️ | | └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | isAllowList | Public ❗️ | |NO❗️ | | └ | _detectTransferRestriction | Internal 🔒 | | | | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_RuleChainlinkPoRBase.sol.md b/doc/surya/surya_report/surya_report_RuleChainlinkPoRBase.sol.md index b50ed54e..2b2151b7 100644 --- a/doc/surya/surya_report/surya_report_RuleChainlinkPoRBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleChainlinkPoRBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleChainlinkPoRBase.sol | a1e200478baa5885cb422102d6800a094cf5abda | +| ./rules/validation/abstract/base/RuleChainlinkPoRBase.sol | f91ce6934da8dd4f13c8c136d1dea1d549695f45 | ### Contracts Description Table @@ -23,6 +23,7 @@ | └ | messageForTransferRestriction | Public ❗️ | |NO❗️ | | └ | _detectTransferRestriction | Internal 🔒 | | | | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | | └ | _transferredFrom | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_RuleChainlinkPoRERC3643.sol.md b/doc/surya/surya_report/surya_report_RuleChainlinkPoRERC3643.sol.md new file mode 100644 index 00000000..c7746975 --- /dev/null +++ b/doc/surya/surya_report/surya_report_RuleChainlinkPoRERC3643.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./rules/validation/deployment/RuleChainlinkPoRERC3643.sol | 666fcf4ef0bc5b1e104235514d4a712550bec528 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **RuleChainlinkPoRERC3643** | Implementation | RuleChainlinkPoR ||| +| └ | | Public ❗️ | 🛑 | RuleChainlinkPoR | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_RuleChainlinkPoRERC3643Ownable2Step.sol.md b/doc/surya/surya_report/surya_report_RuleChainlinkPoRERC3643Ownable2Step.sol.md new file mode 100644 index 00000000..c4f8c7e2 --- /dev/null +++ b/doc/surya/surya_report/surya_report_RuleChainlinkPoRERC3643Ownable2Step.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol | b885da14c378e3fdf5b67dec2d9e00b17a06f042 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **RuleChainlinkPoRERC3643Ownable2Step** | Implementation | RuleChainlinkPoROwnable2Step ||| +| └ | | Public ❗️ | 🛑 | RuleChainlinkPoROwnable2Step | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_RuleChainlinkPoRInvariantStorage.sol.md b/doc/surya/surya_report/surya_report_RuleChainlinkPoRInvariantStorage.sol.md index a7a008ef..4bc074a3 100644 --- a/doc/surya/surya_report/surya_report_RuleChainlinkPoRInvariantStorage.sol.md +++ b/doc/surya/surya_report/surya_report_RuleChainlinkPoRInvariantStorage.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol | 64a0b4372644a3b79b325839cdd9897f5c3ad9f0 | +| ./rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol | e8568c6d91ab8630e0ce94d6dc4ca5c436f623ad | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLight.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLight.sol.md index 0c7f3f3c..f4e87047 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLight.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLight.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/RuleConditionalTransferLight.sol | d20cf57626e59b0e16ae7d8ae18d7c7f1c1de7a5 | +| ./rules/operation/RuleConditionalTransferLight.sol | 4e1e3943d4e53454fa1b24c263871ffd4dc4c0f7 | ### Contracts Description Table @@ -20,7 +20,7 @@ | └ | supportsInterface | Public ❗️ | |NO❗️ | | └ | _onlyComplianceManager | Internal 🔒 | | onlyRole | | └ | _authorizeTransferApproval | Internal 🔒 | | onlyRole | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | | onlyRole | +| └ | _authorizeTokenBindingChange | Internal 🔒 | | onlyRole | ### Legend diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightApprovalBase.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightApprovalBase.sol.md index d38172c0..0ab34bbf 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightApprovalBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightApprovalBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol | ac5eac80044e31e99f670ecc63ece95fa1f8326f | +| ./rules/operation/abstract/RuleConditionalTransferLightApprovalBase.sol | 147867677947edf0d0a36476e65fcdad28502e08 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightBase.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightBase.sol.md index 8fd176df..77f81d7d 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/abstract/RuleConditionalTransferLightBase.sol | 61bc28f3f3069112e53a7939da7cb4bfeca103f9 | +| ./rules/operation/abstract/RuleConditionalTransferLightBase.sol | d69293485b40c9f6bd53bb0b7cce2e86938939c2 | ### Contracts Description Table @@ -23,9 +23,9 @@ | └ | approveAndTransferIfAllowed | Public ❗️ | 🛑 | onlyTransferApprover | | └ | transferred | Public ❗️ | 🛑 | onlyTransferExecutor | | └ | transferred | Public ❗️ | 🛑 | onlyTransferExecutor | -| └ | bindToken | Public ❗️ | 🛑 | onlyComplianceManager | -| └ | bindRuleEngine | Public ❗️ | 🛑 | onlyComplianceManager | -| └ | unbindRuleEngine | Public ❗️ | 🛑 | onlyComplianceManager | +| └ | bindToken | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | bindRuleEngine | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | unbindRuleEngine | Public ❗️ | 🛑 | onlyTokenBindingManager | | └ | isTransferExecutor | Public ❗️ | |NO❗️ | | └ | detectTransferRestriction | Public ❗️ | |NO❗️ | | └ | detectTransferRestrictionFrom | Public ❗️ | |NO❗️ | diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightInvariantStorage.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightInvariantStorage.sol.md index 25e75f82..dbd2624c 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightInvariantStorage.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightInvariantStorage.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol | b3d88b809eda5e44ef47c6e103a347c0c2ca92f8 | +| ./rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol | 0b5813c33fbc24b2e09fee9aac35c2efaa87b7c9 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiToken.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiToken.sol.md index 0e821388..54646157 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiToken.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiToken.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/RuleConditionalTransferLightMultiToken.sol | bc1bc27d2f80a0116ae1987cf795ab22032fce60 | +| ./rules/operation/RuleConditionalTransferLightMultiToken.sol | b79d37d00a665ab19199e41ca8eacaddec678656 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenBase.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenBase.sol.md index 89ea6ab0..95778c83 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol | 126be4df25c91c0cace70424f0e0d608b1cf8258 | +| ./rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol | 920bbe385eb3b6e11472d296798f9a5e259b0a81 | ### Contracts Description Table @@ -34,7 +34,6 @@ | └ | detectTransferRestrictionFrom | Public ❗️ | |NO❗️ | | └ | canTransfer | Public ❗️ | |NO❗️ | | └ | canTransferFrom | Public ❗️ | |NO❗️ | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | 🛑 | | | └ | _approveTransfer | Internal 🔒 | 🛑 | | | └ | _cancelTransferApproval | Internal 🔒 | 🛑 | | | └ | _transferred | Internal 🔒 | 🛑 | | diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenInvariantStorage.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenInvariantStorage.sol.md index 3e19e3be..a72e17b0 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenInvariantStorage.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenInvariantStorage.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol | 69581e98b6b327335f84cefe92a064e0189d9532 | +| ./rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol | 6e7143e1c1de75bc9c11870f7c9802c23a4628d2 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.md index db3c2481..0abd2b09 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightMultiTokenOwnable2Step.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol | e4023e744c8042d7fdc8c117a3c920f14d09e33c | +| ./rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol | bfac420c101f1b65f8aa912dfa2113d67d071cc9 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightOwnable2Step.sol.md b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightOwnable2Step.sol.md index f6ead88e..c46981ae 100644 --- a/doc/surya/surya_report/surya_report_RuleConditionalTransferLightOwnable2Step.sol.md +++ b/doc/surya/surya_report/surya_report_RuleConditionalTransferLightOwnable2Step.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/RuleConditionalTransferLightOwnable2Step.sol | 522390c69b70608392e43493c56e72056e45df93 | +| ./rules/operation/RuleConditionalTransferLightOwnable2Step.sol | 0d55dcd64df3896a3475356a87b823bd24843170 | ### Contracts Description Table @@ -20,7 +20,7 @@ | └ | supportsInterface | Public ❗️ | |NO❗️ | | └ | _onlyComplianceManager | Internal 🔒 | | onlyOwner | | └ | _authorizeTransferApproval | Internal 🔒 | | onlyOwner | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | | onlyOwner | +| └ | _authorizeTokenBindingChange | Internal 🔒 | | onlyOwner | ### Legend diff --git a/doc/surya/surya_report/surya_report_RuleERC2980.sol.md b/doc/surya/surya_report/surya_report_RuleERC2980.sol.md index 89c0d9dc..9c535fa8 100644 --- a/doc/surya/surya_report/surya_report_RuleERC2980.sol.md +++ b/doc/surya/surya_report/surya_report_RuleERC2980.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/deployment/RuleERC2980.sol | df7b966dbe91c0ac24efcf6bc7f03cda96f3ed91 | +| ./rules/validation/deployment/RuleERC2980.sol | 71a7cbdea6d5f4f481ec5fe628343818626dd8d9 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleERC2980Internal.sol.md b/doc/surya/surya_report/surya_report_RuleERC2980Internal.sol.md index dd9dba73..ad1e2b6b 100644 --- a/doc/surya/surya_report/surya_report_RuleERC2980Internal.sol.md +++ b/doc/surya/surya_report/surya_report_RuleERC2980Internal.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol | 5566b1b8c48c5f5d89b01d7883f00743bd7bf8f2 | +| ./rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol | 308b2dd355e02944137733bb93ab247c432f9790 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleIdentityRegistryBase.sol.md b/doc/surya/surya_report/surya_report_RuleIdentityRegistryBase.sol.md index 59b1c345..c76a4a3d 100644 --- a/doc/surya/surya_report/surya_report_RuleIdentityRegistryBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleIdentityRegistryBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleIdentityRegistryBase.sol | 2e24eefcee4613fe38e39197faa509d0b69cebbd | +| ./rules/validation/abstract/base/RuleIdentityRegistryBase.sol | 450bba38258c0d644836710389bb224883a1bc25 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleMaxBalanceBase.sol.md b/doc/surya/surya_report/surya_report_RuleMaxBalanceBase.sol.md index 678c8198..3a0f61b6 100644 --- a/doc/surya/surya_report/surya_report_RuleMaxBalanceBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleMaxBalanceBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleMaxBalanceBase.sol | 62f929cdc9564258f602ff2a298f31a304d1c53f | +| ./rules/validation/abstract/base/RuleMaxBalanceBase.sol | fa72a00a34cec050299ab9f2cd4be2a4ee5030bc | ### Contracts Description Table @@ -24,6 +24,7 @@ | └ | messageForTransferRestriction | Public ❗️ | |NO❗️ | | └ | _detectTransferRestriction | Internal 🔒 | | | | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | | └ | _transferredFrom | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyBase.sol.md b/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyBase.sol.md index 450c634c..2959545a 100644 --- a/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol | 645be19fe00a1ab23bdd92491942d97cb31fa383 | +| ./rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol | dc9656537f33ebce7ffb09435f1620cfc2d0385f | ### Contracts Description Table @@ -23,6 +23,7 @@ | └ | messageForTransferRestriction | Public ❗️ | |NO❗️ | | └ | _detectTransferRestriction | Internal 🔒 | | | | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | | └ | _transferredFrom | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyERC3643.sol.md b/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyERC3643.sol.md new file mode 100644 index 00000000..e66d5506 --- /dev/null +++ b/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyERC3643.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol | a7f6bd206a4098f0b1e21426c46d7cc20cdb905f | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **RuleMaxTotalSupplyERC3643** | Implementation | RuleMaxTotalSupply ||| +| └ | | Public ❗️ | 🛑 | RuleMaxTotalSupply | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyERC3643Ownable2Step.sol.md b/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyERC3643Ownable2Step.sol.md new file mode 100644 index 00000000..26ec81ac --- /dev/null +++ b/doc/surya/surya_report/surya_report_RuleMaxTotalSupplyERC3643Ownable2Step.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol | d7ed63426b3a13b1dd0a25b23af6b72bdd56d84a | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **RuleMaxTotalSupplyERC3643Ownable2Step** | Implementation | RuleMaxTotalSupplyOwnable2Step ||| +| └ | | Public ❗️ | 🛑 | RuleMaxTotalSupplyOwnable2Step | +| └ | _detectTransferRestrictionOnNotify | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_RuleMintAllowance.sol.md b/doc/surya/surya_report/surya_report_RuleMintAllowance.sol.md index 707e831d..451f0e02 100644 --- a/doc/surya/surya_report/surya_report_RuleMintAllowance.sol.md +++ b/doc/surya/surya_report/surya_report_RuleMintAllowance.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/RuleMintAllowance.sol | e2cf3135bdb23ade767c610bd125d6d39706a617 | +| ./rules/operation/RuleMintAllowance.sol | 5dffbe6960ee9eb1154007dc3f33dba2ae4601e0 | ### Contracts Description Table @@ -20,7 +20,7 @@ | └ | supportsInterface | Public ❗️ | |NO❗️ | | └ | _onlyComplianceManager | Internal 🔒 | | onlyRole | | └ | _authorizeSetMintAllowance | Internal 🔒 | | onlyRole | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | | onlyRole | +| └ | _authorizeTokenBindingChange | Internal 🔒 | | onlyRole | ### Legend diff --git a/doc/surya/surya_report/surya_report_RuleMintAllowanceBase.sol.md b/doc/surya/surya_report/surya_report_RuleMintAllowanceBase.sol.md index d8b8f731..3dfa95ff 100644 --- a/doc/surya/surya_report/surya_report_RuleMintAllowanceBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleMintAllowanceBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/abstract/RuleMintAllowanceBase.sol | c702b5250dc5dea9ee0a34fb1644def4b6a1385b | +| ./rules/operation/abstract/RuleMintAllowanceBase.sol | 579db19d0f10e2d0f5e6df95220dc1fd4d3fff42 | ### Contracts Description Table @@ -23,7 +23,7 @@ | └ | increaseMintAllowance | Public ❗️ | 🛑 | onlyAllowanceOperator | | └ | decreaseMintAllowance | Public ❗️ | 🛑 | onlyAllowanceOperator | | └ | clearMintAllowances | Public ❗️ | 🛑 | onlyAllowanceOperator | -| └ | bindToken | Public ❗️ | 🛑 | onlyComplianceManager | +| └ | bindToken | Public ❗️ | 🛑 | onlyTokenBindingManager | | └ | transferred | Public ❗️ | 🛑 | onlyBoundToken | | └ | transferred | Public ❗️ | 🛑 | onlyBoundToken | | └ | messageForTransferRestriction | Public ❗️ | |NO❗️ | diff --git a/doc/surya/surya_report/surya_report_RuleMintAllowanceOwnable2Step.sol.md b/doc/surya/surya_report/surya_report_RuleMintAllowanceOwnable2Step.sol.md index d15ab5d1..04e89c67 100644 --- a/doc/surya/surya_report/surya_report_RuleMintAllowanceOwnable2Step.sol.md +++ b/doc/surya/surya_report/surya_report_RuleMintAllowanceOwnable2Step.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/operation/RuleMintAllowanceOwnable2Step.sol | 47d79e70bd08f2719e55e4f27c356b494e25196f | +| ./rules/operation/RuleMintAllowanceOwnable2Step.sol | 61ec71bd1ee13b2711aea2345717825ddbe0e934 | ### Contracts Description Table @@ -20,7 +20,7 @@ | └ | supportsInterface | Public ❗️ | |NO❗️ | | └ | _onlyComplianceManager | Internal 🔒 | | onlyOwner | | └ | _authorizeSetMintAllowance | Internal 🔒 | | onlyOwner | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | | onlyOwner | +| └ | _authorizeTokenBindingChange | Internal 🔒 | | onlyOwner | ### Legend diff --git a/doc/surya/surya_report/surya_report_RuleNFTAdapter.sol.md b/doc/surya/surya_report/surya_report_RuleNFTAdapter.sol.md index 87cce742..7d673582 100644 --- a/doc/surya/surya_report/surya_report_RuleNFTAdapter.sol.md +++ b/doc/surya/surya_report/surya_report_RuleNFTAdapter.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/core/RuleNFTAdapter.sol | 77a5fb86eed3006ca5020e7d6f223758c8b309a1 | +| ./rules/validation/abstract/core/RuleNFTAdapter.sol | d70064b1ef2bff9e602f871259cd0f17ee44dd5d | ### Contracts Description Table @@ -24,6 +24,7 @@ | └ | detectTransferRestrictionFrom | Public ❗️ | |NO❗️ | | └ | canTransfer | Public ❗️ | |NO❗️ | | └ | canTransferFrom | Public ❗️ | |NO❗️ | +| └ | _isDelegated | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | 🛑 | | | └ | _transferredFrom | Internal 🔒 | 🛑 | | diff --git a/doc/surya/surya_report/surya_report_RuleReceiverWhitelistBase.sol.md b/doc/surya/surya_report/surya_report_RuleReceiverWhitelistBase.sol.md index 2e2a285a..a5018c38 100644 --- a/doc/surya/surya_report/surya_report_RuleReceiverWhitelistBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleReceiverWhitelistBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleReceiverWhitelistBase.sol | d3b77283cf3de8426036be4d0a61e86ae0d1c3c8 | +| ./rules/validation/abstract/base/RuleReceiverWhitelistBase.sol | 5098d370b3b933847b4e24763466a27a030b26c9 | ### Contracts Description Table @@ -15,13 +15,14 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **RuleReceiverWhitelistBase** | Implementation | RuleAddressSet, RuleNFTAdapter, RuleReceiverWhitelistInvariantStorage ||| +| **RuleReceiverWhitelistBase** | Implementation | RuleAddressSet, RuleNFTAdapter, RuleReceiverWhitelistInvariantStorage, IAddressListPolarity ||| | └ | | Public ❗️ | 🛑 | RuleAddressSet | | └ | canReturnTransferRestrictionCode | External ❗️ | |NO❗️ | | └ | transferred | Public ❗️ | |NO❗️ | | └ | transferred | Public ❗️ | |NO❗️ | | └ | messageForTransferRestriction | Public ❗️ | |NO❗️ | | └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | isAllowList | Public ❗️ | |NO❗️ | | └ | _detectTransferRestriction | Internal 🔒 | | | | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_RuleSpenderWhitelistBase.sol.md b/doc/surya/surya_report/surya_report_RuleSpenderWhitelistBase.sol.md index ce8c5c5a..eea4942e 100644 --- a/doc/surya/surya_report/surya_report_RuleSpenderWhitelistBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleSpenderWhitelistBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleSpenderWhitelistBase.sol | f98bec5c483d2dc831f4b751f4a4d72a48cddf84 | +| ./rules/validation/abstract/base/RuleSpenderWhitelistBase.sol | 595702e7fc83aa8743a72a50d1660369cadd6349 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleWhitelistBase.sol.md b/doc/surya/surya_report/surya_report_RuleWhitelistBase.sol.md index 05bc0295..5f17b19c 100644 --- a/doc/surya/surya_report/surya_report_RuleWhitelistBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleWhitelistBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleWhitelistBase.sol | 7cb62bf29323cbf092a1f0787d6b7cf6e929d41b | +| ./rules/validation/abstract/base/RuleWhitelistBase.sol | d5a143b73b371609a2db4d71b61e960d3dd62845 | ### Contracts Description Table @@ -15,10 +15,11 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **RuleWhitelistBase** | Implementation | RuleAddressSet, RuleWhitelistShared, IIdentityRegistryVerified ||| +| **RuleWhitelistBase** | Implementation | RuleAddressSet, RuleWhitelistShared, IIdentityRegistryVerified, IAddressListPolarity ||| | └ | | Public ❗️ | 🛑 | RuleAddressSet | | └ | isVerified | Public ❗️ | |NO❗️ | | └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | isAllowList | Public ❗️ | |NO❗️ | | └ | _detectTransferRestriction | Internal 🔒 | | | | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_RuleWhitelistInvariantStorage.sol.md b/doc/surya/surya_report/surya_report_RuleWhitelistInvariantStorage.sol.md index 5de6dae4..d84d0bb6 100644 --- a/doc/surya/surya_report/surya_report_RuleWhitelistInvariantStorage.sol.md +++ b/doc/surya/surya_report/surya_report_RuleWhitelistInvariantStorage.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol | 15e5c48a6b853a46f25131198e0415fe0ff91524 | +| ./rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol | 7471b0561bbee8918921dc9a35f524334e9064d8 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_RuleWhitelistWrapperBase.sol.md b/doc/surya/surya_report/surya_report_RuleWhitelistWrapperBase.sol.md index d96bc2a5..64ea41b6 100644 --- a/doc/surya/surya_report/surya_report_RuleWhitelistWrapperBase.sol.md +++ b/doc/surya/surya_report/surya_report_RuleWhitelistWrapperBase.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/base/RuleWhitelistWrapperBase.sol | bb3b6454d4316c7a7bcc82e6e599ffc45bacdd7a | +| ./rules/validation/abstract/base/RuleWhitelistWrapperBase.sol | 7676e74e58ea3b9b2b56ebefd897a434f79735c6 | ### Contracts Description Table @@ -24,6 +24,7 @@ | └ | _detectTransferRestrictionFrom | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | | └ | _transferred | Internal 🔒 | | | +| └ | _checkRule | Internal 🔒 | | | | └ | _detectTransferRestrictionForTargets | Internal 🔒 | | | | └ | _msgSender | Internal 🔒 | | | | └ | _msgData | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_TokenSupplyReader.sol.md b/doc/surya/surya_report/surya_report_TokenSupplyReader.sol.md index 52bfbde4..f385c3b6 100644 --- a/doc/surya/surya_report/surya_report_TokenSupplyReader.sol.md +++ b/doc/surya/surya_report/surya_report_TokenSupplyReader.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/core/TokenSupplyReader.sol | d7faee1c8cfbc1c31fb97f65823c1c4648b3d793 | +| ./rules/validation/abstract/core/TokenSupplyReader.sol | f7d1c2a381bcba1a7224165f1298b98984ebbf60 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_TotalSupplyCapManager.sol.md b/doc/surya/surya_report/surya_report_TotalSupplyCapManager.sol.md index 6984c286..e2940dc9 100644 --- a/doc/surya/surya_report/surya_report_TotalSupplyCapManager.sol.md +++ b/doc/surya/surya_report/surya_report_TotalSupplyCapManager.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./rules/validation/abstract/core/TotalSupplyCapManager.sol | 2ccec17348c8be7669d4241186fb870cc4069d8a | +| ./rules/validation/abstract/core/TotalSupplyCapManager.sol | 4b3c48fb12e49a65c1a2ddb3998a5fd7d2cb3b37 | ### Contracts Description Table @@ -15,7 +15,7 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **TotalSupplyCapManager** | Implementation | TokenSupplyReader, RuleMaxTotalSupplyInvariantStorage ||| +| **TotalSupplyCapManager** | Implementation | CapAccounting, TokenSupplyReader, RuleMaxTotalSupplyInvariantStorage ||| | └ | setMaxTotalSupply | Public ❗️ | 🛑 | onlyMaxTotalSupplyManager | | └ | setTokenContract | Public ❗️ | 🛑 | onlyMaxTotalSupplyManager | | └ | _setMaxTotalSupply | Internal 🔒 | 🛑 | | diff --git a/doc/surya/surya_report/surya_report_VersionModule.sol.md b/doc/surya/surya_report/surya_report_VersionModule.sol.md index aeebbe66..cfc20703 100644 --- a/doc/surya/surya_report/surya_report_VersionModule.sol.md +++ b/doc/surya/surya_report/surya_report_VersionModule.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/VersionModule.sol | 41780d1380a0071906b292cf236c8b07f81d941d | +| ./modules/VersionModule.sol | e1382bf6da9625f05c2395a7bf767c61a13fe04a | ### Contracts Description Table diff --git a/doc/technical/contracts/RuleChainlinkPoR.md b/doc/technical/contracts/RuleChainlinkPoR.md index c4e6156b..49e13f06 100644 --- a/doc/technical/contracts/RuleChainlinkPoR.md +++ b/doc/technical/contracts/RuleChainlinkPoR.md @@ -124,7 +124,7 @@ Setting the threshold to `0` disables the check, so the rule then accepts reserv | --- | --- | --- | | `CODE_RESERVES_EXCEEDED` | 75 | `totalSupply + value` would exceed the backed supply | | `CODE_RESERVES_FEED_STALE` | 76 | The feed has not been updated within `maxStalenessSeconds` | -| `CODE_RESERVES_ANSWER_INVALID` | 77 | A round **was** returned but cannot be used: a negative reserve, or an incomplete round (`updatedAt == 0`) | +| `CODE_RESERVES_ANSWER_INVALID` | 77 | A round **was** returned but cannot be used: a negative reserve, an incomplete round (`updatedAt == 0`), or a round stamped in the future (`updatedAt > block.timestamp`) | | `CODE_RESERVES_FEED_UNAVAILABLE` | 79 | **No usable response** could be obtained: `decimals()` or `latestRoundData()` reverted, or the feed reports more than `MAX_FEED_DECIMALS` | | `CODE_TOTAL_SUPPLY_UNAVAILABLE` | 78 | `tokenContract.totalSupply()` reverted, or the token has lost its code | @@ -169,8 +169,8 @@ For a mint (`from == address(0)`): 1. Read `decimals()` and then `latestRoundData()` from `reservesFeed`. 2. Reject with `CODE_RESERVES_FEED_UNAVAILABLE` if either call reverts or the feed reports more than `MAX_FEED_DECIMALS`: there is no answer to judge. -3. Reject with `CODE_RESERVES_ANSWER_INVALID` if a round was returned but `answer < 0` or `updatedAt == 0`. -4. Reject with `CODE_RESERVES_FEED_STALE` if `maxStalenessSeconds != 0` and `block.timestamp - updatedAt > maxStalenessSeconds`. +3. Reject with `CODE_RESERVES_ANSWER_INVALID` if a round was returned but `answer < 0`, `updatedAt == 0`, or `updatedAt > block.timestamp`. A future-dated round is a **malformed answer, not a stale one**, so it is rejected even when `maxStalenessSeconds == 0`. +4. Reject with `CODE_RESERVES_FEED_STALE` if `maxStalenessSeconds != 0` and `block.timestamp - updatedAt > maxStalenessSeconds`. The subtraction cannot underflow: step 3 has already established `updatedAt <= block.timestamp`. 5. Scale the answer from the feed's live decimals to `tokenDecimals` to obtain `backedSupply`. 6. Read `tokenContract.totalSupply()`; reject with `CODE_TOTAL_SUPPLY_UNAVAILABLE` if it reverts or the token has lost its code. 7. Reject with `CODE_RESERVES_EXCEEDED` if `totalSupply + value > backedSupply`. @@ -197,7 +197,7 @@ with data, and a view cannot emit an event. | Code | Meaning | What an operator checks | | --- | --- | --- | | `79` | The feed could not be read at all | Feed liveness; is the configured address a compatible `AggregatorV3Interface`? | -| `77` | A round came back and its contents are unusable | Is this really a Proof of Reserve feed (a price feed can legitimately go negative)? Or wait for the round to complete. | +| `77` | A round came back and its contents are unusable | Is this really a Proof of Reserve feed (a price feed can legitimately go negative)? Wait for the round to complete, or — for a future-dated `updatedAt` — treat the aggregator as compromised and repoint the feed. | `80` is left reserved. Splitting `79` further into "reverted" versus "decimals out of range" was considered and rejected: both mean the configured feed cannot be used, so the remedy is the same. @@ -268,7 +268,7 @@ The decisive difference is **how a rejection is signalled**. `SecureMintPolicy.r | Feed decimals bound | Unbounded (`uint8`) | `<= MAX_FEED_DECIMALS` (36), checked at configuration **and** at read time | | Feed call reverts (`decimals` or `latestRoundData`) | Propagates — mint reverts | `try/catch` → code `77` | | Incomplete round (`updatedAt == 0`) | Not checked | Code `77` | -| Staleness arithmetic | `block.timestamp - updatedAt` — underflow-panics on a future timestamp | Guarded with `block.timestamp > updatedAt` | +| Future-dated round (`updatedAt > block.timestamp`) | Not checked; `block.timestamp - updatedAt` underflow-panics the whole call | Code `77`, unconditionally — not gated on `maxStalenessSeconds` | | Token decimals accepted | `1` to `18` | `0` to `18` (CMTAT equity tokens report 0) | | Reserve margin | 5 modes (percentage / absolute, positive / negative) | None — limit equals reserves exactly | | Scale-up overflow | Checked arithmetic → revert | Saturates at `type(uint256).max` | @@ -284,7 +284,7 @@ The decisive difference is **how a rejection is signalled**. `SecureMintPolicy.r ### Where this rule is stricter - **Feed failures degrade to a code, not a revert.** A feed with no code, a reverting `latestRoundData()`, a negative answer or an incomplete round all yield code `77`. ACE has no `updatedAt == 0` check at all, so with `maxStalenessSeconds == 0` an incomplete round is accepted at face value. -- **No underflow on a future `updatedAt`.** ACE computes `block.timestamp - updatedAt` unguarded; a feed reporting a timestamp ahead of the block panics the whole call. Fail-closed for ACE, but a panic rather than a clean rejection. +- **A future `updatedAt` is rejected, not merely survived.** ACE computes `block.timestamp - updatedAt` unguarded, so a feed reporting a timestamp ahead of the block panics the whole call — fail-closed, but as a panic rather than a clean rejection. This rule returns code `77`, and does so **regardless of `maxStalenessSeconds`**: a timestamp no aggregator on this chain could have written is a malformed answer, and an operator who disables freshness checking must not thereby accept forged timestamps. (Nethermind AuditAgent NM-10; before the fix the underflow guard `block.timestamp > updatedAt` silently accepted any future stamp, so a feed frozen on an old reserve answer could keep authorising mints until that timestamp elapsed.) - **Feed decimals are bounded at configuration time**, so the scaling exponent can never overflow. ACE accepts any `uint8`, where a feed reporting e.g. 78 decimals makes `10 ** 78` revert on every mint. - **`0`-decimals tokens are supported.** ACE requires `decimals > 0`, which excludes CMTAT equity tokens outright. diff --git a/doc/technical/contracts/RuleChainlinkPoRERC3643.md b/doc/technical/contracts/RuleChainlinkPoRERC3643.md new file mode 100644 index 00000000..cc86351a --- /dev/null +++ b/doc/technical/contracts/RuleChainlinkPoRERC3643.md @@ -0,0 +1,155 @@ +# Rule Chainlink PoR — ERC-3643 variant + +> ⚠️ **For ERC-3643 tokens only.** Use plain [`RuleChainlinkPoR`](./RuleChainlinkPoR.md) with CMTAT. +> The two are not interchangeable, and choosing the wrong one **silently** mis-caps issuance in one +> direction or the other. Nothing reverts at deployment to tell you. + +`RuleChainlinkPoRERC3643` and `RuleChainlinkPoRERC3643Ownable2Step` cap minting at the reserves +reported by a Chainlink Proof of Reserve feed, exactly like the stock rule. The reserve logic, +restriction codes (75–79), configuration, roles and events are **identical and inherited**. The only +difference is *when the token is assumed to report the mint*. + +## Why a separate variant: compliance is called AFTER the transfer + +A compliance rule that caps a supply has to know whether the figure it reads already includes the +amount being moved. The two token families answer differently. + +| Token | Order on a mint | `totalSupply()` when the rule is notified | Use | +|---|---|---|---| +| **CMTAT** | rule first, then the mint | **excludes** the new tokens | [`RuleChainlinkPoR`](./RuleChainlinkPoR.md) | +| **ERC-3643 / T-REX** | mint first, then `created` | **includes** the new tokens | `RuleChainlinkPoRERC3643` | + +ERC-3643's `Token.mint` is explicit about it — and note it consults compliance **twice**, on either +side of the state change: + +```solidity +function mint(address _to, uint256 _amount) public override onlyAgent { + // ... + require(_tokenCompliance.canTransfer(address(0), _to, _amount), ComplianceNotFollowed()); + _mint(_to, _amount); // <-- supply changes here + _tokenCompliance.created(_to, _amount); // <-- rule notified afterwards +} +``` + +**ERC-3643 signals a mint with `created`, not `transferred`.** `RuleEngine` implements the full +`ICompliance` surface and forwards `created(to, value)` to each rule as the three-argument +`transferred(address(0), to, value)`, which is the shape every rule in this library already gates on +(`from == address(0)`). No rule-side change is needed for that; what changes is the accounting. + +### What each variant does with it + +Only the **write** path is re-phased. The variant overrides one hook: + +```solidity +function _detectTransferRestrictionOnNotify(address from, address to, uint256 /* value */) + internal view override returns (uint8) +{ + return _detectTransferRestriction(from, to, 0); // the supply already includes the mint +} +``` + +The **read** path is deliberately untouched: `detectTransferRestriction`, `canTransfer` and +`maxBackedSupply` still project the pending amount, because a pre-flight query always runs *before* +the movement — as the `require(... canTransfer ...)` line above shows, the ERC-3643 token depends on +it. Re-phasing the views too would make the pre-flight answer disagree with enforcement. + +The two consultations therefore reduce to the same condition, which is the property that makes the +variant correct: `canTransfer` asks `supply + amount <= reserves` before the mint, and `created` asks +`supply' <= reserves` after it, where `supply' == supply + amount`. + +### What goes wrong with the wrong variant + +| Deployment | Effect | +|---|---| +| Stock rule on an **ERC-3643** token | The amount is counted twice. `canTransfer` accepts the mint, the token mints, then `created` rejects it and the whole transaction reverts — **fully backed mints fail**. The largest single mint from an empty supply is halved to `reserves / 2`. It is not a uniform halving: a series of small mints can still creep up to the full reserves, so the failure looks intermittent and depends on how issuance is chunked. | +| This variant on a **CMTAT** token | The pending amount is ignored on enforcement. The pre-flight view still blocks an over-reserve mint, but the write hook would no longer stop one that slipped past — **the backing guarantee is weakened**. | + +## Deployment + +Constructors match the stock rule exactly. + +```solidity +new RuleChainlinkPoRERC3643( + admin, // DEFAULT_ADMIN_ROLE + tokenContract, // the ERC-3643 token; must expose totalSupply() + tokenDecimals, // 0–18, checked against decimals() when the token exposes it + reservesFeed, // AggregatorV3Interface + maxStalenessSeconds // 0 disables the staleness check +); +``` + +Wire it as a rule inside a `RuleEngine` occupying the token's **compliance** slot: + +``` +ERC-3643 Token ── compliance ──▶ RuleEngine ──▶ RuleChainlinkPoRERC3643 +``` + +Use `RuleEngine`, not a bare rule: ERC-3643 drives mint and burn through `created` / `destroyed`, +which the validation rules do not implement. `RuleEngine` implements the full `ICompliance` surface +and forwards them. + +`RuleChainlinkPoRERC3643Ownable2Step` is the same contract under `Ownable2Step` instead of +`AccessControl`. + +### ⚠️ Deployment order: build the rule AFTER `Token.init` + +ERC-3643 deploys the token and initialises it in two steps, and **an uninitialised `Token` reports +`decimals() == 0`**. The rule's constructor probes `decimals()` and accepts a matching value, so a +rule constructed before `init` is configured for a 0-decimals token — and `init(..., 18, ...)` then +makes it an 18-decimals token while the rule still believes 0. + +Nothing reverts and no event marks it. The reserve answer is simply scaled by `10 ** 18` too little +and every mint is refused; the same mistake with the decimals reversed would authorise **unbacked +minting** instead. The constructor probe cannot catch this — it genuinely succeeded at the time. + +- **Construct the rule after `token.init(...)`**, or +- call `setTokenMetadata(token, decimals)` once the token is initialised to re-sync. + +In a `TREXFactory.deployTREXSuite` flow the token address only exists after the factory call anyway, +so the natural order is: deploy the `RuleEngine`, deploy the suite with it as compliance, then deploy +the rule against the finished token and `engine.addRule(...)`. + +Pinned by `testRuleBuiltBeforeInitCachesTheWrongDecimals`. + +### On `CODE_TOTAL_SUPPLY_UNAVAILABLE` (78) + +`Token.totalSupply()` is `external view { return _totalSupply; }` — no modifier, no external call — +so it cannot revert, and code 78 is unreachable against a **directly deployed** ERC-3643 token. The +guarded read is still not dead weight: the standard T-REX deployment puts the token behind a +`TokenProxy` resolving its implementation through an `ImplementationAuthority`, and a proxy repointed +at a broken implementation *can* make the call revert. The rule then returns 78 and blocks minting +instead of breaking the MUST-NOT-revert views. Pinned by +`testSupplyIsAlwaysReadableOnADirectlyDeployedToken`. + +## Behaviour inherited unchanged + +- **Mints only.** Transfers and burns always pass, including while the feed is stale, broken or + reporting zero — a lapsed feed must never trap holders in their position. +- **Restriction codes** 75 (reserves exceeded), 76 (feed stale), 77 (answer unusable, including a + future-dated round), 78 (total supply unavailable), 79 (feed unreadable). +- **Live feed decimals**, never cached; `maxBackedSupply()` previews the ceiling; the read path never + reverts. See [`RuleChainlinkPoR`](./RuleChainlinkPoR.md) for the full treatment. +- **One token per instance.** The rule reads `totalSupply()` from its configured `tokenContract`, + never from the token that triggered the check, and cannot learn that identity behind a RuleEngine. + Do not add one instance to two engines. + +## Tests + +`test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol` drives the **genuine** vendored +`lib/ERC-3643/` token (4.2.0-beta1) — not a mock — through this rule: mints up to the reserves, +rejection past them, incremental issuance against a shared ceiling, a raised feed answer raising the +ceiling, transfers and burns staying open while reserves are zero, and a stale feed halting issuance +without trapping holders. Two tests pin the stock rule's failure on the same token so the reason this +variant exists stays executable. + +Run it with the dedicated profile, which `forge test` alone does **not** include: + +```bash +FOUNDRY_PROFILE=erc3643 forge test +``` + +## See also + +- [`RuleChainlinkPoR`](./RuleChainlinkPoR.md) — the CMTAT rule and the full PoR reference +- [`RULE_SEMANTICS.md` §5](../guides/RULE_SEMANTICS.md) — the two seams the cap rules expose, and why + a tracked-supply rule is a different design diff --git a/doc/technical/contracts/RuleConditionalTransferLight.md b/doc/technical/contracts/RuleConditionalTransferLight.md index b6da8586..bb33507e 100644 --- a/doc/technical/contracts/RuleConditionalTransferLight.md +++ b/doc/technical/contracts/RuleConditionalTransferLight.md @@ -65,6 +65,44 @@ Approves the transfer and immediately calls `SafeERC20.safeTransferFrom` on the Works in **both** topologies, provided the bindings are set correctly — see [Binding: token vs RuleEngine](#binding-token-vs-ruleengine). +#### It requires a token that calls back, and now checks that it did + +The helper **inverts checks-effects-interactions on purpose**: it records the approval *before* +`safeTransferFrom`, so the approval exists while the token runs its compliance callback into this rule and the +callback can consume it. That is only correct if the callback actually arrives. + +It ends with a post-condition: + +```solidity +uint256 approvalsBefore = approvedCount(from, to, value); +approveTransfer(from, to, value); +... +IERC20(token).safeTransferFrom(from, to, value); +require( + approvedCount(from, to, value) == approvalsBefore, + RuleConditionalTransferLight_ApprovalNotConsumed(token, from, to, value) +); +``` + +If the count did not come back down, no callback reached the rule — a plain ERC-20 bound with `bindToken`, or a +RuleEngine never bound or since unbound with `unbindRuleEngine`. Before this check the transfer **succeeded** and +left the approval standing, indistinguishable from an operator-created one and enough to authorise a later, +never-approved transfer of exactly `(from, to, value)`. The only remedy was for the operator to notice the +leftover count and call `resetApproval` (Nethermind AuditAgent `NM-17`). + +Points worth knowing: + +- It compares against the count **before** the helper ran, not against zero, so an operator's own outstanding + approvals for the same tuple survive untouched. +- Reading state *after* the external call is deliberate. A hostile token can only make the check **fail**, never + pass spuriously; a path that consumed more than one approval also fails, which is the direction you want. +- It is a **behaviour change** for a deployment that ran the helper against a non-callback token: that call now + reverts instead of completing. That is the point — the transfer was leaving a compliance hole behind. +- Cost: two warm `SLOAD`s on an operator-only path. + +Pinned by `testRevertsWhenTheTokenDoesNotCallBack`, `testPreExistingApprovalsSurviveTheHelper` and +`testDirectBindingFlowStillConsumesExactlyOne`. + ### `approvedCount(address from, address to, uint256 value) → uint256` Returns the current approval count for the `(from, to, value)` tuple. diff --git a/doc/technical/contracts/RuleConditionalTransferLightMultiToken.md b/doc/technical/contracts/RuleConditionalTransferLightMultiToken.md index 9798d0eb..82f9365b 100644 --- a/doc/technical/contracts/RuleConditionalTransferLightMultiToken.md +++ b/doc/technical/contracts/RuleConditionalTransferLightMultiToken.md @@ -109,6 +109,24 @@ Returns the remaining count for a specific token key. Approves and executes `safeTransferFrom` on the specified token, requiring allowance for this rule as spender. +Like its single-token twin, the helper **inverts checks-effects-interactions on purpose** — the approval is +recorded *before* `safeTransferFrom` so the token's compliance callback can consume it — and it now ends with a +post-condition asserting the approval was in fact consumed: + +```solidity +require( + approvedCount(token, from, to, value) == approvalsBefore, + RuleConditionalTransferLightMultiToken_ApprovalNotConsumed(token, from, to, value) +); +``` + +A count that did not come back down means no callback reached the rule — the token is not bound directly, or is +a plain ERC-20 that notifies nobody. Before this check the transfer completed and left a spendable approval for +`(token, from, to, value)` behind, enough to authorise a later never-approved transfer of that exact tuple +(Nethermind AuditAgent `NM-17`). The comparison is against the count **before** the helper ran, so an operator's +own outstanding approvals are untouched. Pinned by +`testApproveAndTransferRevertsWhenTheTokenDoesNotCallBack`. + ### `transferred(...)` Only bound tokens can call transfer execution hooks. Approval consumption uses the **caller** (`msg.sender`) as the token key, which is why the rule must be bound directly to each token. See [Deployment topology](#deployment-topology--why-a-ruleengine-does-not-work). diff --git a/doc/technical/contracts/RuleIdentityRegistry.md b/doc/technical/contracts/RuleIdentityRegistry.md index 33f05a58..bbae2396 100644 --- a/doc/technical/contracts/RuleIdentityRegistry.md +++ b/doc/technical/contracts/RuleIdentityRegistry.md @@ -105,6 +105,19 @@ Returns the current identity registry address. Returns `address(0)` if none is s This is what lets an **unverified minter** mint to a verified recipient, exactly as ERC-3643 requires (*"`mint` … only require[s] the receiver to be whitelisted and verified"*). +### Note for subclasses: the two hooks cannot diverge + +`_detectTransferRestrictionFrom` screens the spender and then **always delegates** to +`_detectTransferRestriction`, including when no registry is set and when the transfer is a burn. Those two cases +resolve to `TRANSFER_OK` inside the delegate, so the answer is unchanged — but the delegation is what guarantees +that a subclass overriding **only** `_detectTransferRestriction`, the natural hook for adding a check, has that +check honoured on `transferFrom` and `burnFrom` as well as on `transfer`. + +Until `v0.6.0` the function returned `TRANSFER_OK` directly in those two cases, so such a subclass silently +screened one entrypoint and not the other. `RuleSanctionsListBase` carries the same guarantee for the same +reason. If you extend either rule, override `_detectTransferRestriction` and leave the delegation intact; the +behaviour is pinned by `test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol`. + ## Usage scenario The operator deploys `RuleIdentityRegistry` and calls `setIdentityRegistry(registry)`. The registry is maintained by a compliance provider who verifies investor identities. When Alice (unverified) attempts to receive tokens, `isVerified(alice)` returns `false` and the transfer is rejected with code 56. After the registry marks Alice as verified, the transfer succeeds. Calling `clearIdentityRegistry()` disables checks entirely. diff --git a/doc/technical/contracts/RuleMaxTotalSupplyERC3643.md b/doc/technical/contracts/RuleMaxTotalSupplyERC3643.md new file mode 100644 index 00000000..e9c2fa98 --- /dev/null +++ b/doc/technical/contracts/RuleMaxTotalSupplyERC3643.md @@ -0,0 +1,130 @@ +# Rule Max Total Supply — ERC-3643 variant + +> ⚠️ **For ERC-3643 tokens only.** Use plain [`RuleMaxTotalSupply`](./RuleMaxTotalSupply.md) with CMTAT. +> The two are not interchangeable, and choosing the wrong one **silently** mis-caps issuance in one +> direction or the other. Nothing reverts at deployment to tell you. + +`RuleMaxTotalSupplyERC3643` and `RuleMaxTotalSupplyERC3643Ownable2Step` cap minting at a static +maximum supply, exactly like the stock rule. The cap logic, restriction codes (50, 51), +configuration, roles and events are **identical and inherited**. The only difference is *when the +token is assumed to report the mint*. + +## Why a separate variant: compliance is called AFTER the transfer + +A rule that caps a supply has to know whether the figure it reads already includes the amount being +minted. The two token families answer differently. + +| Token | Order on a mint | `totalSupply()` when the rule is notified | Use | +|---|---|---|---| +| **CMTAT** | rule first, then the mint | **excludes** the new tokens | [`RuleMaxTotalSupply`](./RuleMaxTotalSupply.md) | +| **ERC-3643 / T-REX** | mint first, then `created` | **includes** the new tokens | `RuleMaxTotalSupplyERC3643` | + +ERC-3643's `Token.mint` is explicit, and consults compliance **twice** — on either side of the state +change: + +```solidity +function mint(address _to, uint256 _amount) public override onlyAgent { + // ... + require(_tokenCompliance.canTransfer(address(0), _to, _amount), ComplianceNotFollowed()); + _mint(_to, _amount); // <-- supply changes here + _tokenCompliance.created(_to, _amount); // <-- rule notified afterwards +} +``` + +**ERC-3643 signals a mint with `created`, not `transferred`.** `RuleEngine` implements the full +`ICompliance` surface and forwards `created(to, value)` to each rule as the three-argument +`transferred(address(0), to, value)`, which is the shape every rule already gates on +(`from == address(0)`). No rule-side change is needed for the signal; what changes is the accounting. + +### What each variant does with it + +Only the **write** path is re-phased. The variant overrides one hook: + +```solidity +function _detectTransferRestrictionOnNotify(address from, address to, uint256 /* value */) + internal view override returns (uint8) +{ + return _detectTransferRestriction(from, to, 0); // the supply already includes the mint +} +``` + +The **read** path is deliberately untouched: `detectTransferRestriction` and `canTransfer` still +project the pending amount, because a pre-flight query always runs *before* the movement — as the +`require(... canTransfer ...)` line above shows, the ERC-3643 token depends on it. Re-phasing the +views too would make the pre-flight answer disagree with enforcement. + +The two consultations therefore reduce to the same condition, which is what makes the variant +correct: `canTransfer` asks `supply + amount <= cap` before the mint, and `created` asks +`supply' <= cap` after it, where `supply' == supply + amount`. + +### What goes wrong with the wrong variant + +| Deployment | Effect | +|---|---| +| Stock rule on an **ERC-3643** token | The amount is counted twice. `canTransfer` accepts the mint, the token mints, then `created` rejects it and the whole transaction reverts — **mints within the ceiling fail**. The largest single mint from an empty supply is halved to `cap / 2`. It is not a uniform halving: a series of small mints can still creep to the full cap, so the failure looks intermittent and depends on how issuance is chunked. | +| This variant on a **CMTAT** token | The pending amount is ignored on enforcement. The pre-flight view still blocks an over-cap mint, but the write hook would no longer stop one that slipped past — **the ceiling is weakened**. | + +## Deployment + +Constructors match the stock rule exactly. + +```solidity +new RuleMaxTotalSupplyERC3643( + admin, // DEFAULT_ADMIN_ROLE + tokenContract, // the ERC-3643 token; must expose totalSupply() + maxTotalSupply // the ceiling +); +``` + +Wire it as a rule inside a `RuleEngine` occupying the token's **compliance** slot: + +``` +ERC-3643 Token ── compliance ──▶ RuleEngine ──▶ RuleMaxTotalSupplyERC3643 +``` + +Use `RuleEngine`, not a bare rule: ERC-3643 drives mint and burn through `created` / `destroyed`, +which the validation rules do not implement. + +`RuleMaxTotalSupplyERC3643Ownable2Step` is the same contract under `Ownable2Step` instead of +`AccessControl`. + +### Composing with Proof of Reserve + +[`RuleChainlinkPoRERC3643`](./RuleChainlinkPoRERC3643.md) caps minting at the reported reserves with +**no margin parameter**, so pair the two when a static ceiling is wanted alongside the reserve-backed +one. Add both to the same engine; whichever limit binds first stops the mint. The engine returns the +**first non-zero code**, so rule order decides whether a rejection is reported as `50` or `75`. Both +orderings are exercised in the test suite below. + +## Behaviour inherited unchanged + +- **Mints only.** Transfers always pass; burns always pass and *free headroom*, because the cap is on + supply rather than on cumulative issuance. +- **Restriction codes** 50 (max total supply exceeded) and 51 (total supply unavailable — the token + reverted or lost its code; fail-closed, and the read path still never reverts). +- **Lowering the cap below the current supply** does not claw anything back; it simply blocks further + mints until burns bring the supply back under. +- **One token per instance.** The rule reads `totalSupply()` from its configured `tokenContract`, + never from the token that triggered the check, and cannot learn that identity behind a RuleEngine. + Do not add one instance to two engines. + +## Tests + +- `test/RuleMaxTotalSupply/RuleMaxTotalSupplyERC3643.t.sol` — unit coverage in the default profile. +- `test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol` — drives the **genuine** vendored + `lib/ERC-3643/` token (4.2.0-beta1), not a mock: mints to the ceiling, rejection past it, + incremental issuance, burns freeing headroom, a raised cap raising the ceiling, and both + compositions with the Proof-of-Reserve variant. Two tests pin the stock rule's failure on the same + token so the reason this variant exists stays executable. + +The real-token suite needs the dedicated profile, which `forge test` alone does **not** include: + +```bash +FOUNDRY_PROFILE=erc3643 forge test +``` + +## See also + +- [`RuleMaxTotalSupply`](./RuleMaxTotalSupply.md) — the CMTAT rule and the full reference +- [`RuleChainlinkPoRERC3643`](./RuleChainlinkPoRERC3643.md) — the reserve-backed sibling +- [`RULE_SEMANTICS.md` §5](../guides/RULE_SEMANTICS.md) — the two seams the cap rules expose diff --git a/doc/technical/contracts/RuleWhitelistWrapper.md b/doc/technical/contracts/RuleWhitelistWrapper.md index ded4baa0..bfbec82f 100644 --- a/doc/technical/contracts/RuleWhitelistWrapper.md +++ b/doc/technical/contracts/RuleWhitelistWrapper.md @@ -6,7 +6,9 @@ This rule aggregates multiple child whitelist rules using OR logic. An address i ## Architecture -Each child rule must implement `IAddressList`. The wrapper iterates through all registered rules and returns `true` for an address as soon as one rule lists it. Iteration stops early once all required addresses are resolved. +Each child rule must implement `IAddressList` **and must be an allow-list**. The wrapper iterates through all registered rules and returns `true` for an address as soon as one rule lists it. Iteration stops early once all required addresses are resolved. + +> ⚠️ **`IAddressList` carries membership, not polarity.** The wrapper reads a child's `areAddressesListed` answer and treats `true` as *eligible*. It has no way to ask whether the child meant "allowed" or "denied", and nothing in `addRule` constrains that — see [Child rules must be allow-lists](#child-rules-must-be-allow-lists). ![ruleWhitelistWrapper.drawio](../../schema/rule/ruleWhitelistWrapper.drawio.png) @@ -71,13 +73,140 @@ The wrapper reuses restriction codes from the whitelist rule: | `removeRule(address rule_)` | `RULES_MANAGEMENT_ROLE` | Removes a single child rule | | `clearRules()` | `RULES_MANAGEMENT_ROLE` | Removes all child rules | +#### Child rules must be allow-lists + +**The wrapper cannot tell an allow-list from a deny-list, and adding the wrong one inverts its meaning.** + +`IAddressList` expresses only *membership* — "is this address in my set?" — never what membership means. The +wrapper ORs those answers and reads `true` as **eligible**. A `RuleBlacklist` is a perfectly valid `IRule`, +exposes the same `IAddressList` surface, and passes every check `addRule` performs, but its set means the +opposite: listed addresses are the ones that must be **denied**. + +Add a `RuleBlacklist` as a child and the wrapper reports its blacklisted addresses as whitelisted. Because the +wrapper is also the token's `isVerified` answer under ERC-3643, `isVerified(blacklistedAddress)` returns `true` +as well. + +| Safe as a child | Not a child | +| --- | --- | +| `RuleWhitelist`, `RuleWhitelistOwnable2Step` | `RuleBlacklist` — inverted polarity | +| `RuleReceiverWhitelist`, `RuleReceiverWhitelistOwnable2Step` | `RuleSpenderWhitelist` — its set is spenders, not holders | +| Any custom rule whose listed addresses are the **permitted** ones | Any rule whose `IAddressList` set means something other than "eligible holder" | + +**This is now enforced, not merely documented.** It could not be caught by ERC-165 alone — `RuleBlacklist` +advertises the same `IAddressList` ids as the whitelist rules, because `IAddressList` describes *membership* and +both kinds of list have members. The fix is the separate marker interface that observation implies: +[`IAddressListPolarity`](#child-rules-are-erc-165-checked) adds a single `isAllowList()` function, the wrapper +requires it and refuses any child answering `false`. Pinned by `test_WW2_DenyListChildIsRejectedAtAddRule`. + +#### Children are ERC-165-checked + +`addRule` and `setRules` both route through `_checkRule`, which requires the candidate to advertise +**`IAddressListBatchQuery`** via ERC-165, on top of the inherited non-zero and not-already-present checks. A +candidate that does not is rejected with `RuleWhitelistWrapper_ChildIsNotAnAddressList(rule)`. + +This closes the failure where a valid `IRule` that is not an address list — `RuleMaxTotalSupply`, say — was +accepted and then reverted the blind `areAddressesListed` call during a transfer. The early exit in the child +scan made that *input-dependent*: an address pair already resolved by an earlier child still worked, so the +wrapper looked healthy right up until a pair that needed the full scan (audit `F-5`, Nethermind AuditAgent +`NM-18`). It also refuses a **nested wrapper**, which does not implement `areAddressesListed` and would brick the +parent the same way. + +`ERC165Checker.supportsInterface` is itself non-reverting — a bounded staticcall returning `false` for a codeless +address, a missing selector or malformed return data — so a hostile candidate cannot brick the setter that is +screening it. + +##### Two questions, two interfaces + +Membership and meaning are different questions, so the guard asks both: + +| Requirement | Interface | Failure | +| --- | --- | --- | +| Can you answer "is this address listed?" | `IAddressListBatchQuery` (`0x20e8e17a`) | `RuleWhitelistWrapper_ChildIsNotAnAddressList` | +| Do you declare what membership *means*? | `IAddressListPolarity` (`0xdc4efe10`) | `RuleWhitelistWrapper_ChildDoesNotDeclarePolarity` | +| Does it mean **allowed**? | `isAllowList() == true` | `RuleWhitelistWrapper_ChildIsNotAnAllowList` | + +**Absence of the polarity declaration is a refusal, never an assumed allow-list.** That is the only reading that +fails closed for a contract predating the interface or deliberately declining it. + +What each rule declares: + +| Rule | `isAllowList()` | As a wrapper child | +| --- | --- | --- | +| `RuleWhitelist` | `true` | ✅ accepted | +| `RuleReceiverWhitelist` | `true` | ✅ accepted | +| `RuleBlacklist` | `false` | ❌ rejected — deny-list | +| `RuleSpenderWhitelist` | *does not implement the interface* | ❌ rejected — see below | +| `RuleWhitelistWrapper` (nested) | *does not implement `areAddressesListed`* | ❌ rejected at the first check | + +`RuleSpenderWhitelist` **deliberately abstains, and must not be "fixed" to declare `true`.** Its set genuinely is +an allow-list, so `true` would be honest about polarity and still wrong: the listed addresses are permitted +*spenders*, not permitted *holders*, and the wrapper would read them as eligible transfer participants. Polarity +is only half the question; the other half is what the addresses are. Withholding the declaration is what makes +the fail-closed check refuse it — pinned by `test_WW2_ChildDecliningToDeclarePolarityIsRejected`. + +##### Wrappers cannot nest, deliberately + +A `RuleWhitelistWrapper` does not implement `areAddressesListed`, so it fails the first check and cannot be a +child of another wrapper. That is a decision, not an omission (Nethermind AuditAgent `NM-19`, declined). + +**Nesting would buy no expressive power.** The wrapper is an OR, and `OR(OR(a,b), OR(c,d))` ≡ `OR(a,b,c,d)` — an +OR nested in an OR flattens. Every policy a nested wrapper could express is expressible with a flat child list, +and the composition integrators actually reach for is already available one level up: + +| Composition | How | +| --- | --- | +| **OR** of lists | one wrapper, flat children | +| **AND** of ORs | several wrappers in the `RuleEngine`, which returns the first non-zero code | +| OR of ORs | identical to a flat wrapper | + +It would also cost. The scan is [~8.8k gas per child](#gas-cost-of-the-child-rule-scan) and the *rejected* path +never early-exits, so a 10 × 10 nest costs **~880k gas per transfer** where the equivalent flat wrapper costs +**~90k** — the same policy at ten times the price, paid by every transferring holder. And it would open a cycle +class (`A → B → A`) that recurses to out-of-gas, bricking transfers *and* `isVerified`, with no cheap on-chain +defence. + +Delegated administration — the real motivation — already works flat: see the [usage scenario](#usage-scenario), +where three operators each manage their own `RuleWhitelist` under one wrapper. + +##### Why the check asks for a sub-interface, not all of `IAddressList` + +The wrapper calls **one** function on its children: + +```solidity +bool[] memory isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress); +``` + +`IAddressList` declares eight (`addAddress`, `removeAddress`, `addAddresses`, `removeAddresses`, +`listedAddressCount`, `isAddressListed`, `areAddressesListed`, and `contains` inherited from +`IIdentityRegistryContains`). Requiring the full id would demand seven functions the wrapper never touches — +including all four **write** functions, which a read-only aggregating child has no reason to expose — and reject +an otherwise perfectly serviceable child. An ERC-165 check should ask for what is actually called. + +`IAddressListBatchQuery` therefore declares `areAddressesListed` alone, and `IAddressList` inherits it: + +| Constant | Value | Covers | +| --- | --- | --- | +| `IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID` | `0x20e8e17a` | `areAddressesListed(address[])` — **what the wrapper requires** | +| `IADDRESS_LIST_INTERFACE_ID` | `0x5d10e182` | the full eight-selector hierarchy | + +Factoring the selector into a parent left the flattened set unchanged, so `0x5d10e182` keeps its value and every +rule advertises both ids. The sub-interface id is safe to state as a literal, unlike the full one: it declares a +single function and inherits nothing, so there is no omitted-parent trap. All of this is asserted in +`test/InterfaceId/AddressListInterfaceId.t.sol`. + +This is a category error by a trusted role rather than an attack — the same role can already remove every child +outright, which fails closed — but it fails **open**, silently, so it is worth checking at configuration time and +in any deployment review. Reported as Nethermind AuditAgent `NM-20`. + ### `setCheckSpender(bool value)` Enables or disables spender checks. Restricted to `DEFAULT_ADMIN_ROLE`. ### `isVerified(address targetAddress) → bool` -Returns `true` if the address is listed in at least one child rule. +Returns `true` if the address is listed in at least one child rule. This is the ERC-3643 eligibility answer, and +it resolves through the same child scan as the transfer check, so the two can never disagree about an address — +including when a child's polarity is wrong (see [Child rules must be allow-lists](#child-rules-must-be-allow-lists)). ### `rule(uint256 index) → address` diff --git a/doc/technical/guides/RULE_SEMANTICS.md b/doc/technical/guides/RULE_SEMANTICS.md index b66a9f0a..168031ba 100644 --- a/doc/technical/guides/RULE_SEMANTICS.md +++ b/doc/technical/guides/RULE_SEMANTICS.md @@ -35,7 +35,7 @@ Legend: ✅ screened / can block · ❌ not screened · ⚙️ conditional (see | Rule | When its oracle/registry is unset | Stateful on transfer? [7] | Authoritative pre-flight view | Restriction codes | |---|---|---|---|---| | `RuleWhitelist` | n/a (local address set) | ❌ | `canTransfer` / `canTransferFrom` | 21–25 | -| `RuleWhitelistWrapper` | empty wrapper ⇒ **all rejected** (fail-closed) | ❌ | `canTransfer` / `canTransferFrom` | 21–25 | +| `RuleWhitelistWrapper` | empty wrapper ⇒ **all rejected** (fail-closed); children must be allow-lists [12b] | ❌ | `canTransfer` / `canTransferFrom` | 21–25 | | `RuleReceiverWhitelist` | n/a (local address set) | ❌ | `canTransfer` / `canTransferFrom` | 81 | | `RuleSpenderWhitelist` | n/a (local address set) | ❌ | `canTransfer` (always ✓) / `canTransferFrom` | 66 | | `RuleBlacklist` | n/a (local address set) | ❌ | `canTransfer` / `canTransferFrom` | 36–38 | @@ -70,7 +70,17 @@ Not every rule exposes the same entrypoints. The ERC-7943 `tokenId` overloads an | `RuleConditionalTransferLightMultiToken` | ❌ | ✅ | ❌ | | `RuleMintAllowance` | ❌ | ❌ | ❌ | -The `tokenId` parameter is **always ignored** by the rules that accept it — `RuleNFTAdapter` exists purely to re-expose the same restriction logic under the ERC-7943 signatures. The `tokenId` overload of any function therefore returns exactly what its fungible counterpart returns, and the `ctx` entrypoints dispatch to the same internal hooks (`ctx.sender == 0` or `ctx.sender == ctx.from` ⇒ the direct hook; otherwise the spender-aware hook). This parity is asserted for every rule above in `test/TransferContext/OverloadParity.t.sol`. +The `tokenId` parameter is **always ignored** by the rules that accept it — `RuleNFTAdapter` exists purely to re-expose the same restriction logic under the ERC-7943 signatures. Entrypoints describing the same transfer therefore return the same answer, asserted for every rule above in `test/TransferContext/OverloadParity.t.sol`. + +**How each interface signals a direct transfer differs, and that decides the routing.** An owner moving their own tokens reaches the adapter as `spender == from` on the ERC-7943 overloads (the spec calls that parameter "the address performing the transfer (owner/operator)") and as `sender == from` on the `ctx` entrypoints, whereas the CMTAT path signals it with the 3-arg overload or `spender == address(0)`: + +| Interface | Direct transfer arrives as | Delegated transfer arrives as | +|---|---|---| +| CMTAT 3-arg / 4-arg | the 3-arg overload, or `spender == address(0)` | `spender != address(0)` | +| ERC-7943 `tokenId` overloads | `spender == from` | `spender != from` | +| `ITransferContext` | `sender == from`, or `sender == address(0)` | `sender != from` | + +Every adapter entrypoint normalises `spender == from` to the **direct** hook. The 4-arg CMTAT path deliberately does not, because its own convention already distinguishes the two — so `4-arg(spender == from)` and the ERC-7943 5-arg call with the same arguments describe *different* transfers and are expected to differ. Do not "align" them: an owner-initiated ERC-721 `transferFrom` would then be screened as delegated, which `RuleSpenderWhitelist` documents as always allowed. Both halves are pinned by `test_NM6_SelfSpenderIsNotScreenedByTheSpenderWhitelist` and `test_NM6_CmtatFourArgPathKeepsScreeningASelfSpender`. **Access control on the `ctx` entrypoints (threat `AC-5`).** `transferred(FungibleTransferContext)` / `transferred(MultiTokenTransferContext)` are `external` with **no caller restriction** on the validation rules. That is safe because those rules' hooks are `view`: an arbitrary caller can run the check and be reverted by it, but cannot mutate any state. The stateful multi-token rule guards its own `ctx` entrypoint with `onlyTransferExecutor`. @@ -105,8 +115,123 @@ The `tokenId` parameter is **always ignored** by the rules that accept it — `R 11. **`RuleMintAllowance.canTransfer` / `detectTransferRestriction` are NOT authoritative** (finding **F-7**): they are hardcoded to "allowed" because the 3-arg signature has no minter identity. Pre-flight a mint with `canTransferFrom(minter, address(0), to, value)`. See [RuleMintAllowance.md](../contracts/RuleMintAllowance.md#eligibility-views-which-one-is-authoritative). +12b. **`RuleWhitelistWrapper` children must be ALLOW-lists, and this is now enforced.** `IAddressList` carries *membership*, not polarity: the wrapper ORs its children's `areAddressesListed` answers and reads `true` as **eligible**. A `RuleBlacklist` implements that interface identically and advertises the same ids, so ERC-165 alone could not tell them apart — adding one made its blacklisted addresses whitelisted and `isVerified` returned `true` for them (`NM-20`). Polarity is now declared rather than inferred: **`IAddressListPolarity`** (`0xdc4efe10`) adds a single `isAllowList()`, and `addRule` requires the interface *and* a `true` answer, on top of the `IAddressListBatchQuery` check from `NM-18`. **Absence of the polarity declaration is a refusal, never an assumed allow-list** — the only reading that fails closed. `RuleWhitelist` and `RuleReceiverWhitelist` declare `true`; `RuleBlacklist` declares `false`; `RuleSpenderWhitelist` deliberately declines, because its set is permitted *spenders* rather than permitted *holders* and polarity alone would mislead. A nested wrapper is refused at the first check, since it does not implement `areAddressesListed` — **deliberately**: an OR nested in an OR is algebraically flat (`OR(OR(a,b),OR(c,d))` ≡ `OR(a,b,c,d)`), so nesting adds no expressive power, while costing multiplicatively (~8.8k gas per child, and the rejected path never early-exits) and opening an `A → B → A` cycle class with no cheap on-chain defence. AND-of-ORs is available by putting several wrappers in the `RuleEngine`, which returns the first non-zero code. `NM-19`, declined. + 12. **The ERC-7943 `tokenId` overloads** are `detectTransferRestriction(from,to,tokenId,value)`, `detectTransferRestrictionFrom(spender,from,to,tokenId,value)`, `canTransfer(from,to,tokenId,amount)`, `canTransferFrom(spender,from,to,tokenId,value)`, `transferred(from,to,tokenId,value)` and `transferred(spender,from,to,tokenId,value)` — all supplied by `RuleNFTAdapter`. Per ERC-7943, `amount`/`value` MUST be `1` for ERC-721. The rules ignore `tokenId` entirely; it exists so an ERC-721/ERC-1155 token can call the same compliance rule without a shim. +## 5. Cap rules: the two seams for an ERC-3643 variant + +`RuleMaxBalance`, `RuleMaxTotalSupply` and `RuleChainlinkPoR` all end in the same question — *would this movement leave an observed figure above its cap?* — and that question has two free variables. Each is a documented `internal virtual` hook, so a variant overrides one line rather than reimplementing a rule. + +The shared arithmetic lives in [`CapAccounting`](../../../src/rules/validation/abstract/core/CapAccounting.sol), which holds **no storage** and is deliberately ignorant of both variables. + +### Seam 1 — accounting phase: `_detectTransferRestrictionOnNotify` + +**The stock rules assume the token calls them BEFORE it moves the value**, so the observation still excludes it and `value` must be counted. CMTAT does this. **ERC-3643 / T-REX calls afterwards** — `Token.transfer` runs `_transfer` then `_tokenCompliance.transferred`, and `mint` runs `_mint` then `created` — so the observation already includes the value and counting it again **halves the effective cap**, rejecting movements that are within it (Nethermind AuditAgent NM-11). + +Adapting is one override, because "the observation already includes it" is the same as "there is nothing left to add": + +```solidity +function _detectTransferRestrictionOnNotify(address from, address to, uint256) + internal view override returns (uint8) +{ + return _detectTransferRestriction(from, to, 0); +} +``` + +**Only the write path is routed through this hook, never the read path.** A pre-flight view (`detectTransferRestriction`, `canTransfer`, `remainingCapacity`) always runs *before* the movement on either kind of token, so it must always project `value`. Re-phasing it too would make the pre-flight answer disagree with enforcement — the mirror image of the bug being fixed. + +### Seam 2 — observation source: `_currentSupply` / `_balanceOf` + +Both are `internal view virtual`, so a rule may serve the figure from **its own storage** instead of calling the token — the shape needed for a rule that tracks the supply itself from an opening figure set at the start of the token's life. + +A rule that keeps its own running total also controls *when* it updates it, so it checks before it records and **seam 1 stops applying to it**: it never depends on the host token's call order. + +Two constraints before building one: + +- **It must observe every change or it drifts, permanently and silently.** Being installed after issuance has begun, removed and re-added, or served by a second engine all desynchronise it. A rule that reads the token self-heals; an accumulator does not. +- **Supply can be tracked; per-address balances are version-dependent and therefore unsafe.** How `Token.recoveryAddress` moves a balance **changed across T-REX versions**: up to 4.1 it routed through the public `forcedTransfer`, which *does* call `_tokenCompliance.transferred`; the vendored **4.2.0-beta1 calls `_transfer` directly and notifies nobody** (verified: zero `_tokenCompliance` references in that function body). A tracked per-address ledger is therefore in sync on one minor version and permanently skewed on the next, by an agent-callable path with no on-chain signal — a dependency no rule should carry. Total supply is unaffected by recovery either way, so a tracked-supply rule is safe from this. +- A tracked rule's write hook mutates state, so it belongs under `src/rules/operation/`, not `src/rules/validation/`. + +### Worked examples + +`src/mocks/harness/ERC3643CapHarnesses.sol` implements all four (one per cap rule for seam 1, plus a tracked-supply rule for seam 2), and `test/CapAccounting/ERC3643CapSeams.t.sol` asserts that the stock rules double-count under post-update accounting while the variants do not, and that neither ever admits anything above the cap. + +--- + +## 6. ERC-3643 compatibility — which rules actually work on a T-REX token + +A rule's guarantees depend on **what the token tells it and when**. CMTAT and ERC-3643 / T-REX differ on both, +so a rule that is correct on one can be inert or wrong on the other — silently, with nothing reverting at +deployment. This section is the per-rule answer. + +### The two differences that cause everything below + +| | CMTAT | ERC-3643 / T-REX | +|---|---|---| +| **Spender** | forwarded on the 4-arg `transferred(spender, from, to, value)` (v3.3+) | **never forwarded** — `transfer` *and* `transferFrom` both call the 3-arg `transferred(from, to, value)` | +| **Ordering** | rule called **before** the value moves | rule called **after** — `_transfer` then `transferred`; `_mint` then `created` | +| **Mint signal** | 4-arg `transferred(minter, address(0), to, value)` | `created(to, value)`, which `RuleEngine` forwards as 3-arg `transferred(address(0), to, value)` | + +Both paths also call `canTransfer` **before** the movement, so the read views are unaffected by the ordering +difference and must always project the pending amount. + +### Per-rule status + +| Rule | On ERC-3643 | Why | +|---|---|---| +| `RuleWhitelist` | ✅ works, one flag inert | `from`/`to` arrive on the 3-arg path. **`checkSpender` never fires** | +| `RuleWhitelistWrapper` | ✅ works, one flag inert | as above | +| `RuleReceiverWhitelist` | ✅ works | screens `to` only, which the 3-arg path carries | +| `RuleBlacklist` | ✅ works, spender leg inert | blocks a listed `from`/`to`; a listed **spender** moving someone else's tokens is not caught | +| `RuleSanctionsList` | ✅ works, spender leg inert | as above | +| `RuleERC2980` | ✅ works, spender leg inert | whitelist and frozen checks on `from`/`to` fire; the frozen-**spender** leg does not | +| `RuleIdentityRegistry` | ✅ works, one flag inert | `to` is screened. **`checkSpender` never fires**. Usually redundant anyway: the token already calls `isVerified(_to)` itself | +| `RuleMaxTotalSupply` | ⚠️ **use `RuleMaxTotalSupplyERC3643`** | post-update ordering ⇒ the amount is counted twice and fully-backed mints revert | +| `RuleChainlinkPoR` | ⚠️ **use `RuleChainlinkPoRERC3643`** | same | +| `RuleMaxBalance` | ❌ **not supported** | same double-count, and no variant exists — see below | +| `RuleConditionalTransferLight` | ✅ works | approvals are keyed `(from, to, value)`; ordering is irrelevant to consuming one. Requires `bindRuleEngine` | +| `RuleConditionalTransferLightMultiToken` | ❌ **not supported** | direct-binding only, and ERC-3643 needs the engine for `created` / `destroyed` | +| `RuleMintAllowance` | ❌ **inert** | the quota is debited only on the 4-arg path; `created` arrives with no minter identity, so nothing is debited and every mint passes | + +### Reading the three failure modes + +They are not equally dangerous, and the difference matters more than the symbol: + +- **"one flag / leg inert"** — the rule enforces less than its configuration suggests. Fail-**open** for that + leg: an operator who set `checkSpender = true` gets no spender screening and no signal. The `from`/`to` + screening is unaffected, so the rule still does its main job. +- **"use the ERC-3643 variant"** — the stock rule fails **closed**: it rejects mints that are within the cap. + Nothing is over-issued, but issuance breaks in a way that looks intermittent, because only the amount *in + flight* is double-counted. The variants re-phase the write path only. +- **"inert" / "not supported"** — the rule enforces **nothing**, or cannot be wired at all. `RuleMintAllowance` + is the one to watch: it is silently permissive rather than restrictive, and its pre-flight view says + `TRANSFER_OK` too, so neither the token nor an integrator sees a problem. + +### Why `RuleMaxBalance` has no ERC-3643 variant + +Not effort — a policy decision that has not been made. Its observation is **per-address**, so unlike the two +supply rules it engages on every transfer, and two T-REX agent powers interact badly with a post-update variant: + +- **`forcedTransfer` does notify compliance**, so the variant would *revert* an agent's forced transfer that + pushes the recipient over the cap. On T-REX ≤ 4.1, where `recoveryAddress` routes through `forcedTransfer`, + that **bricks wallet recovery** whenever the destination already holds tokens. +- On the vendored **4.2.0-beta1 `recoveryAddress` notifies nobody**, so a recovered wallet can silently sit + above the cap. A token-reading rule self-heals — further receipts are blocked — but the invariant is violated + in state with no event. + +Whether an agent action should be cap-exempt is the question to settle first. T-REX's own module library also +ships a `MaxBalanceModule`, so the marginal value is the lowest of the three. + +### Wiring, whichever rules you choose + +Use a **`RuleEngine`**, never a bare rule. ERC-3643 drives mint and burn through `created` / `destroyed`, which +the validation rules do not implement; the engine implements the full `ICompliance` surface and forwards them. +And build a `RuleChainlinkPoRERC3643` **after** `Token.init` — an uninitialised token reports `decimals() == 0`, +which the rule's constructor accepts and caches. + +--- + --- See [`CLAUDE_AUDIT.md`](../../security/audits/tools/v0.4.0/claude-audit/CLAUDE_AUDIT.md) for the findings referenced above. diff --git a/lib/RuleEngine b/lib/RuleEngine index ab9def2f..ca75429c 160000 --- a/lib/RuleEngine +++ b/lib/RuleEngine @@ -1 +1 @@ -Subproject commit ab9def2f19ae71af304127f42d20d9831cad1a2b +Subproject commit ca75429c581a2eb9043e4719561e941d0b2e1206 diff --git a/src/mocks/IERC3643ComplianceFull.sol b/src/mocks/IERC3643ComplianceFull.sol index 77f43e40..fd8fb294 100644 --- a/src/mocks/IERC3643ComplianceFull.sol +++ b/src/mocks/IERC3643ComplianceFull.sol @@ -7,8 +7,11 @@ pragma solidity ^0.8.20; * including functions inherited by IERC3643Compliance from its parent interfaces * (IERC3643ComplianceRead.canTransfer, IERC3643IComplianceContract.transferred). * - * Purpose: computing the correct ERC-165 interface ID for the full ERC-3643 - * ICompliance interface via `type(IERC3643ComplianceFull).interfaceId`. + * Purpose: pinning the ERC-165 interface ID of the full ERC-3643 ICompliance + * interface from an independent source. The rules themselves advertise + * `ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID`, which RuleEngine derives from + * its own interface hierarchy; this flat redeclaration is the cross-check that the + * derivation still yields the wire value, so a refactor upstream cannot silently move it. * * Background: `type(IFoo).interfaceId` only XORs selectors defined *directly* on * `IFoo`, not those inherited from parent interfaces. Using `type(IERC3643Compliance).interfaceId` diff --git a/src/mocks/harness/ERC3643CapHarnesses.sol b/src/mocks/harness/ERC3643CapHarnesses.sol new file mode 100644 index 00000000..8de03646 --- /dev/null +++ b/src/mocks/harness/ERC3643CapHarnesses.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {AggregatorV3Interface} from "../../rules/interfaces/AggregatorV3Interface.sol"; +import {ITotalSupply} from "../../rules/interfaces/ITotalSupply.sol"; +import {RuleChainlinkPoR} from "../../rules/validation/deployment/RuleChainlinkPoR.sol"; +import {RuleMaxBalance} from "../../rules/validation/deployment/RuleMaxBalance.sol"; +import {RuleMaxTotalSupply} from "../../rules/validation/deployment/RuleMaxTotalSupply.sol"; + +/** + * @title ERC-3643 cap-rule harnesses + * @notice Worked examples of the two seams the cap rules expose, used by + * `test/CapAccounting/ERC3643CapSeams.t.sol` to prove they are sufficient. + * + * @dev **Seam 1 — accounting phase (`_detectTransferRestrictionOnNotify`).** CMTAT calls a rule + * BEFORE it moves the value, so the observation excludes it. ERC-3643 / T-REX calls AFTER, so the + * observation already includes it and counting `value` again halves the effective cap. Overriding the + * notification hook to re-ask with `value = 0` is the whole adaptation. + * + * @dev **Seam 2 — observation source (`_currentSupply` / `_balanceOf`).** Both are `internal view + * virtual`, so a rule may serve the figure from its own storage instead of calling the token. A rule + * that keeps its own running total also controls when it is updated, which makes seam 1 moot for it. + * + * These are test doubles, not deployable rules. See + * `doc/technical/guides/RULE_SEMANTICS.md` for the write-up. + */ + +/// @notice `RuleMaxTotalSupply` for a token that notifies after minting. +contract ERC3643MaxTotalSupplyHarness is RuleMaxTotalSupply { + constructor(address admin, address tokenContract_, uint256 maxTotalSupply_) + RuleMaxTotalSupply(admin, tokenContract_, maxTotalSupply_) + {} + + /// @dev Seam 1: the observation already includes the minted value. + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + override + returns (uint8) + { + // `totalSupply()` already includes the mint, so there is nothing left to add. + return _detectTransferRestriction(from, to, 0); + } +} + +/// @notice `RuleMaxBalance` for a token that notifies after moving the value. +contract ERC3643MaxBalanceHarness is RuleMaxBalance { + constructor(address admin, address balanceToken_, uint256 maxBalance_) + RuleMaxBalance(admin, balanceToken_, maxBalance_) + {} + + /// @dev Seam 1: the observation already includes the received value. + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + override + returns (uint8) + { + // `balanceOf(to)` already includes the received value. + return _detectTransferRestriction(from, to, 0); + } +} + +/// @notice `RuleChainlinkPoR` for a token that notifies after minting. +contract ERC3643ChainlinkPoRHarness is RuleChainlinkPoR { + constructor( + address admin, + address tokenContract_, + uint8 tokenDecimals_, + AggregatorV3Interface reservesFeed_, + uint256 maxStalenessSeconds_ + ) RuleChainlinkPoR(admin, tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_) {} + + /// @dev Seam 1: the observation already includes the minted value. + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + override + returns (uint8) + { + return _detectTransferRestriction(from, to, 0); + } +} + +/** + * @notice `RuleMaxTotalSupply` serving the supply from its OWN storage instead of the token. + * @dev Demonstrates seam 2. A real version would maintain {trackedSupply} from the write hook and is + * a larger design: it must observe every supply change or it drifts. The same approach is NOT safe for + * per-address balances: `Token.recoveryAddress` notifies compliance on T-REX <= 4.1 (it calls the public + * `forcedTransfer`) but not on 4.2.0-beta1 (it calls `_transfer` directly), so a shadow ledger's + * correctness would depend on the token's minor version. Total supply is unaffected by recovery. + */ +contract TrackedSupplyHarness is RuleMaxTotalSupply { + /// @notice Supply as this rule believes it to be; never read from the token. + uint256 public trackedSupply; + + constructor(address admin, address tokenContract_, uint256 maxTotalSupply_) + RuleMaxTotalSupply(admin, tokenContract_, maxTotalSupply_) + {} + + /// @notice Seeds the opening figure; a real rule would restrict and one-shot this. + function setTrackedSupply(uint256 supply) external { + trackedSupply = supply; + } + + /// @dev Seam 2: the observation comes from storage, never from the token. + function _currentSupply() internal view override returns (bool available, uint256 supply) { + return (true, trackedSupply); + } + + /// @dev Unused on the read path once {_currentSupply} is overridden. + function _supplyToken() internal view override returns (ITotalSupply) { + // Never consulted on the read path; kept so configuration stays valid. + return tokenContract; + } +} diff --git a/src/mocks/harness/IdentityRegistryDelegationHarness.sol b/src/mocks/harness/IdentityRegistryDelegationHarness.sol new file mode 100644 index 00000000..d2f25270 --- /dev/null +++ b/src/mocks/harness/IdentityRegistryDelegationHarness.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; +import {RuleIdentityRegistry} from "../../rules/validation/deployment/RuleIdentityRegistry.sol"; + +/** + * @title IdentityRegistryExtraCheckHarness + * @notice A subclass that adds a screening check which does NOT depend on the identity registry + * (Nethermind AuditAgent NM-3, the mirror of `CLAUDE_ANALYSIS.md` F-2). + * @dev This is the shape that exposes the defect. `_detectTransferRestrictionFrom` used to return + * `TRANSFER_OK` outright when the registry was unset or the transfer was a burn, instead of + * delegating to {_detectTransferRestriction}. A subclass extending only that hook -- the + * natural place to add a check -- therefore applied to `transfer` but silently not to + * `transferFrom` or `burnFrom`. A compliance rule that screens one entrypoint and not the + * other is the failure this harness exists to catch. + */ +contract IdentityRegistryExtraCheckHarness is RuleIdentityRegistry { + /** + * @notice Restriction code returned for the extra, registry-independent check. + */ + uint8 public constant CODE_EXTRA_BLOCKED = 202; + + /** + * @notice Address this subclass blocks regardless of what the registry says. + */ + address public immutable BLOCKED; + + constructor(address admin, address identityRegistry_, bool checkSender_, bool checkSpender_, address blocked) + RuleIdentityRegistry(admin, identityRegistry_, checkSender_, checkSpender_) + { + BLOCKED = blocked; + } + + /** + * @notice Applies the base identity screening, then the extra registry-independent check. + */ + function _detectTransferRestriction(address from, address to, uint256 value) + internal + view + virtual + override + returns (uint8) + { + uint8 code = super._detectTransferRestriction(from, to, value); + if (code != uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK)) { + return code; + } + if (from == BLOCKED || to == BLOCKED) { + return CODE_EXTRA_BLOCKED; + } + return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + } +} diff --git a/src/modules/VersionModule.sol b/src/modules/VersionModule.sol index 03806432..6669b03f 100644 --- a/src/modules/VersionModule.sol +++ b/src/modules/VersionModule.sol @@ -11,7 +11,7 @@ abstract contract VersionModule is IERC3643Version { /** * @notice The contract version string returned by {version}. */ - string private constant VERSION = "0.5.0"; + string private constant VERSION = "0.6.0"; /*////////////////////////////////////////////////////////////// PUBLIC FUNCTIONS diff --git a/src/rules/interfaces/IAddressList.sol b/src/rules/interfaces/IAddressList.sol index 768b42ad..378fd8a5 100644 --- a/src/rules/interfaces/IAddressList.sol +++ b/src/rules/interfaces/IAddressList.sol @@ -3,10 +3,60 @@ pragma solidity ^0.8.20; import {IIdentityRegistryContains} from "./IIdentityRegistry.sol"; +/** + * @title IAddressListBatchQuery — the batch membership question, and nothing else. + * @notice The minimum a contract must expose to be usable as a child of `RuleWhitelistWrapper`. + * @dev Split out of {IAddressList} deliberately. The wrapper calls exactly one function on its + * children, so demanding the whole of {IAddressList} — which also carries four write functions, two + * further read functions and `contains` — would reject a perfectly serviceable read-only child. + * ERC-165 checks should ask for what is actually called. + * + * WARNING: this interface conveys **membership, not polarity**. It says whether an address is in the + * implementer's set, never whether being in that set means "allowed" or "denied". A deny-list + * implements it just as faithfully as an allow-list, so no ERC-165 check can tell them apart; a + * consumer that reads `true` as "eligible" must constrain its children by configuration. + */ +interface IAddressListBatchQuery { + /** + * @notice Checks multiple addresses for listing status. + * @param targetAddresses Array of addresses to check. + * @return results Boolean array aligned by index with listing results. + */ + function areAddressesListed(address[] memory targetAddresses) external view returns (bool[] memory results); +} + +/** + * @title IAddressListPolarity — what membership of the set MEANS. + * @notice The half of an address list that {IAddressListBatchQuery} cannot express. + * @dev `areAddressesListed` reports *membership*; it says nothing about whether being a member is a + * permission or a prohibition. An allow-list and a deny-list implement that interface identically and + * advertise the same ERC-165 id, so a consumer reading `true` as "eligible" cannot tell them apart — + * add a deny-list to an allow-list aggregator and its blocked addresses silently become permitted. + * + * Declaring polarity explicitly is what makes it checkable. A consumer requires this interface via + * ERC-165 and then reads {isAllowList}, so a wrong-polarity list is refused at configuration time + * instead of inverting the consumer's meaning at run time. + * + * WARNING: polarity is not the only way a list can be the wrong list. It says nothing about WHO the + * listed addresses are — a rule listing permitted *spenders* is an allow-list and still meaningless + * to a consumer screening *holders*. A contract whose set is not about the subject its consumers + * screen should decline to implement this interface at all, so a fail-closed consumer refuses it. + */ +interface IAddressListPolarity { + /** + * @notice Whether membership of this contract's address set means ALLOWED. + * @return allowed True when listed addresses are the permitted ones (an allow-list); false when + * listed addresses are the prohibited ones (a deny-list). + */ + function isAllowList() external view returns (bool allowed); +} + /** * @title IAddressList — interface for managing and querying a set of addresses. + * @dev Inherits {IAddressListBatchQuery}; the flattened selector set is unchanged, so + * {AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID} keeps its value. */ -interface IAddressList is IIdentityRegistryContains { +interface IAddressList is IIdentityRegistryContains, IAddressListBatchQuery { /* ============ Events ============ */ /** * @notice Emitted when a batch add completes. @@ -84,11 +134,4 @@ interface IAddressList is IIdentityRegistryContains { * @return isListed True if listed, otherwise false. */ function isAddressListed(address targetAddress) external view returns (bool isListed); - - /** - * @notice Checks multiple addresses for listing status. - * @param targetAddresses Array of addresses to check. - * @return results Boolean array aligned by index with listing results. - */ - function areAddressesListed(address[] memory targetAddresses) external view returns (bool[] memory results); } diff --git a/src/rules/interfaces/library/AddressListInterfaceId.sol b/src/rules/interfaces/library/AddressListInterfaceId.sol index eb370874..0c447826 100644 --- a/src/rules/interfaces/library/AddressListInterfaceId.sol +++ b/src/rules/interfaces/library/AddressListInterfaceId.sol @@ -18,4 +18,30 @@ library AddressListInterfaceId { * @notice ERC-165 interface ID of the full {IAddressList} hierarchy. */ bytes4 public constant IADDRESS_LIST_INTERFACE_ID = 0x5d10e182; + + /** + * @notice ERC-165 interface ID of {IAddressListBatchQuery}, the single function + * `areAddressesListed(address[])`. + * @dev This is what `RuleWhitelistWrapper` requires of a child, because it is the only function + * the wrapper ever calls. Demanding {IADDRESS_LIST_INTERFACE_ID} instead would also require four + * write functions, `listedAddressCount`, `isAddressListed` and `contains` — none of which the + * wrapper uses — and would exclude a read-only child that is otherwise perfectly usable. + * + * Safe to state as a literal: {IAddressListBatchQuery} declares one function and inherits + * nothing, so unlike {IADDRESS_LIST_INTERFACE_ID} there is no omitted-parent trap here. The + * value equals the selector of the single function; asserted in + * test/InterfaceId/AddressListInterfaceId.t.sol. + */ + bytes4 public constant IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID = 0x20e8e17a; + + /** + * @notice ERC-165 interface ID of {IAddressListPolarity}, the single function `isAllowList()`. + * @dev Paired with {IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID} by consumers that read membership as + * eligibility: the first says the contract can answer, this one says what the answer means. A + * consumer must treat its ABSENCE as a refusal, not as an allow-list — that is the only reading + * that fails closed for a contract predating the interface or deliberately declining it. + * + * Safe as a literal for the same reason as the batch-query id: one function, no inheritance. + */ + bytes4 public constant IADDRESS_LIST_POLARITY_INTERFACE_ID = 0xdc4efe10; } diff --git a/src/rules/operation/RuleConditionalTransferLight.sol b/src/rules/operation/RuleConditionalTransferLight.sol index bc75ea71..0fe1c8d0 100644 --- a/src/rules/operation/RuleConditionalTransferLight.sol +++ b/src/rules/operation/RuleConditionalTransferLight.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.20; import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {ComplianceInterfaceId} from "RuleEngine/modules/library/ComplianceInterfaceId.sol"; import {RuleInterfaceId} from "RuleEngine/modules/library/RuleInterfaceId.sol"; import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol"; import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol"; import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; -import {IERC3643ComplianceFull} from "../../mocks/IERC3643ComplianceFull.sol"; import {AccessControlModuleStandalone} from "../../modules/AccessControlModuleStandalone.sol"; import {RuleConditionalTransferLightBase} from "./abstract/RuleConditionalTransferLightBase.sol"; import {ERC3643ComplianceRolesStorage} from "RuleEngine/modules/library/ERC3643ComplianceRolesStorage.sol"; @@ -48,7 +48,7 @@ contract RuleConditionalTransferLight is return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId - || interfaceId == type(IERC3643ComplianceFull).interfaceId + || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID || AccessControlEnumerable.supportsInterface(interfaceId); } @@ -69,11 +69,5 @@ contract RuleConditionalTransferLight is /** * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. */ - function _authorizeComplianceBindingChange(address) - internal - view - virtual - override - onlyRole(COMPLIANCE_MANAGER_ROLE) - {} + function _authorizeTokenBindingChange(address) internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} } diff --git a/src/rules/operation/RuleConditionalTransferLightMultiToken.sol b/src/rules/operation/RuleConditionalTransferLightMultiToken.sol index b3bb8e2e..c7352628 100644 --- a/src/rules/operation/RuleConditionalTransferLightMultiToken.sol +++ b/src/rules/operation/RuleConditionalTransferLightMultiToken.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.20; import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {ComplianceInterfaceId} from "RuleEngine/modules/library/ComplianceInterfaceId.sol"; import {RuleInterfaceId} from "RuleEngine/modules/library/RuleInterfaceId.sol"; import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol"; import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol"; import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; -import {IERC3643ComplianceFull} from "../../mocks/IERC3643ComplianceFull.sol"; import {AccessControlModuleStandalone} from "../../modules/AccessControlModuleStandalone.sol"; import {RuleConditionalTransferLightMultiTokenBase} from "./abstract/RuleConditionalTransferLightMultiTokenBase.sol"; import {ERC3643ComplianceRolesStorage} from "RuleEngine/modules/library/ERC3643ComplianceRolesStorage.sol"; @@ -40,7 +40,7 @@ contract RuleConditionalTransferLightMultiToken is return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId - || interfaceId == type(IERC3643ComplianceFull).interfaceId + || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID || AccessControlEnumerable.supportsInterface(interfaceId); } diff --git a/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol b/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol index cbb4ebf0..ee37e2cf 100644 --- a/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol +++ b/src/rules/operation/RuleConditionalTransferLightMultiTokenOwnable2Step.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {ComplianceInterfaceId} from "RuleEngine/modules/library/ComplianceInterfaceId.sol"; import {RuleInterfaceId} from "RuleEngine/modules/library/RuleInterfaceId.sol"; import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol"; import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol"; import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; -import {IERC3643ComplianceFull} from "../../mocks/IERC3643ComplianceFull.sol"; import {RuleConditionalTransferLightMultiTokenBase} from "./abstract/RuleConditionalTransferLightMultiTokenBase.sol"; import {Ownable2StepERC165Module} from "../../modules/Ownable2StepERC165Module.sol"; @@ -40,7 +40,7 @@ contract RuleConditionalTransferLightMultiTokenOwnable2Step is || interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId - || interfaceId == type(IERC3643ComplianceFull).interfaceId; + || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID; } /** diff --git a/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol b/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol index f5e585e6..493f4e1d 100644 --- a/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol +++ b/src/rules/operation/RuleConditionalTransferLightOwnable2Step.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {ComplianceInterfaceId} from "RuleEngine/modules/library/ComplianceInterfaceId.sol"; import {RuleInterfaceId} from "RuleEngine/modules/library/RuleInterfaceId.sol"; import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol"; import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol"; import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; -import {IERC3643ComplianceFull} from "../../mocks/IERC3643ComplianceFull.sol"; import {RuleConditionalTransferLightBase} from "./abstract/RuleConditionalTransferLightBase.sol"; import {Ownable2StepERC165Module} from "../../modules/Ownable2StepERC165Module.sol"; @@ -47,7 +47,7 @@ contract RuleConditionalTransferLightOwnable2Step is || interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || interfaceId == type(IERC7551Compliance).interfaceId - || interfaceId == type(IERC3643ComplianceFull).interfaceId; + || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID; } /*////////////////////////////////////////////////////////////// @@ -67,5 +67,5 @@ contract RuleConditionalTransferLightOwnable2Step is /** * @notice Reverts unless the caller is the owner. */ - function _authorizeComplianceBindingChange(address) internal view virtual override onlyOwner {} + function _authorizeTokenBindingChange(address) internal view virtual override onlyOwner {} } diff --git a/src/rules/operation/RuleMintAllowance.sol b/src/rules/operation/RuleMintAllowance.sol index 83173831..fb08355e 100644 --- a/src/rules/operation/RuleMintAllowance.sol +++ b/src/rules/operation/RuleMintAllowance.sol @@ -67,11 +67,5 @@ contract RuleMintAllowance is AccessControlModuleStandalone, RuleMintAllowanceBa /** * @notice Reverts unless the caller holds `COMPLIANCE_MANAGER_ROLE`. */ - function _authorizeComplianceBindingChange(address) - internal - view - virtual - override - onlyRole(COMPLIANCE_MANAGER_ROLE) - {} + function _authorizeTokenBindingChange(address) internal view virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} } diff --git a/src/rules/operation/RuleMintAllowanceOwnable2Step.sol b/src/rules/operation/RuleMintAllowanceOwnable2Step.sol index 45470feb..9d12b012 100644 --- a/src/rules/operation/RuleMintAllowanceOwnable2Step.sol +++ b/src/rules/operation/RuleMintAllowanceOwnable2Step.sol @@ -64,5 +64,5 @@ contract RuleMintAllowanceOwnable2Step is RuleMintAllowanceBase, Ownable2Step, O /** * @notice Reverts unless the caller is the owner. */ - function _authorizeComplianceBindingChange(address) internal view virtual override onlyOwner {} + function _authorizeTokenBindingChange(address) internal view virtual override onlyOwner {} } diff --git a/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol b/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol index 7aeeb492..df47d9be 100644 --- a/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol +++ b/src/rules/operation/abstract/RuleConditionalTransferLightBase.sol @@ -7,6 +7,8 @@ import {IERC3643ComplianceRead, IERC3643IComplianceContract} from "CMTAT/interfa import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; import {IRule} from "RuleEngine/interfaces/IRule.sol"; import {ERC3643ComplianceModule} from "RuleEngine/modules/ERC3643ComplianceModule.sol"; +import {TokenBindingModule} from "RuleEngine/modules/TokenBindingModule.sol"; +import {ITokenBinding} from "RuleEngine/interfaces/ITokenBinding.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {RuleConditionalTransferLightApprovalBase} from "./RuleConditionalTransferLightApprovalBase.sol"; @@ -119,12 +121,22 @@ abstract contract RuleConditionalTransferLightBase is address token = getTokenBound(); require(token != address(0), RuleConditionalTransferLight_TokenNotBound()); + uint256 approvalsBefore = approvedCount(from, to, value); approveTransfer(from, to, value); uint256 allowed = IERC20(token).allowance(from, address(this)); require(allowed >= value, RuleConditionalTransferLight_InsufficientAllowance(token, from, allowed, value)); IERC20(token).safeTransferFrom(from, to, value); + + // The approval above exists ONLY for the token's compliance callback to consume. If the count + // did not come back down, no callback reached this rule -- the binding is wrong -- and leaving + // the surplus would authorise a later, never-approved transfer of the same tuple. Read after + // the external call deliberately: a hostile token can make this fail, never pass spuriously. + require( + approvedCount(from, to, value) == approvalsBefore, + RuleConditionalTransferLight_ApprovalNotConsumed(token, from, to, value) + ); return true; } @@ -177,7 +189,12 @@ abstract contract RuleConditionalTransferLightBase is * {unbindRuleEngine} before rebinding. * @param token The ERC-20 token to bind to this rule. */ - function bindToken(address token) public virtual override onlyComplianceManager { + function bindToken(address token) + public + virtual + override(ITokenBinding, TokenBindingModule) + onlyTokenBindingManager + { require(getTokenBound() == address(0), RuleConditionalTransferLight_TokenAlreadyBound()); _bindToken(token); } @@ -198,7 +215,7 @@ abstract contract RuleConditionalTransferLightBase is * @param ruleEngine_ The RuleEngine allowed to call `transferred`. It MUST serve only the token * bound via {bindToken}. */ - function bindRuleEngine(address ruleEngine_) public virtual onlyComplianceManager { + function bindRuleEngine(address ruleEngine_) public virtual onlyTokenBindingManager { require(ruleEngine_ != address(0), RuleConditionalTransferLight_RuleEngineAddressZeroNotAllowed()); require(ruleEngine == address(0), RuleConditionalTransferLight_RuleEngineAlreadyBound()); ruleEngine = ruleEngine_; @@ -209,7 +226,7 @@ abstract contract RuleConditionalTransferLightBase is * @notice Revokes the bound RuleEngine's authorization to call the transfer execution hooks. * @dev Does NOT clear `approvalCounts` — see the {bindToken} warning and {resetApproval}. */ - function unbindRuleEngine() public virtual onlyComplianceManager { + function unbindRuleEngine() public virtual onlyTokenBindingManager { address previous = ruleEngine; require(previous != address(0), RuleConditionalTransferLight_RuleEngineNotBound()); ruleEngine = address(0); diff --git a/src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol b/src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol index 3f5126e2..0e6fd1ae 100644 --- a/src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol +++ b/src/rules/operation/abstract/RuleConditionalTransferLightInvariantStorage.sol @@ -77,6 +77,21 @@ abstract contract RuleConditionalTransferLightInvariantStorage is RuleSharedInva ); error TransferNotApproved(); error TransferApprovalNotFound(); + /** + * @notice The approval created by {approveAndTransferIfAllowed} was not consumed by the transfer. + * @dev The helper inverts CEI deliberately so the approval exists while the token runs its + * compliance callback. That is only correct if the callback actually reaches this rule; when it + * does not -- a plain ERC-20 bound for the helper, or a RuleEngine never bound or since unbound -- + * the transfer used to succeed and leave a spendable approval behind, authorising a later + * never-approved transfer of the same tuple. The post-condition turns that silent hole into this + * revert. Nethermind AuditAgent NM-17. + * @param token The bound ERC-20 the transfer was executed on. + * @param from The sender of the transfer. + * @param to The recipient of the transfer. + * @param value The amount transferred. + */ + error RuleConditionalTransferLight_ApprovalNotConsumed(address token, address from, address to, uint256 value); + error RuleConditionalTransferLight_RuleEngineAddressZeroNotAllowed(); error RuleConditionalTransferLight_RuleEngineNotBound(); error RuleConditionalTransferLight_RuleEngineAlreadyBound(); diff --git a/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol b/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol index 127c79b1..ecc83791 100644 --- a/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol +++ b/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenBase.sol @@ -136,6 +136,7 @@ abstract contract RuleConditionalTransferLightMultiTokenBase is { require(isTokenBound(token), RuleConditionalTransferLightMultiToken_InvalidToken()); + uint256 approvalsBefore = approvedCount(token, from, to, value); _approveTransfer(token, from, to, value); uint256 allowed = IERC20(token).allowance(from, address(this)); @@ -144,6 +145,14 @@ abstract contract RuleConditionalTransferLightMultiTokenBase is ); IERC20(token).safeTransferFrom(from, to, value); + + // See the single-token twin: the approval exists only for the token's compliance callback, so + // a count that did not come back down means no callback reached this rule and the surplus + // would otherwise stay spendable. + require( + approvedCount(token, from, to, value) == approvalsBefore, + RuleConditionalTransferLightMultiToken_ApprovalNotConsumed(token, from, to, value) + ); return true; } @@ -321,21 +330,6 @@ abstract contract RuleConditionalTransferLightMultiTokenBase is == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); } - /** - * @notice Authorizes changes to compliance binding: restricted to the compliance manager. - * @dev NOT `view`, unlike every other access-control hook in this codebase. This is structural, - * not an oversight: the implementation delegates to `_onlyComplianceManager()`, which - * `lib/RuleEngine`'s {ERC3643ComplianceModule} declares as `internal virtual` (non-`view`). - * Solidity checks mutability against a virtual's DECLARED type, not the installed override, - * so calling it from a `view` function is a compile error — even though every override of it - * in this repo is `view`. It can only become `view` once the upstream declaration does. - * (The single-token rules avoid this by overriding this hook directly with `onlyRole(...)` - * instead of delegating, which is why they are already `view`.) - */ - function _authorizeComplianceBindingChange(address) internal virtual override { - _onlyComplianceManager(); - } - /** * @notice Records a new approval for the given per-token transfer; reverts if the token is not bound. * @param token The token the transfer applies to. diff --git a/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol b/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol index 4e69d930..271615a7 100644 --- a/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol +++ b/src/rules/operation/abstract/RuleConditionalTransferLightMultiTokenInvariantStorage.sol @@ -75,6 +75,20 @@ abstract contract RuleConditionalTransferLightMultiTokenInvariantStorage is Rule error RuleConditionalTransferLightMultiToken_InsufficientAllowance( address token, address owner, uint256 allowance, uint256 required ); + /** + * @notice The approval created by {approveAndTransferIfAllowed} was not consumed by the transfer. + * @dev See the single-token twin: the helper inverts CEI so the approval exists for the token's + * compliance callback, and this post-condition catches the case where no callback reached the + * rule and the approval would otherwise have been left spendable. Nethermind AuditAgent NM-17. + * @param token The token the transfer was executed on. + * @param from The sender of the transfer. + * @param to The recipient of the transfer. + * @param value The amount transferred. + */ + error RuleConditionalTransferLightMultiToken_ApprovalNotConsumed( + address token, address from, address to, uint256 value + ); + error RuleConditionalTransferLightMultiToken_InvalidToken(); error RuleConditionalTransferLightMultiToken_TransferNotApproved(); error RuleConditionalTransferLightMultiToken_TransferApprovalNotFound(); diff --git a/src/rules/operation/abstract/RuleMintAllowanceBase.sol b/src/rules/operation/abstract/RuleMintAllowanceBase.sol index fee137a1..5eee10d2 100644 --- a/src/rules/operation/abstract/RuleMintAllowanceBase.sol +++ b/src/rules/operation/abstract/RuleMintAllowanceBase.sol @@ -7,6 +7,8 @@ import {IERC3643ComplianceRead, IERC3643IComplianceContract} from "CMTAT/interfa import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; import {IRule} from "RuleEngine/interfaces/IRule.sol"; import {ERC3643ComplianceModule} from "RuleEngine/modules/ERC3643ComplianceModule.sol"; +import {TokenBindingModule} from "RuleEngine/modules/TokenBindingModule.sol"; +import {ITokenBinding} from "RuleEngine/interfaces/ITokenBinding.sol"; import {VersionModule} from "../../../modules/VersionModule.sol"; import {RuleMintAllowanceInvariantStorage} from "./RuleMintAllowanceInvariantStorage.sol"; @@ -139,7 +141,12 @@ abstract contract RuleMintAllowanceBase is * behavior. Call {clearMintAllowances} before rebinding to discard the previous quotas. * @param token The caller (RuleEngine/token) to bind to this rule. */ - function bindToken(address token) public virtual override onlyComplianceManager { + function bindToken(address token) + public + virtual + override(ITokenBinding, TokenBindingModule) + onlyTokenBindingManager + { require(getTokenBound() == address(0), RuleMintAllowance_TokenAlreadyBound()); _bindToken(token); } diff --git a/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol b/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol index 3af9eee2..104e8278 100644 --- a/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol +++ b/src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol @@ -61,7 +61,7 @@ abstract contract RuleAddressSetInternal is RuleAddressSetInvariantStorage { * the sentinel with THIS rule's error rather than a generic one. * @param targetAddress The candidate address. */ - function _requireNotZeroAddress(address targetAddress) internal pure { + function _requireNotZeroAddress(address targetAddress) internal pure virtual { require(targetAddress != address(0), RuleAddressSet_ZeroAddressNotAllowed()); } diff --git a/src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol b/src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol index cee1a148..540faee4 100644 --- a/src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol +++ b/src/rules/validation/abstract/RuleAddressSet/invariantStorage/RuleWhitelistInvariantStorage.sol @@ -70,6 +70,34 @@ abstract contract RuleWhitelistInvariantStorage is RuleSharedInvariantStorage { */ event AllowBurnUpdated(bool newValue); + /** + * @notice A candidate child rule does not answer `areAddressesListed(address[])`. + * @dev Raised by `RuleWhitelistWrapper` when a rule is added that does not advertise + * {AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID} via ERC-165. Without the guard + * the wrapper accepted it and then reverted on the blind call during a transfer, bricking every + * check whose targets were not already resolved. Nethermind AuditAgent NM-18, audit F-5. + * @param rule The rejected candidate. + */ + error RuleWhitelistWrapper_ChildIsNotAnAddressList(address rule); + + /** + * @notice A candidate child rule does not declare whether its list means "allowed" or "denied". + * @dev Absence is treated as a refusal, never as an assumed allow-list: that is the only reading + * that fails closed for a contract predating {IAddressListPolarity} or deliberately declining it + * (`RuleSpenderWhitelist` declines, because its set is spenders rather than holders). + * @param rule The rejected candidate. + */ + error RuleWhitelistWrapper_ChildDoesNotDeclarePolarity(address rule); + + /** + * @notice A candidate child rule declares itself a DENY-list; this wrapper aggregates allow-lists. + * @dev The wrapper ORs its children's membership answers and reads `true` as eligible, so a + * deny-list child would make its blocked addresses permitted and `isVerified` report them as + * verified investors. Nethermind AuditAgent NM-20. + * @param rule The rejected candidate. + */ + error RuleWhitelistWrapper_ChildIsNotAnAllowList(address rule); + error RuleWhitelist_InvalidTransfer(address rule, address from, address to, uint256 value, uint8 code); error RuleWhitelist_InvalidTransferFrom( address rule, address spender, address from, address to, uint256 value, uint8 code diff --git a/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol b/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol index 1e9033b2..93e0f430 100644 --- a/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol +++ b/src/rules/validation/abstract/RuleERC2980/RuleERC2980Internal.sol @@ -139,7 +139,7 @@ abstract contract RuleERC2980Internal is RuleERC2980InvariantStorage { * with THIS rule's error rather than a generic one. * @param targetAddress The candidate address. */ - function _requireNotZeroAddress(address targetAddress) internal pure { + function _requireNotZeroAddress(address targetAddress) internal pure virtual { require(targetAddress != address(0), RuleERC2980_ZeroAddressNotAllowed()); } diff --git a/src/rules/validation/abstract/base/RuleBlacklistBase.sol b/src/rules/validation/abstract/base/RuleBlacklistBase.sol index aa7c5258..640cda34 100644 --- a/src/rules/validation/abstract/base/RuleBlacklistBase.sol +++ b/src/rules/validation/abstract/base/RuleBlacklistBase.sol @@ -5,6 +5,7 @@ import {RuleAddressSet} from "../RuleAddressSet/RuleAddressSet.sol"; import {RuleNFTAdapter} from "../core/RuleNFTAdapter.sol"; import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; import {RuleBlacklistInvariantStorage} from "../RuleAddressSet/invariantStorage/RuleBlacklistInvariantStorage.sol"; +import {IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; @@ -15,7 +16,12 @@ import {IRule} from "RuleEngine/interfaces/IRule.sol"; * @title RuleBlacklistBase * @notice Core blacklist logic without access-control policy. */ -abstract contract RuleBlacklistBase is RuleAddressSet, RuleNFTAdapter, RuleBlacklistInvariantStorage { +abstract contract RuleBlacklistBase is + RuleAddressSet, + RuleNFTAdapter, + RuleBlacklistInvariantStorage, + IAddressListPolarity +{ /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -98,9 +104,19 @@ abstract contract RuleBlacklistBase is RuleAddressSet, RuleNFTAdapter, RuleBlack // Advertise IAddressList: this rule manages an address set and is callable through // the IAddressList interface. return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID || RuleTransferValidation.supportsInterface(interfaceId); } + /** + * @inheritdoc IAddressListPolarity + * @dev Listed addresses are the BLOCKED ones. A consumer that reads membership as eligibility must refuse this rule. + */ + function isAllowList() public pure virtual override returns (bool) { + return false; + } + /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ diff --git a/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol b/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol index c5114f6b..98b3a5a1 100644 --- a/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol +++ b/src/rules/validation/abstract/base/RuleChainlinkPoRBase.sol @@ -137,9 +137,9 @@ abstract contract RuleChainlinkPoRBase is RuleTransferValidation, ChainlinkPoRFe if (!supplyAvailable) { return CODE_TOTAL_SUPPLY_UNAVAILABLE; } - // Overflow-safe: `currentSupply + value` could exceed uint256 and this is a - // MUST-NOT-revert ERC-1404/ERC-3643 view, so compare against the remaining headroom. - if (currentSupply > backedSupply || value > backedSupply - currentSupply) { + // The comparison, the overflow-safety and the pre-update accounting assumption all live in + // {CapAccounting}; the reserve figure is simply this rule's cap. + if (_capExceededBy(currentSupply, backedSupply, value)) { return CODE_RESERVES_EXCEEDED; } return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); @@ -158,6 +158,29 @@ abstract contract RuleChainlinkPoRBase is RuleTransferValidation, ChainlinkPoRFe return _detectTransferRestriction(from, to, value); } + /** + * @notice Restriction code for the NOTIFICATION phase, i.e. what the write hook enforces. + * @dev **The seam an ERC-3643 variant overrides.** Defaults to the pre-flight check, correct for a token + * that notifies BEFORE moving the value (CMTAT). A token that notifies AFTERWARDS reports an observation + * that already includes `value`, and counting it again halves the effective cap; such a variant overrides + * this with `_detectTransferRestriction(from, to, 0)`. + * + * The read path is deliberately NOT routed through here: a pre-flight view always runs before the movement + * on either kind of token, so it must always count `value`. + * @param from Sender address. + * @param to Recipient address. + * @param value Amount moved. + * @return The restriction code the write hook will enforce. + */ + function _detectTransferRestrictionOnNotify(address from, address to, uint256 value) + internal + view + virtual + returns (uint8) + { + return _detectTransferRestriction(from, to, value); + } + /** * @notice Enforces the reserve backing for a direct transfer, reverting on violation. * @param from Sender address; the zero address denotes a mint whose backing is checked. @@ -165,7 +188,7 @@ abstract contract RuleChainlinkPoRBase is RuleTransferValidation, ChainlinkPoRFe * @param value Transfer amount. */ function _transferred(address from, address to, uint256 value) internal view virtual { - uint8 code = _detectTransferRestriction(from, to, value); + uint8 code = _detectTransferRestrictionOnNotify(from, to, value); require( code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), RuleChainlinkPoR_InvalidTransfer(address(this), from, to, value, code) @@ -180,7 +203,7 @@ abstract contract RuleChainlinkPoRBase is RuleTransferValidation, ChainlinkPoRFe * @param value Transfer amount. */ function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual { - uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + uint8 code = _detectTransferRestrictionOnNotify(from, to, value); require( code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), RuleChainlinkPoR_InvalidTransferFrom(address(this), spender, from, to, value, code) diff --git a/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol b/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol index b6340a20..5c47cf58 100644 --- a/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol +++ b/src/rules/validation/abstract/base/RuleIdentityRegistryBase.sol @@ -232,21 +232,20 @@ abstract contract RuleIdentityRegistryBase is RuleNFTAdapter, RuleIdentityRegist returns (uint8) { IIdentityRegistryVerified registry = identityRegistry; - if (address(registry) == address(0)) { - return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); - } - // ERC-3643: burn bypasses all eligibility checks. - if (to == address(0)) { - return uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); + // The guard scopes ONLY the spender check; the delegation is unconditional, as in + // {RuleSanctionsListBase}. Returning TRANSFER_OK here instead would silently drop any check a + // subclass adds by overriding {_detectTransferRestriction} alone. An unset registry and a burn + // (to == 0) both resolve to TRANSFER_OK inside the delegate, so no answer changes. + if (address(registry) == address(0) || to == address(0)) { + return _detectTransferRestriction(from, to, value); } // OPT-IN, stricter than ERC-3643 ("`transferFrom` works the same way" — receiver only). // Mint (from == 0) is exempt: the minter acts on its own authority, not as a delegated // ERC-20 spender. This is what makes an unverified MINTER able to mint to a verified // recipient, exactly as the specification requires. - // Burn (to == 0) is exempt too, but by the early return above -- do NOT re-test `to` here. - // The condition would be dead, and re-stating it reads as though burn were handled at this - // point rather than six lines earlier. + // Burn (to == 0) never reaches this line -- the guard above delegates it -- so do NOT + // re-test `to` here; the condition would be dead. if (checkSpender && spender != address(0) && from != address(0) && !registry.isVerified(spender)) { return CODE_ADDRESS_SPENDER_NOT_VERIFIED; } diff --git a/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol b/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol index 346f5912..bc541243 100644 --- a/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol +++ b/src/rules/validation/abstract/base/RuleMaxBalanceBase.sol @@ -156,6 +156,29 @@ abstract contract RuleMaxBalanceBase is RuleTransferValidation, BalanceCapManage return _detectTransferRestriction(from, to, value); } + /** + * @notice Restriction code for the NOTIFICATION phase, i.e. what the write hook enforces. + * @dev **The seam an ERC-3643 variant overrides.** Defaults to the pre-flight check, correct for a token + * that notifies BEFORE moving the value (CMTAT). A token that notifies AFTERWARDS reports an observation + * that already includes `value`, and counting it again halves the effective cap; such a variant overrides + * this with `_detectTransferRestriction(from, to, 0)`. + * + * The read path is deliberately NOT routed through here: a pre-flight view always runs before the movement + * on either kind of token, so it must always count `value`. + * @param from Sender address. + * @param to Recipient address. + * @param value Amount moved. + * @return The restriction code the write hook will enforce. + */ + function _detectTransferRestrictionOnNotify(address from, address to, uint256 value) + internal + view + virtual + returns (uint8) + { + return _detectTransferRestriction(from, to, value); + } + /** * @notice Enforces the cap for a direct transfer, reverting on violation. * @param from Sender address. @@ -163,7 +186,7 @@ abstract contract RuleMaxBalanceBase is RuleTransferValidation, BalanceCapManage * @param value Transfer amount. */ function _transferred(address from, address to, uint256 value) internal view virtual { - uint8 code = _detectTransferRestriction(from, to, value); + uint8 code = _detectTransferRestrictionOnNotify(from, to, value); require( code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), RuleMaxBalance_InvalidTransfer(address(this), from, to, value, code) @@ -178,7 +201,7 @@ abstract contract RuleMaxBalanceBase is RuleTransferValidation, BalanceCapManage * @param value Transfer amount. */ function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual { - uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + uint8 code = _detectTransferRestrictionOnNotify(from, to, value); require( code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), RuleMaxBalance_InvalidTransferFrom(address(this), spender, from, to, value, code) diff --git a/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol b/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol index 473653aa..afd07132 100644 --- a/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol +++ b/src/rules/validation/abstract/base/RuleMaxTotalSupplyBase.sol @@ -122,6 +122,29 @@ abstract contract RuleMaxTotalSupplyBase is RuleTransferValidation, TotalSupplyC return _detectTransferRestriction(from, to, value); } + /** + * @notice Restriction code for the NOTIFICATION phase, i.e. what the write hook enforces. + * @dev **The seam an ERC-3643 variant overrides.** Defaults to the pre-flight check, correct for a token + * that notifies BEFORE moving the value (CMTAT). A token that notifies AFTERWARDS reports an observation + * that already includes `value`, and counting it again halves the effective cap; such a variant overrides + * this with `_detectTransferRestriction(from, to, 0)`. + * + * The read path is deliberately NOT routed through here: a pre-flight view always runs before the movement + * on either kind of token, so it must always count `value`. + * @param from Sender address. + * @param to Recipient address. + * @param value Amount moved. + * @return The restriction code the write hook will enforce. + */ + function _detectTransferRestrictionOnNotify(address from, address to, uint256 value) + internal + view + virtual + returns (uint8) + { + return _detectTransferRestriction(from, to, value); + } + /** * @notice Enforces the max-total-supply restriction for a direct transfer, reverting on violation. * @param from Sender address; the zero address denotes a mint whose supply is checked. @@ -129,7 +152,7 @@ abstract contract RuleMaxTotalSupplyBase is RuleTransferValidation, TotalSupplyC * @param value Transfer amount. */ function _transferred(address from, address to, uint256 value) internal view virtual { - uint8 code = _detectTransferRestriction(from, to, value); + uint8 code = _detectTransferRestrictionOnNotify(from, to, value); require( code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), RuleMaxTotalSupply_InvalidTransfer(address(this), from, to, value, code) @@ -144,7 +167,7 @@ abstract contract RuleMaxTotalSupplyBase is RuleTransferValidation, TotalSupplyC * @param value Transfer amount. */ function _transferredFrom(address spender, address from, address to, uint256 value) internal view virtual { - uint8 code = _detectTransferRestrictionFrom(spender, from, to, value); + uint8 code = _detectTransferRestrictionOnNotify(from, to, value); require( code == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK), RuleMaxTotalSupply_InvalidTransferFrom(address(this), spender, from, to, value, code) diff --git a/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol b/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol index ce7cc3bf..b5716ea7 100644 --- a/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol +++ b/src/rules/validation/abstract/base/RuleReceiverWhitelistBase.sol @@ -5,6 +5,7 @@ import {RuleAddressSet} from "../RuleAddressSet/RuleAddressSet.sol"; import {RuleNFTAdapter} from "../core/RuleNFTAdapter.sol"; import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; import {RuleReceiverWhitelistInvariantStorage} from "../invariant/RuleReceiverWhitelistInvariantStorage.sol"; +import {IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; import {IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; @@ -29,7 +30,12 @@ import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; * @dev There is no `allowMint` flag, unlike {RuleWhitelist}: ERC-3643 gates minting on receiver * eligibility alone. Compose with `RuleMaxTotalSupply` or `RuleChainlinkPoR` to cap issuance. */ -abstract contract RuleReceiverWhitelistBase is RuleAddressSet, RuleNFTAdapter, RuleReceiverWhitelistInvariantStorage { +abstract contract RuleReceiverWhitelistBase is + RuleAddressSet, + RuleNFTAdapter, + RuleReceiverWhitelistInvariantStorage, + IAddressListPolarity +{ /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -93,9 +99,19 @@ abstract contract RuleReceiverWhitelistBase is RuleAddressSet, RuleNFTAdapter, R // Advertise IAddressList: this rule manages an address set and is callable through // the IAddressList interface. return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID || RuleTransferValidation.supportsInterface(interfaceId); } + /** + * @inheritdoc IAddressListPolarity + * @dev Listed addresses are the permitted receivers. + */ + function isAllowList() public pure virtual override returns (bool) { + return true; + } + /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ diff --git a/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol b/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol index af9d05c6..ab187d51 100644 --- a/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol +++ b/src/rules/validation/abstract/base/RuleSpenderWhitelistBase.sol @@ -14,6 +14,13 @@ import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; * @title RuleSpenderWhitelistBase * @notice Restricts `transferFrom`-style flows to whitelisted spenders only. * @dev Direct transfers (`transferred(from,to,value)`) are intentionally no-op. + * + * @dev **Deliberately does NOT implement {IAddressListPolarity}, and must not be made to.** Its set is + * an allow-list, so declaring `isAllowList() == true` would be honest about polarity and still wrong: + * the listed addresses are permitted **spenders**, not permitted **holders**. Declaring polarity would + * let `RuleWhitelistWrapper` accept this rule and then read whitelisted spenders as eligible transfer + * participants. Withholding the declaration is what makes the wrapper's fail-closed check refuse it. + * Polarity is only half the question; the other half is what the addresses are. */ abstract contract RuleSpenderWhitelistBase is RuleAddressSet, RuleNFTAdapter, RuleSpenderWhitelistInvariantStorage { /*////////////////////////////////////////////////////////////// @@ -77,6 +84,7 @@ abstract contract RuleSpenderWhitelistBase is RuleAddressSet, RuleNFTAdapter, Ru // Advertise IAddressList: this rule manages an address set and is callable through // the IAddressList interface. return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID || RuleTransferValidation.supportsInterface(interfaceId); } diff --git a/src/rules/validation/abstract/base/RuleWhitelistBase.sol b/src/rules/validation/abstract/base/RuleWhitelistBase.sol index caa7c355..a6a2ede3 100644 --- a/src/rules/validation/abstract/base/RuleWhitelistBase.sol +++ b/src/rules/validation/abstract/base/RuleWhitelistBase.sol @@ -5,13 +5,19 @@ import {RuleAddressSet} from "../RuleAddressSet/RuleAddressSet.sol"; import {RuleWhitelistShared} from "../core/RuleWhitelistShared.sol"; import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; import {IIdentityRegistryVerified} from "../../../interfaces/IIdentityRegistry.sol"; +import {IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; /** * @title RuleWhitelistBase * @notice Core whitelist logic without access-control policy. */ -abstract contract RuleWhitelistBase is RuleAddressSet, RuleWhitelistShared, IIdentityRegistryVerified { +abstract contract RuleWhitelistBase is + RuleAddressSet, + RuleWhitelistShared, + IIdentityRegistryVerified, + IAddressListPolarity +{ /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -60,9 +66,19 @@ abstract contract RuleWhitelistBase is RuleAddressSet, RuleWhitelistShared, IIde // Advertise IAddressList: this rule manages an address set and is usable as a // child rule of RuleWhitelistWrapper, which calls it through IAddressList. return interfaceId == AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + || interfaceId == AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID || RuleTransferValidation.supportsInterface(interfaceId); } + /** + * @inheritdoc IAddressListPolarity + * @dev Listed addresses are the permitted transfer participants. + */ + function isAllowList() public pure virtual override returns (bool) { + return true; + } + /*////////////////////////////////////////////////////////////// ACCESS CONTROL //////////////////////////////////////////////////////////////*/ diff --git a/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol b/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol index 66896b09..15b73e6b 100644 --- a/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol +++ b/src/rules/validation/abstract/base/RuleWhitelistWrapperBase.sol @@ -9,12 +9,22 @@ import {RuleTransferValidation} from "../core/RuleTransferValidation.sol"; /* ==== RuleEngine === */ import {RulesManagementModule} from "RuleEngine/modules/RulesManagementModule.sol"; /* ==== Interfaces === */ -import {IAddressList} from "../../../interfaces/IAddressList.sol"; +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {IAddressListBatchQuery, IAddressListPolarity} from "../../../interfaces/IAddressList.sol"; +import {AddressListInterfaceId} from "../../../interfaces/library/AddressListInterfaceId.sol"; import {IIdentityRegistryVerified} from "../../../interfaces/IIdentityRegistry.sol"; /** * @title Wrapper to call several different whitelist rules (base) - * @dev Child rules must implement {IAddressList}. + * @dev Child rules must implement {IAddressList} and must be ALLOW-lists. + * + * WARNING: {IAddressList} carries membership, not polarity. This wrapper ORs its children's + * `areAddressesListed` answers and reads `true` as ELIGIBLE. A deny-list such as `RuleBlacklist` + * satisfies the same interface and passes every check {addRule} performs, yet its set means the + * opposite: add one as a child and its blacklisted addresses become whitelisted, and {isVerified} + * reports them as verified investors. An ERC-165 guard would not catch this -- a blacklist advertises + * the same interface id, because the interface really is the same. Polarity is configuration + * discipline enforced by the rules manager, not by this contract. Nethermind AuditAgent NM-20. */ abstract contract RuleWhitelistWrapperBase is RulesManagementModule, @@ -213,6 +223,38 @@ abstract contract RuleWhitelistWrapperBase is RuleWhitelistShared._transferredFrom(spender, from, to, value); } + /** + * @notice Rejects a child rule that cannot answer the only question this wrapper asks it. + * @dev Mirrors `RuleEngineBase._checkRule`, which guards its own children the same way. The + * requirement is {IAddressListBatchQuery} — a single function — rather than the whole of + * {IAddressList}, because `areAddressesListed` is the only function the wrapper ever calls; + * demanding the full interface would also require four write functions and three further reads, + * excluding a read-only child that works perfectly. + * + * `ERC165Checker.supportsInterface` is itself non-reverting -- a bounded staticcall returning + * false for a codeless address, a missing selector or malformed return data -- so a hostile + * candidate cannot brick the setter screening it. + * + * WARNING: this cannot check POLARITY. A deny-list answers `areAddressesListed` just as + * faithfully as an allow-list and advertises the same id, so it passes here and then inverts the + * wrapper's meaning. Children must be allow-lists by configuration; see the contract-level note. + * @param rule_ The candidate child rule. + */ + function _checkRule(address rule_) internal view virtual override { + RulesManagementModule._checkRule(rule_); + require( + ERC165Checker.supportsInterface(rule_, AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID), + RuleWhitelistWrapper_ChildIsNotAnAddressList(rule_) + ); + // Membership alone is not enough: the child must also say what membership MEANS. Absence of the + // declaration is a refusal, never an assumed allow-list -- the only reading that fails closed. + require( + ERC165Checker.supportsInterface(rule_, AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID), + RuleWhitelistWrapper_ChildDoesNotDeclarePolarity(rule_) + ); + require(IAddressListPolarity(rule_).isAllowList(), RuleWhitelistWrapper_ChildIsNotAnAllowList(rule_)); + } + /** * @notice Evaluates target addresses across all child rules. * @param targetAddress Addresses to validate (from/to[/spender]). @@ -234,7 +276,7 @@ abstract contract RuleWhitelistWrapperBase is for (uint256 i = 0; i < rulesLength; ++i) { // Call the whitelist rules // Gas cost grows with the number of rules. Keep the wrapper list bounded. - bool[] memory isListed = IAddressList(rule(i)).areAddressesListed(targetAddress); + bool[] memory isListed = IAddressListBatchQuery(rule(i)).areAddressesListed(targetAddress); for (uint256 j = 0; j < targetsLength; ++j) { if (isListed[j] && !result[j]) { result[j] = true; diff --git a/src/rules/validation/abstract/core/BalanceCapManager.sol b/src/rules/validation/abstract/core/BalanceCapManager.sol index 592d12f6..982f545e 100644 --- a/src/rules/validation/abstract/core/BalanceCapManager.sol +++ b/src/rules/validation/abstract/core/BalanceCapManager.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {RuleMaxBalanceInvariantStorage} from "../invariant/RuleMaxBalanceInvariantStorage.sol"; import {IBalanceOf} from "../../../interfaces/IBalanceOf.sol"; import {RuleAddressSetInternal} from "../RuleAddressSet/RuleAddressSetInternal.sol"; +import {CapAccounting} from "./CapAccounting.sol"; /** * @title BalanceCapManager @@ -24,7 +25,7 @@ import {RuleAddressSetInternal} from "../RuleAddressSet/RuleAddressSetInternal.s * @dev The exemption list reuses {RuleAddressSetInternal}, so the set storage, the zero-address guard * and the batch semantics are shared code rather than a second implementation. */ -abstract contract BalanceCapManager is RuleAddressSetInternal, RuleMaxBalanceInvariantStorage { +abstract contract BalanceCapManager is CapAccounting, RuleAddressSetInternal, RuleMaxBalanceInvariantStorage { /** * @notice The token whose balances are observed. * @dev Trusted to report an accurate balance; not trusted to stay callable. @@ -212,8 +213,7 @@ abstract contract BalanceCapManager is RuleAddressSetInternal, RuleMaxBalanceInv if (!available) { return (false, 0); } - uint256 cap = maxBalance; - return (true, balance >= cap ? 0 : cap - balance); + return (true, _capHeadroom(balance, maxBalance)); } /** @@ -261,7 +261,6 @@ abstract contract BalanceCapManager is RuleAddressSetInternal, RuleMaxBalanceInv if (!balanceAvailable) { return (false, false); } - uint256 cap = maxBalance; - return (true, balance > cap || value > cap - balance); + return (true, _capExceededBy(balance, maxBalance, value)); } } diff --git a/src/rules/validation/abstract/core/CapAccounting.sol b/src/rules/validation/abstract/core/CapAccounting.sol new file mode 100644 index 00000000..fdf1a6fa --- /dev/null +++ b/src/rules/validation/abstract/core/CapAccounting.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +/** + * @title CapAccounting + * @notice The one question every cap rule ends in: would adding `value` leave an observed figure + * above its cap? Owns that arithmetic; knows nothing about where either number came from. + * + * @dev Declares **no storage** and no constructor, so adding it to a rule's inheritance chain cannot + * move a slot, and an upgradeable variant may adopt it freely. + * + * @dev Deliberately carries **no notion of pre- or post-update accounting**. Whether the observation + * already includes the value being moved depends on WHICH PATH is running, not on the rule: a + * pre-flight view always runs before the movement, while the write hook runs after it on a token that + * notifies afterwards. A single flag here would answer for both and silently make the pre-flight view + * disagree with enforcement. That distinction belongs one level up, in each rule's + * `_detectTransferRestrictionOnNotify` hook. + */ +abstract contract CapAccounting { + /** + * @notice Whether adding `value` to `observed` would pass `cap`. + * @dev Never reverts and never overflows: the projected total is never formed, the comparison is + * against the remaining headroom instead. Both matter because every caller sits on a + * MUST-NOT-revert ERC-1404 read path. Pass `value = 0` to ask only whether `observed` is already + * over the cap -- which is exactly the question a post-update notification needs to answer. + * @param observed The figure read for this check: a holder's balance, or a total supply. + * @param cap The ceiling `observed` may not pass. + * @param value The amount being added, or `0` when it is already counted in `observed`. + * @return True when the result would breach the cap. + */ + function _capExceededBy(uint256 observed, uint256 cap, uint256 value) internal pure virtual returns (bool) { + // Already over the line whatever is added. Also guarantees the subtraction below. + if (observed > cap) { + return true; + } + return value > cap - observed; + } + + /** + * @notice How much may still be added before `observed` reaches `cap`. + * @param observed The figure read for this check. + * @param cap The ceiling. + * @return The remaining headroom; `0` when already at or over the cap. + */ + function _capHeadroom(uint256 observed, uint256 cap) internal pure virtual returns (uint256) { + return observed >= cap ? 0 : cap - observed; + } +} diff --git a/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol b/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol index 3b371d7e..ee21cd36 100644 --- a/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol +++ b/src/rules/validation/abstract/core/ChainlinkPoRFeedManager.sol @@ -7,6 +7,7 @@ import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.s import {IDecimals} from "../../../interfaces/IDecimals.sol"; import {ITotalSupply} from "../../../interfaces/ITotalSupply.sol"; import {TokenSupplyReader} from "./TokenSupplyReader.sol"; +import {CapAccounting} from "./CapAccounting.sol"; /** * @title ChainlinkPoRFeedManager @@ -26,7 +27,7 @@ import {TokenSupplyReader} from "./TokenSupplyReader.sol"; * both the feed and the token to have code and EIP-6780 makes it permanent: a `try` to a codeless * address reverts *uncatchably*. Assumes a Cancun-or-later chain. */ -abstract contract ChainlinkPoRFeedManager is TokenSupplyReader, RuleChainlinkPoRInvariantStorage { +abstract contract ChainlinkPoRFeedManager is CapAccounting, TokenSupplyReader, RuleChainlinkPoRInvariantStorage { /** * @notice The Proof of Reserve data feed consulted before every mint. */ @@ -207,12 +208,17 @@ abstract contract ChainlinkPoRFeedManager is TokenSupplyReader, RuleChainlinkPoR return (CODE_RESERVES_FEED_UNAVAILABLE, 0); } try feed.latestRoundData() returns (uint80, int256 answer, uint256, uint256 updatedAt, uint80) { - // A negative reserve is meaningless and `updatedAt == 0` marks a round that never completed. - if (answer < 0 || updatedAt == 0) { + // Three malformed answers, not stale ones: a negative reserve is meaningless, `updatedAt == 0` + // marks a round that never completed, and a round stamped in the FUTURE cannot have been written + // by an aggregator on this chain. Rejecting the future stamp here rather than as a staleness case + // is deliberate -- `maxStalenessSeconds == 0` disables freshness checking, and a forged timestamp + // must not become acceptable because an operator chose not to police staleness. + if (answer < 0 || updatedAt == 0 || updatedAt > block.timestamp) { return (CODE_RESERVES_ANSWER_INVALID, 0); } uint256 staleness = maxStalenessSeconds; - if (staleness != 0 && block.timestamp > updatedAt && block.timestamp - updatedAt > staleness) { + // `updatedAt <= block.timestamp` is guaranteed above, so the subtraction cannot underflow. + if (staleness != 0 && block.timestamp - updatedAt > staleness) { return (CODE_RESERVES_FEED_STALE, 0); } // `answer >= 0` was just checked, so the cast to uint256 preserves the value. diff --git a/src/rules/validation/abstract/core/RuleNFTAdapter.sol b/src/rules/validation/abstract/core/RuleNFTAdapter.sol index 47d70dd0..867f765e 100644 --- a/src/rules/validation/abstract/core/RuleNFTAdapter.sol +++ b/src/rules/validation/abstract/core/RuleNFTAdapter.sol @@ -15,6 +15,16 @@ import {ITransferContext} from "../../../interfaces/ITransferContext.sol"; * @title Rule NFT Adapter * @notice Provides ERC-7943 overloads for rules that already implement core transfer checks. * @dev Delegates tokenId overloads to RuleTransferValidation's internal hooks. + * + * @dev **The interfaces here signal "direct transfer" differently, and {_isDelegated} is where that is + * reconciled.** ERC-7943 documents its `spender` as "the address performing the transfer + * (owner/operator)" and {ITransferContext} documents `sender` as the token's `msg.sender`, so on BOTH + * an owner moving their own tokens arrives as `spender == from`. The CMTAT 3-arg/4-arg pair instead + * signals it with `spender == address(0)` and the 3-arg overload. Every entrypoint on this adapter + * therefore normalises `spender == from` to the direct hook; the 4-arg CMTAT path deliberately does + * NOT, because its own convention already distinguishes the two. Do not "align" them: an owner- + * initiated ERC-721 `transferFrom` would then be screened as a delegated transfer, which + * {RuleSpenderWhitelistBase} documents as always allowed. */ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleComplianceExtend, ITransferContext { /** @@ -44,7 +54,7 @@ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleC * @inheritdoc ITransferContext */ function transferred(MultiTokenTransferContext calldata ctx) external virtual override { - if (ctx.sender != address(0) && ctx.sender != ctx.from) { + if (_isDelegated(ctx.sender, ctx.from)) { _transferredFrom(ctx.sender, ctx.from, ctx.to, ctx.value); } else { _transferred(ctx.from, ctx.to, ctx.value); @@ -55,7 +65,7 @@ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleC * @inheritdoc ITransferContext */ function transferred(FungibleTransferContext calldata ctx) external virtual override { - if (ctx.sender != address(0) && ctx.sender != ctx.from) { + if (_isDelegated(ctx.sender, ctx.from)) { _transferredFrom(ctx.sender, ctx.from, ctx.to, ctx.value); } else { _transferred(ctx.from, ctx.to, ctx.value); @@ -98,7 +108,11 @@ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleC virtual override(IERC7943NonFungibleComplianceExtend) { - _transferredFrom(spender, from, to, value); + if (_isDelegated(spender, from)) { + _transferredFrom(spender, from, to, value); + } else { + _transferred(from, to, value); + } } /** @@ -137,7 +151,9 @@ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleC override(IERC7943NonFungibleComplianceExtend) returns (uint8) { - return _detectTransferRestrictionFrom(spender, from, to, value); + return _isDelegated(spender, from) + ? _detectTransferRestrictionFrom(spender, from, to, value) + : _detectTransferRestriction(from, to, value); } /** @@ -176,7 +192,7 @@ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleC override(IERC7943NonFungibleComplianceExtend) returns (bool) { - return _detectTransferRestrictionFrom(spender, from, to, value) + return detectTransferRestrictionFrom(spender, from, to, 0, value) == uint8(IERC1404Extend.REJECTED_CODE_BASE.TRANSFER_OK); } @@ -184,6 +200,21 @@ abstract contract RuleNFTAdapter is RuleTransferValidation, IERC7943NonFungibleC INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ + /** + * @notice Returns whether `spender` acts on behalf of `from`, rather than being `from` itself. + * @dev The whole adapter routes on this. `spender == from` is an owner-initiated transfer and takes + * the direct hook, matching what a plain `transfer` produces on the CMTAT path (`spender == 0`, + * 3-arg overload). Nethermind AuditAgent NM-6: the ERC-7943 overloads used to call the + * spender-aware hook unconditionally, so an owner-initiated ERC-721 `transferFrom` was screened as + * delegated while the identical {ITransferContext} call was not. + * @param spender Address performing the transfer, as reported by the calling interface. + * @param from Address the tokens leave. + * @return True when the transfer is delegated and the spender must be screened. + */ + function _isDelegated(address spender, address from) internal pure virtual returns (bool) { + return spender != address(0) && spender != from; + } + /** * @notice Internal hook for post-transfer validation or state updates. * @param from Address tokens are transferred from. diff --git a/src/rules/validation/abstract/core/TotalSupplyCapManager.sol b/src/rules/validation/abstract/core/TotalSupplyCapManager.sol index 7d1b3137..c289e850 100644 --- a/src/rules/validation/abstract/core/TotalSupplyCapManager.sol +++ b/src/rules/validation/abstract/core/TotalSupplyCapManager.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {RuleMaxTotalSupplyInvariantStorage} from "../invariant/RuleMaxTotalSupplyInvariantStorage.sol"; import {ITotalSupply} from "../../../interfaces/ITotalSupply.sol"; import {TokenSupplyReader} from "./TokenSupplyReader.sol"; +import {CapAccounting} from "./CapAccounting.sol"; /** * @title TotalSupplyCapManager @@ -18,7 +19,7 @@ import {TokenSupplyReader} from "./TokenSupplyReader.sol"; * {TokenSupplyReader} via {_supplyToken}; the deployment precondition documented there applies * unchanged. */ -abstract contract TotalSupplyCapManager is TokenSupplyReader, RuleMaxTotalSupplyInvariantStorage { +abstract contract TotalSupplyCapManager is CapAccounting, TokenSupplyReader, RuleMaxTotalSupplyInvariantStorage { /** * @dev tokenContract is trusted to report an *accurate* totalSupply -- nothing on-chain can * verify that -- but it is NOT trusted to stay callable: a reverting or codeless token yields @@ -128,7 +129,6 @@ abstract contract TotalSupplyCapManager is TokenSupplyReader, RuleMaxTotalSupply if (!supplyAvailable) { return (false, false); } - uint256 cap = maxTotalSupply; - return (true, currentSupply > cap || value > cap - currentSupply); + return (true, _capExceededBy(currentSupply, maxTotalSupply, value)); } } diff --git a/src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol b/src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol index 478fa657..f337e171 100644 --- a/src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol +++ b/src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol @@ -59,10 +59,13 @@ abstract contract RuleChainlinkPoRInvariantStorage is RuleSharedInvariantStorage uint8 public constant CODE_RESERVES_FEED_STALE = 76; /** * @notice Restriction code returned when the feed responded but the answer cannot be used: - * a negative reserve, or an incomplete round (`updatedAt == 0`). + * a negative reserve, an incomplete round (`updatedAt == 0`), or a round stamped in the future. * @dev Distinct from {CODE_RESERVES_FEED_UNAVAILABLE}: here a round *was* returned, so the feed * is reachable and the problem is the data. An operator seeing this checks whether the * configured address is really a Proof of Reserve feed, or waits for the round to complete. + * @dev A future `updatedAt` is rejected here, NOT as staleness: `maxStalenessSeconds == 0` disables + * freshness checking, and a forged timestamp must not become acceptable because an operator chose + * not to police staleness. */ uint8 public constant CODE_RESERVES_ANSWER_INVALID = 77; /** diff --git a/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol b/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol new file mode 100644 index 00000000..6db52e97 --- /dev/null +++ b/src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {RuleChainlinkPoR} from "./RuleChainlinkPoR.sol"; + +/** + * @title RuleChainlinkPoRERC3643 + * @notice {RuleChainlinkPoR} for **ERC-3643 tokens only**. Identical reserve logic; the sole difference is WHEN the + * token reports the mint. + * + * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 / + * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes + * the new tokens. CMTAT calls the rule first and must use plain {RuleChainlinkPoR}. + * + * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The + * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the reserves reported by the feed; this variant + * on CMTAT ignores the pending amount and weakens enforcement. + * + * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643 + * calls `canTransfer` before `_mint`. + */ +contract RuleChainlinkPoRERC3643 is RuleChainlinkPoR { + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + /** + * @param admin Address that receives the default admin role. + * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero). + * @param tokenDecimals_ Decimals of that token (0 to 18, checked against `decimals()` when exposed). + * @param reservesFeed_ Proof of Reserve data feed implementing `AggregatorV3Interface`. + * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check. + */ + constructor( + address admin, + address tokenContract_, + uint8 tokenDecimals_, + AggregatorV3Interface reservesFeed_, + uint256 maxStalenessSeconds_ + ) RuleChainlinkPoR(admin, tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_) {} + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Enforcement for a token that reports the mint after performing it. + * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the + * minted amount, so the comparison reduces to "is the post-mint supply within the reserves". + * @param from Sender address; the zero address denotes the mint this rule gates. + * @param to Recipient address. + * @return The restriction code the write hook enforces. + */ + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + virtual + override + returns (uint8) + { + return _detectTransferRestriction(from, to, 0); + } +} diff --git a/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol b/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol new file mode 100644 index 00000000..7bd3a60e --- /dev/null +++ b/src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {AggregatorV3Interface} from "../../interfaces/AggregatorV3Interface.sol"; +import {RuleChainlinkPoROwnable2Step} from "./RuleChainlinkPoROwnable2Step.sol"; + +/** + * @title RuleChainlinkPoRERC3643Ownable2Step + * @notice {RuleChainlinkPoROwnable2Step} for **ERC-3643 tokens only**. Identical reserve logic; the sole difference is WHEN the + * token reports the mint. + * + * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 / + * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes + * the new tokens. CMTAT calls the rule first and must use plain {RuleChainlinkPoROwnable2Step}. + * + * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The + * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the reserves reported by the feed; this variant + * on CMTAT ignores the pending amount and weakens enforcement. + * + * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643 + * calls `canTransfer` before `_mint`. + */ +contract RuleChainlinkPoRERC3643Ownable2Step is RuleChainlinkPoROwnable2Step { + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + /** + * @param owner Contract owner. + * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero). + * @param tokenDecimals_ Decimals of that token (0 to 18, checked against `decimals()` when exposed). + * @param reservesFeed_ Proof of Reserve data feed implementing `AggregatorV3Interface`. + * @param maxStalenessSeconds_ Initial staleness threshold in seconds; 0 disables the check. + */ + constructor( + address owner, + address tokenContract_, + uint8 tokenDecimals_, + AggregatorV3Interface reservesFeed_, + uint256 maxStalenessSeconds_ + ) RuleChainlinkPoROwnable2Step(owner, tokenContract_, tokenDecimals_, reservesFeed_, maxStalenessSeconds_) {} + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Enforcement for a token that reports the mint after performing it. + * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the + * minted amount, so the comparison reduces to "is the post-mint supply within the reserves". + * @param from Sender address; the zero address denotes the mint this rule gates. + * @param to Recipient address. + * @return The restriction code the write hook enforces. + */ + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + virtual + override + returns (uint8) + { + return _detectTransferRestriction(from, to, 0); + } +} diff --git a/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol b/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol new file mode 100644 index 00000000..2eef9881 --- /dev/null +++ b/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {RuleMaxTotalSupply} from "./RuleMaxTotalSupply.sol"; + +/** + * @title RuleMaxTotalSupplyERC3643 + * @notice {RuleMaxTotalSupply} for **ERC-3643 tokens only**. Identical supply-cap logic; the sole difference is WHEN the + * token reports the mint. + * + * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 / + * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes + * the new tokens. CMTAT calls the rule first and must use plain {RuleMaxTotalSupply}. + * + * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The + * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the configured ceiling; this variant + * on CMTAT ignores the pending amount and weakens enforcement. + * + * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643 + * calls `canTransfer` before `_mint`. + */ +contract RuleMaxTotalSupplyERC3643 is RuleMaxTotalSupply { + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + /** + * @param admin Address that receives the default admin role. + * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero). + * @param maxTotalSupply_ Initial maximum supply. + */ + constructor(address admin, address tokenContract_, uint256 maxTotalSupply_) + RuleMaxTotalSupply(admin, tokenContract_, maxTotalSupply_) + {} + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Enforcement for a token that reports the mint after performing it. + * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the + * minted amount, so the comparison reduces to "is the post-mint supply within the ceiling". + * @param from Sender address; the zero address denotes the mint this rule gates. + * @param to Recipient address. + * @return The restriction code the write hook enforces. + */ + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + virtual + override + returns (uint8) + { + return _detectTransferRestriction(from, to, 0); + } +} diff --git a/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol b/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol new file mode 100644 index 00000000..c3ee962b --- /dev/null +++ b/src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {RuleMaxTotalSupplyOwnable2Step} from "./RuleMaxTotalSupplyOwnable2Step.sol"; + +/** + * @title RuleMaxTotalSupplyERC3643Ownable2Step + * @notice {RuleMaxTotalSupplyOwnable2Step} for **ERC-3643 tokens only**. Identical supply-cap logic; the sole difference is WHEN the + * token reports the mint. + * + * @dev **Use this variant if and only if the token calls compliance AFTER it has moved the value.** ERC-3643 / + * T-REX does: `mint` runs `_mint` and only then `_tokenCompliance.created`, so `totalSupply()` already includes + * the new tokens. CMTAT calls the rule first and must use plain {RuleMaxTotalSupplyOwnable2Step}. + * + * @dev **Picking the wrong variant breaks the cap silently, and nothing reverts at configuration time.** The + * stock rule on ERC-3643 counts the minted amount twice and rejects mints that are within the configured ceiling; this variant + * on CMTAT ignores the pending amount and weakens enforcement. + * + * @dev Only the WRITE path is re-phased — the read views still project the pending amount, because ERC-3643 + * calls `canTransfer` before `_mint`. + */ +contract RuleMaxTotalSupplyERC3643Ownable2Step is RuleMaxTotalSupplyOwnable2Step { + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + /** + * @param owner Contract owner. + * @param tokenContract_ Token contract that exposes totalSupply (must be non-zero). + * @param maxTotalSupply_ Initial maximum supply. + */ + constructor(address owner, address tokenContract_, uint256 maxTotalSupply_) + RuleMaxTotalSupplyOwnable2Step(owner, tokenContract_, maxTotalSupply_) + {} + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Enforcement for a token that reports the mint after performing it. + * @dev Re-asks the standard check with nothing left to add: `totalSupply()` already includes the + * minted amount, so the comparison reduces to "is the post-mint supply within the ceiling". + * @param from Sender address; the zero address denotes the mint this rule gates. + * @param to Recipient address. + * @return The restriction code the write hook enforces. + */ + function _detectTransferRestrictionOnNotify( + address from, + address to, + uint256 /* value */ + ) + internal + view + virtual + override + returns (uint8) + { + return _detectTransferRestriction(from, to, 0); + } +} diff --git a/test/CapAccounting/ERC3643CapSeams.t.sol b/test/CapAccounting/ERC3643CapSeams.t.sol new file mode 100644 index 00000000..fce7e777 --- /dev/null +++ b/test/CapAccounting/ERC3643CapSeams.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {HelperContract} from "../HelperContract.sol"; +import { + ERC3643ChainlinkPoRHarness, + ERC3643MaxBalanceHarness, + ERC3643MaxTotalSupplyHarness, + TrackedSupplyHarness +} from "src/mocks/harness/ERC3643CapHarnesses.sol"; +import {AggregatorV3Mock} from "src/mocks/AggregatorV3Mock.sol"; +import {AggregatorV3Interface} from "src/rules/interfaces/AggregatorV3Interface.sol"; +import {BalanceOfMock} from "src/mocks/BalanceOfMock.sol"; +import {RuleChainlinkPoR} from "src/rules/validation/deployment/RuleChainlinkPoR.sol"; +import {RuleMaxBalance} from "src/rules/validation/deployment/RuleMaxBalance.sol"; +import {RuleMaxTotalSupply} from "src/rules/validation/deployment/RuleMaxTotalSupply.sol"; +import {TotalSupplyMock} from "src/mocks/TotalSupplyMock.sol"; + +/** + * @title ERC3643CapSeams + * @notice Proves the two seams the cap rules expose are sufficient to build an ERC-3643 variant, + * without changing what the stock (CMTAT) rules do. + * @dev The three cap rules assume the token calls them BEFORE moving the value, so the observation + * still excludes it. ERC-3643 / T-REX calls AFTER — `Token.transfer` runs `_transfer` then + * `_tokenCompliance.transferred`, and `mint` runs `_mint` then `created` — so the observation + * already includes it and the stock rule counts it twice, rejecting transfers that are within + * the cap (Nethermind AuditAgent NM-11). + * + * Each test below simulates both call orders against the same cap and asserts: + * - the stock rule is correct pre-update and double-counts post-update; + * - the harness, which overrides one hook, is correct post-update; + * - neither rule ever admits anything ABOVE the cap. + */ +contract ERC3643CapSeams is Test, HelperContract { + uint256 private constant CAP = 1000; + + /*////////////////////////////////////////////////////////////// + SEAM 1 — MAX TOTAL SUPPLY + //////////////////////////////////////////////////////////////*/ + + function testMaxTotalSupply_StockRuleDoubleCountsUnderPostUpdateAccounting() public { + TotalSupplyMock token = new TotalSupplyMock(); + RuleMaxTotalSupply rule = new RuleMaxTotalSupply(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + + // Pre-update (CMTAT): supply still 0 when the rule is called. A mint of exactly the cap fits. + token.setTotalSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, CAP), TRANSFER_OK); + + // Post-update (T-REX): the mint already landed, so totalSupply == CAP when the rule is called. + // The stock rule adds CAP again and rejects a mint that exactly fills the cap. + token.setTotalSupply(CAP); + assertEq( + rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, CAP), + CODE_MAX_TOTAL_SUPPLY_EXCEEDED, + "NM-11: the stock rule counts the value twice on a post-update token" + ); + } + + function testMaxTotalSupply_Erc3643HarnessIsCorrectUnderPostUpdateAccounting() public { + TotalSupplyMock token = new TotalSupplyMock(); + ERC3643MaxTotalSupplyHarness rule = new ERC3643MaxTotalSupplyHarness(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + + // A mint that exactly fills the cap: post-mint supply == CAP, which is allowed. + token.setTotalSupply(CAP); + vm.prank(address(token)); + rule.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + + // One unit more: post-mint supply == CAP + 1, which is not. + token.setTotalSupply(CAP + 1); + vm.prank(address(token)); + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + function testMaxTotalSupply_PreFlightViewStillCountsTheValue() public { + // The read path must NOT be re-phased: a pre-flight query always runs before the movement, + // on either kind of token, so it still has to add `value`. + TotalSupplyMock token = new TotalSupplyMock(); + ERC3643MaxTotalSupplyHarness rule = new ERC3643MaxTotalSupplyHarness(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + + token.setTotalSupply(CAP); + assertEq( + rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), + CODE_MAX_TOTAL_SUPPLY_EXCEEDED, + "pre-flight must still project the pending value" + ); + } + + /*////////////////////////////////////////////////////////////// + SEAM 1 — MAX BALANCE + //////////////////////////////////////////////////////////////*/ + + function testMaxBalance_StockRuleDoubleCountsUnderPostUpdateAccounting() public { + BalanceOfMock token = new BalanceOfMock(); + RuleMaxBalance rule = new RuleMaxBalance(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + + token.setBalance(ADDRESS1, 0); + assertEq(rule.detectTransferRestriction(ADDRESS2, ADDRESS1, CAP), TRANSFER_OK); + + token.setBalance(ADDRESS1, CAP); + assertEq( + rule.detectTransferRestriction(ADDRESS2, ADDRESS1, CAP), + rule.CODE_MAX_BALANCE_EXCEEDED(), + "NM-11: the stock rule counts the value twice on a post-update token" + ); + } + + function testMaxBalance_Erc3643HarnessIsCorrectUnderPostUpdateAccounting() public { + BalanceOfMock token = new BalanceOfMock(); + ERC3643MaxBalanceHarness rule = new ERC3643MaxBalanceHarness(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + + token.setBalance(ADDRESS1, CAP); + vm.prank(address(token)); + rule.transferred(ADDRESS2, ADDRESS1, CAP); + + token.setBalance(ADDRESS1, CAP + 1); + vm.prank(address(token)); + vm.expectRevert(); + rule.transferred(ADDRESS2, ADDRESS1, 1); + } + + /*////////////////////////////////////////////////////////////// + SEAM 1 — CHAINLINK PROOF OF RESERVE + //////////////////////////////////////////////////////////////*/ + + function testChainlinkPoR_Erc3643HarnessIsCorrectUnderPostUpdateAccounting() public { + TotalSupplyMock token = new TotalSupplyMock(); + AggregatorV3Mock feed = new AggregatorV3Mock(0, int256(CAP)); + + RuleChainlinkPoR stock = + new RuleChainlinkPoR(DEFAULT_ADMIN_ADDRESS, address(token), 0, AggregatorV3Interface(address(feed)), 0); + ERC3643ChainlinkPoRHarness harness = new ERC3643ChainlinkPoRHarness( + DEFAULT_ADMIN_ADDRESS, address(token), 0, AggregatorV3Interface(address(feed)), 0 + ); + + // Reserves back exactly CAP. Post-mint supply is CAP, so the mint is fully backed. + token.setTotalSupply(CAP); + assertEq( + stock.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, CAP), + CODE_RESERVES_EXCEEDED, + "NM-11: the stock rule counts the minted value twice against the reserves" + ); + vm.prank(address(token)); + harness.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + + // Minting past the reserves is still rejected by the harness. + token.setTotalSupply(CAP + 1); + vm.prank(address(token)); + vm.expectRevert(); + harness.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + /*////////////////////////////////////////////////////////////// + SEAM 2 — OBSERVATION SOURCE + //////////////////////////////////////////////////////////////*/ + + function testTrackedSupply_ObservationCanComeFromTheRuleInsteadOfTheToken() public { + TotalSupplyMock token = new TotalSupplyMock(); + TrackedSupplyHarness rule = new TrackedSupplyHarness(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + + // The token reports a supply the rule must ignore entirely. + token.setTotalSupply(type(uint256).max); + + rule.setTrackedSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, CAP), TRANSFER_OK); + + rule.setTrackedSupply(CAP); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), CODE_MAX_TOTAL_SUPPLY_EXCEEDED); + } +} diff --git a/test/ERC3643Compliance/ERC3643RuleEngineWhitelist.t.sol b/test/ERC3643Compliance/ERC3643RuleEngineWhitelist.t.sol index 5ad8399d..1d14976b 100644 --- a/test/ERC3643Compliance/ERC3643RuleEngineWhitelist.t.sol +++ b/test/ERC3643Compliance/ERC3643RuleEngineWhitelist.t.sol @@ -22,7 +22,7 @@ import {RuleWhitelist} from "src/rules/validation/deployment/RuleWhitelist.sol"; * Wiring, transcribed from `Token.setCompliance` (`Token.sol:515-522`): the token calls * `bindToken(address(this))` on the compliance contract **itself**, so the engine needs * `setTokenSelfBindingApproval(token, true)` beforehand. That path exists in - * `ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange` specifically for + * `TokenBindingExtendedModule._authorizeTokenBindingChange` specifically for * ERC-3643 compatibility. */ contract ERC3643RuleEngineWhitelist is Test, HelperContract, IdentityRegistryWhitelistInvariantStorage { diff --git a/test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol b/test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol new file mode 100644 index 00000000..e15dc25d --- /dev/null +++ b/test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {ComplianceNotFollowed, Token} from "ERC3643/token/Token.sol"; +import {RuleEngine} from "RuleEngine/deployment/RuleEngine.sol"; +import {AggregatorV3Mock} from "src/mocks/AggregatorV3Mock.sol"; +import {IdentityRegistryWhitelist} from "src/registry/IdentityRegistryWhitelist.sol"; +import {AggregatorV3Interface} from "src/rules/interfaces/AggregatorV3Interface.sol"; +import { + RuleChainlinkPoRInvariantStorage +} from "src/rules/validation/abstract/invariant/RuleChainlinkPoRInvariantStorage.sol"; +import {RuleChainlinkPoR} from "src/rules/validation/deployment/RuleChainlinkPoR.sol"; +import {RuleChainlinkPoRERC3643} from "src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol"; + +/** + * @title Proof of Reserve against the REAL vendored ERC-3643 token + * @notice Deploys the genuine `Token` from `lib/ERC-3643/` (4.2.0-beta1) and drives it through: + * + * real ERC-3643 Token ── compliance slot ──▶ RuleEngine ──▶ RuleChainlinkPoR[ERC3643] + * └─ identity slot ────▶ IdentityRegistryWhitelist + * + * @dev The point of this suite is the ORDER in which the real token consults compliance on a mint: + * + * ```solidity + * function mint(address _to, uint256 _amount) public onlyAgent { + * require(_tokenCompliance.canTransfer(address(0), _to, _amount), ComplianceNotFollowed()); + * _mint(_to, _amount); // <-- supply changes HERE + * _tokenCompliance.created(_to, _amount); // <-- rule notified AFTERWARDS + * } + * ``` + * + * One transaction, both paths, different accounting. `canTransfer` runs BEFORE the mint, so it + * must project `_amount`; `created` runs AFTER, and `RuleEngine` forwards it as the three-argument + * `transferred(address(0), to, value)`, by which point `totalSupply()` already includes `_amount`. + * {RuleChainlinkPoRERC3643} re-phases only the second. The stock {RuleChainlinkPoR}, built for + * CMTAT (which calls the rule first), counts the amount twice and reverts a fully backed mint -- + * `testStockRuleRevertsAFullyBackedMint` is that regression, run against the real token rather + * than a mock. + * + * @dev Built by the dedicated profile because `Token.sol` pins `pragma solidity 0.8.30` exactly: + * + * FOUNDRY_PROFILE=erc3643 forge test + */ +contract ERC3643RealTokenChainlinkPoR is Test, RuleChainlinkPoRInvariantStorage { + address private constant ADMIN = address(1); + address private constant AGENT = address(10); + address private constant INVESTOR = address(11); + address private constant INVESTOR2 = address(12); + + /// @dev Feed and token both report 0 decimals, so a reserve answer is a token amount as-is. + uint256 private constant RESERVES = 1000; + uint8 private constant TRANSFER_OK_CODE = 0; + + IdentityRegistryWhitelist private registry; + AggregatorV3Mock private feed; + RuleEngine private engine; + Token private token; + + function setUp() public { + token = new Token(); + feed = new AggregatorV3Mock(0, int256(RESERVES)); + + vm.startPrank(ADMIN); + registry = new IdentityRegistryWhitelist(ADMIN); + engine = new RuleEngine(ADMIN, address(0), address(0)); + vm.stopPrank(); + + _wire(); + } + + /// @dev Everything except which rule sits in the engine; the rule is added per test. + function _wire() private { + vm.prank(ADMIN); + engine.setTokenSelfBindingApproval(address(token), true); + + token.init(address(registry), address(engine), "Real ERC-3643 PoR", "R3643", 0, address(0)); + + token.addAgent(AGENT); + vm.prank(AGENT); + token.unpause(); + + bytes32 registrarRole = registry.IDENTITY_REGISTRAR_ROLE(); + vm.startPrank(ADMIN); + registry.grantRole(registrarRole, AGENT); + registry.grantRole(registrarRole, address(token)); + vm.stopPrank(); + + vm.startPrank(AGENT); + registry.registerIdentity(INVESTOR, address(0), 0); + registry.registerIdentity(INVESTOR2, address(0), 0); + vm.stopPrank(); + } + + function _useErc3643Rule() private returns (RuleChainlinkPoRERC3643 rule) { + rule = new RuleChainlinkPoRERC3643(ADMIN, address(token), 0, AggregatorV3Interface(address(feed)), 0); + vm.prank(ADMIN); + engine.addRule(rule); + } + + function _useStockRule() private returns (RuleChainlinkPoR rule) { + rule = new RuleChainlinkPoR(ADMIN, address(token), 0, AggregatorV3Interface(address(feed)), 0); + vm.prank(ADMIN); + engine.addRule(rule); + } + + /*////////////////////////////////////////////////////////////// + THE ERC-3643 VARIANT + //////////////////////////////////////////////////////////////*/ + + function testMintUpToTheReservesSucceeds() public { + _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES); + + assertEq(token.totalSupply(), RESERVES, "a mint of exactly the backed supply must land"); + assertEq(token.balanceOf(INVESTOR), RESERVES); + } + + function testMintBeyondTheReservesIsRejected() public { + _useErc3643Rule(); + + // Blocked by the pre-flight `canTransfer` the token runs before `_mint`. + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, RESERVES + 1); + + assertEq(token.totalSupply(), 0, "nothing may be issued past the reserves"); + } + + function testIncrementalMintsShareTheSameReserveCeiling() public { + _useErc3643Rule(); + + vm.startPrank(AGENT); + token.mint(INVESTOR, 600); + token.mint(INVESTOR2, 400); + vm.stopPrank(); + assertEq(token.totalSupply(), RESERVES); + + // The reserves are now fully committed; one more unit is not backed. + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, 1); + } + + function testRaisingTheReservesRaisesTheCeiling() public { + _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES); + + feed.setAnswer(int256(RESERVES * 2)); + + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES); + assertEq(token.totalSupply(), RESERVES * 2); + } + + function testTransfersAndBurnsAreNeverGatedByTheFeed() public { + _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES); + + // Reserves collapse to nothing: issuance stops, holders stay mobile. + feed.setAnswer(0); + + vm.prank(INVESTOR); + token.transfer(INVESTOR2, 400); + assertEq(token.balanceOf(INVESTOR2), 400); + + vm.prank(AGENT); + token.burn(INVESTOR2, 400); + assertEq(token.totalSupply(), RESERVES - 400); + + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, 1); + } + + function testAStaleFeedStopsIssuanceButNotHolders() public { + RuleChainlinkPoRERC3643 rule = _useErc3643Rule(); + + vm.prank(ADMIN); + rule.setMaxStalenessSeconds(1 hours); + + vm.prank(AGENT); + token.mint(INVESTOR, 100); + + vm.warp(block.timestamp + 2 hours); + + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, 1); + + // ...but the existing holder can still move and exit. + vm.prank(INVESTOR); + token.transfer(INVESTOR2, 100); + assertEq(token.balanceOf(INVESTOR2), 100); + } + + /*////////////////////////////////////////////////////////////// + WHY THE STOCK RULE IS NOT USABLE HERE (NM-11) + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The stock, CMTAT-shaped rule double-counts the mint on a real ERC-3643 token. + * @dev `canTransfer` passes (supply 0 + 1000 <= 1000), the token mints, and then `created` finds + * `totalSupply() == 1000` and adds the amount a second time. The mint reverts even though it + * is exactly and fully backed -- the pre-flight answer and enforcement disagree inside one + * transaction. + */ + function testStockRuleRevertsAFullyBackedMint() public { + _useStockRule(); + + vm.prank(AGENT); + vm.expectRevert(); + token.mint(INVESTOR, RESERVES); + + assertEq(token.totalSupply(), 0, "the fully backed mint was rejected"); + } + + /** + * @notice The stock rule halves the largest SINGLE mint it will accept. + * @dev Every mint has its amount counted twice -- once by the post-mint `totalSupply()` and once + * as `value` -- so from an empty supply the ceiling on one mint is `RESERVES / 2`. Note the + * damage is not a uniform halving of the cap: a series of small mints can still creep up to + * the full reserves, since only the amount in flight is double-counted. What is guaranteed + * is that some fully backed mints are refused, and which ones depends on how issuance is + * chunked -- a worse failure mode than a plainly halved cap, because it looks intermittent. + */ + function testStockRuleHalvesTheLargestSingleMint() public { + _useStockRule(); + + // One over half the reserves: post-mint supply 501 plus 501 again exceeds 1000. + vm.prank(AGENT); + vm.expectRevert(); + token.mint(INVESTOR, RESERVES / 2 + 1); + + // Exactly half is the most it will take in one go. + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES / 2); + assertEq(token.totalSupply(), RESERVES / 2); + } + + /*////////////////////////////////////////////////////////////// + THE READ PATH IS NOT RE-PHASED + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The ERC-3643 variant still projects the pending amount on the read path. + * @dev The token itself depends on this: it calls `canTransfer(address(0), to, amount)` BEFORE + * `_mint`, so a view that ignored `amount` would wave through a mint the write hook then + * reverts. Only the notification is re-phased. + */ + function testPreFlightViewStillProjectsThePendingAmount() public { + RuleChainlinkPoRERC3643 rule = _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES); + + assertEq( + rule.detectTransferRestriction(address(0), INVESTOR, 1), + CODE_RESERVES_EXCEEDED, + "pre-flight must still count the amount being requested" + ); + assertFalse(rule.canTransfer(address(0), INVESTOR, 1)); + + // And it agrees with the token's own pre-flight consultation. + assertFalse(engine.canTransfer(address(0), INVESTOR, 1)); + } + + /*////////////////////////////////////////////////////////////// + THE SUPPLY READ ITSELF + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Code 78 (`CODE_TOTAL_SUPPLY_UNAVAILABLE`) is unreachable against a directly deployed + * ERC-3643 token, so the guarded read costs nothing but is not dead weight either. + * @dev `Token.totalSupply()` is `external view { return _totalSupply; }` — no modifier, no + * external call, so it cannot revert and `_currentSupply()` always reports available. The + * branch still earns its place: the standard T-REX deployment puts the token behind a + * `TokenProxy` whose implementation is resolved through an `ImplementationAuthority`, and a + * proxy repointed at a bad implementation *can* make `totalSupply()` revert. The rule then + * returns 78 and blocks minting rather than breaking the MUST-NOT-revert views. + */ + function testSupplyIsAlwaysReadableOnADirectlyDeployedToken() public { + RuleChainlinkPoRERC3643 rule = _useErc3643Rule(); + + assertEq(rule.detectTransferRestriction(address(0), INVESTOR, 1), TRANSFER_OK_CODE); + + vm.prank(AGENT); + token.mint(INVESTOR, RESERVES); + + // Still readable with a non-zero supply; the ceiling, not the read, is what now binds. + assertEq(rule.detectTransferRestriction(address(0), INVESTOR, 1), CODE_RESERVES_EXCEEDED); + } + + /** + * @notice DEPLOYMENT ORDER: build the rule AFTER `Token.init`, or its cached decimals are wrong. + * @dev ERC-3643 deploys then initialises, and an uninitialised `Token` reports `decimals() == 0`. + * The rule's constructor probes `decimals()` and accepts a matching `0`, so a rule built + * first is happily configured for a 0-decimals token — and then `init(..., 18, ...)` makes it + * an 18-decimals token while the rule still believes 0. Nothing reverts and no event marks + * it; the reserve answer is simply scaled by `10 ** 18` too little, and every mint is + * refused. The same mistake with the decimals reversed would over-mint instead. + * + * There is no on-chain fix: the constructor probe genuinely succeeded. The remedy is + * ordering (construct after `init`) or calling `setTokenMetadata` afterwards to re-sync. + */ + function testRuleBuiltBeforeInitCachesTheWrongDecimals() public { + Token fresh = new Token(); + assertEq(fresh.decimals(), 0, "an uninitialised token reports 0 decimals"); + + AggregatorV3Mock scaledFeed = new AggregatorV3Mock(8, int256(RESERVES * 1e8)); + RuleChainlinkPoRERC3643 early = + new RuleChainlinkPoRERC3643(ADMIN, address(fresh), 0, AggregatorV3Interface(address(scaledFeed)), 0); + + RuleEngine freshEngine = new RuleEngine(ADMIN, address(0), address(0)); + vm.prank(ADMIN); + freshEngine.setTokenSelfBindingApproval(address(fresh), true); + fresh.init(address(registry), address(freshEngine), "Late init", "LATE", 18, address(0)); + + assertEq(fresh.decimals(), 18, "the token is now an 18-decimals token"); + assertEq(early.tokenDecimals(), 0, "but the rule still believes 0"); + + (, uint256 backed) = early.maxBackedSupply(); + assertEq(backed, RESERVES, "reserves scaled into 0 decimals"); + + // Re-syncing after init is the operator-side remedy. + vm.prank(ADMIN); + early.setTokenMetadata(address(fresh), 18); + (, uint256 corrected) = early.maxBackedSupply(); + assertEq(corrected, RESERVES * 1e18, "and now the ceiling is in the token's own units"); + } + + /// @notice The variant reports the same reserve ceiling as the stock rule; only enforcement differs. + function testMaxBackedSupplyIsUnchanged() public { + RuleChainlinkPoRERC3643 rule = _useErc3643Rule(); + + (uint8 code, uint256 backed) = rule.maxBackedSupply(); + assertEq(code, 0); + assertEq(backed, RESERVES); + } +} diff --git a/test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol b/test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol new file mode 100644 index 00000000..12a613f2 --- /dev/null +++ b/test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity 0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {ComplianceNotFollowed, Token} from "ERC3643/token/Token.sol"; +import {RuleEngine} from "RuleEngine/deployment/RuleEngine.sol"; +import {AggregatorV3Mock} from "src/mocks/AggregatorV3Mock.sol"; +import {IdentityRegistryWhitelist} from "src/registry/IdentityRegistryWhitelist.sol"; +import {AggregatorV3Interface} from "src/rules/interfaces/AggregatorV3Interface.sol"; +import { + RuleMaxTotalSupplyInvariantStorage +} from "src/rules/validation/abstract/invariant/RuleMaxTotalSupplyInvariantStorage.sol"; +import {RuleChainlinkPoRERC3643} from "src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol"; +import {RuleMaxTotalSupply} from "src/rules/validation/deployment/RuleMaxTotalSupply.sol"; +import {RuleMaxTotalSupplyERC3643} from "src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol"; + +/** + * @title Max total supply against the REAL vendored ERC-3643 token + * @notice Deploys the genuine `Token` from `lib/ERC-3643/` (4.2.0-beta1) and drives it through: + * + * real ERC-3643 Token ── compliance slot ──▶ RuleEngine ──▶ RuleMaxTotalSupply[ERC3643] + * └─ identity slot ────▶ IdentityRegistryWhitelist + * + * @dev Same accounting question as the Proof-of-Reserve suite: the token calls compliance on BOTH + * sides of the mint -- + * + * ```solidity + * require(_tokenCompliance.canTransfer(address(0), _to, _amount), ComplianceNotFollowed()); + * _mint(_to, _amount); // supply changes HERE + * _tokenCompliance.created(_to, _amount); // rule notified AFTERWARDS + * ``` + * + * -- and `RuleEngine` forwards `created` as the three-argument `transferred(address(0), to, value)`. + * {RuleMaxTotalSupplyERC3643} re-phases only the notification. + * + * @dev The last section covers the composition the documentation prescribes: {RuleChainlinkPoRERC3643} + * has no margin parameter, so a static ceiling is added by putting both rules in the same engine. + * + * @dev Run with the dedicated profile: `FOUNDRY_PROFILE=erc3643 forge test`. + */ +contract ERC3643RealTokenMaxTotalSupply is Test, RuleMaxTotalSupplyInvariantStorage { + address private constant ADMIN = address(1); + address private constant AGENT = address(10); + address private constant INVESTOR = address(11); + address private constant INVESTOR2 = address(12); + + uint256 private constant CAP = 1000; + + IdentityRegistryWhitelist private registry; + RuleEngine private engine; + Token private token; + + function setUp() public { + token = new Token(); + + vm.startPrank(ADMIN); + registry = new IdentityRegistryWhitelist(ADMIN); + engine = new RuleEngine(ADMIN, address(0), address(0)); + vm.stopPrank(); + + vm.prank(ADMIN); + engine.setTokenSelfBindingApproval(address(token), true); + + token.init(address(registry), address(engine), "Real ERC-3643 Cap", "R3643", 0, address(0)); + + token.addAgent(AGENT); + vm.prank(AGENT); + token.unpause(); + + bytes32 registrarRole = registry.IDENTITY_REGISTRAR_ROLE(); + vm.startPrank(ADMIN); + registry.grantRole(registrarRole, AGENT); + registry.grantRole(registrarRole, address(token)); + vm.stopPrank(); + + vm.startPrank(AGENT); + registry.registerIdentity(INVESTOR, address(0), 0); + registry.registerIdentity(INVESTOR2, address(0), 0); + vm.stopPrank(); + } + + function _useErc3643Rule() private returns (RuleMaxTotalSupplyERC3643 rule) { + rule = new RuleMaxTotalSupplyERC3643(ADMIN, address(token), CAP); + vm.prank(ADMIN); + engine.addRule(rule); + } + + function _useStockRule() private returns (RuleMaxTotalSupply rule) { + rule = new RuleMaxTotalSupply(ADMIN, address(token), CAP); + vm.prank(ADMIN); + engine.addRule(rule); + } + + /*////////////////////////////////////////////////////////////// + THE ERC-3643 VARIANT + //////////////////////////////////////////////////////////////*/ + + function testMintUpToTheCapSucceeds() public { + _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP); + + assertEq(token.totalSupply(), CAP, "a mint of exactly the cap must land"); + } + + function testMintBeyondTheCapIsRejected() public { + _useErc3643Rule(); + + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, CAP + 1); + + assertEq(token.totalSupply(), 0); + } + + function testIncrementalMintsShareTheSameCeiling() public { + _useErc3643Rule(); + + vm.startPrank(AGENT); + token.mint(INVESTOR, 600); + token.mint(INVESTOR2, 400); + vm.stopPrank(); + assertEq(token.totalSupply(), CAP); + + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, 1); + } + + /// @notice Burning frees headroom, because the cap is on supply rather than on cumulative issuance. + function testBurningFreesHeadroom() public { + _useErc3643Rule(); + + vm.startPrank(AGENT); + token.mint(INVESTOR, CAP); + token.burn(INVESTOR, 400); + assertEq(token.totalSupply(), CAP - 400); + + token.mint(INVESTOR2, 400); + vm.stopPrank(); + assertEq(token.totalSupply(), CAP); + } + + function testTransfersAreNeverGatedByTheCap() public { + _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP); + + vm.prank(INVESTOR); + token.transfer(INVESTOR2, 400); + assertEq(token.balanceOf(INVESTOR2), 400); + } + + function testRaisingTheCapRaisesTheCeiling() public { + RuleMaxTotalSupplyERC3643 rule = _useErc3643Rule(); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP); + + vm.prank(ADMIN); + rule.setMaxTotalSupply(CAP * 2); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP); + assertEq(token.totalSupply(), CAP * 2); + } + + /*////////////////////////////////////////////////////////////// + WHY THE STOCK RULE IS NOT USABLE HERE (NM-11) + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The stock, CMTAT-shaped rule double-counts the mint on a real ERC-3643 token. + * @dev `canTransfer` passes (0 + 1000 <= 1000), the token mints, then `created` finds + * `totalSupply() == 1000` and adds the amount again. A mint that exactly fills the cap + * reverts -- pre-flight and enforcement disagree inside one transaction. + */ + function testStockRuleRevertsAMintThatExactlyFillsTheCap() public { + _useStockRule(); + + vm.prank(AGENT); + vm.expectRevert(); + token.mint(INVESTOR, CAP); + + assertEq(token.totalSupply(), 0); + } + + /** + * @notice The stock rule halves the largest SINGLE mint it will accept. + * @dev Only the amount in flight is double-counted, so a series of small mints can still creep to + * the full cap. That makes the damage look intermittent rather than a clean halving. + */ + function testStockRuleHalvesTheLargestSingleMint() public { + _useStockRule(); + + vm.prank(AGENT); + vm.expectRevert(); + token.mint(INVESTOR, CAP / 2 + 1); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP / 2); + assertEq(token.totalSupply(), CAP / 2); + } + + /*////////////////////////////////////////////////////////////// + COMPOSITION WITH THE PROOF-OF-RESERVE VARIANT + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The documented pairing: PoR has no margin parameter, so a static ceiling is added by + * putting {RuleMaxTotalSupplyERC3643} in the same engine. + * @dev Whichever limit binds first stops the mint. The engine returns the FIRST non-zero code, so + * rule order decides whether a rejection is reported as `50` or `75`. + */ + function testComposesWithTheProofOfReserveVariant() public { + // Reserves are generous; the static cap is the binding constraint. + AggregatorV3Mock feed = new AggregatorV3Mock(0, int256(CAP * 10)); + RuleChainlinkPoRERC3643 por = + new RuleChainlinkPoRERC3643(ADMIN, address(token), 0, AggregatorV3Interface(address(feed)), 0); + RuleMaxTotalSupplyERC3643 cap = new RuleMaxTotalSupplyERC3643(ADMIN, address(token), CAP); + + vm.startPrank(ADMIN); + engine.addRule(por); + engine.addRule(cap); + vm.stopPrank(); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP); + + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, 1); + assertEq(engine.detectTransferRestriction(address(0), INVESTOR, 1), CODE_MAX_TOTAL_SUPPLY_EXCEEDED); + } + + /// @notice ...and with the reserves as the binding constraint instead, the PoR code is reported. + function testTheTighterOfTheTwoLimitsBinds() public { + AggregatorV3Mock feed = new AggregatorV3Mock(0, int256(CAP / 2)); + RuleChainlinkPoRERC3643 por = + new RuleChainlinkPoRERC3643(ADMIN, address(token), 0, AggregatorV3Interface(address(feed)), 0); + RuleMaxTotalSupplyERC3643 cap = new RuleMaxTotalSupplyERC3643(ADMIN, address(token), CAP); + + vm.startPrank(ADMIN); + engine.addRule(por); + engine.addRule(cap); + vm.stopPrank(); + + vm.prank(AGENT); + token.mint(INVESTOR, CAP / 2); + + vm.prank(AGENT); + vm.expectRevert(ComplianceNotFollowed.selector); + token.mint(INVESTOR, 1); + assertEq(token.totalSupply(), CAP / 2, "reserves bound before the static cap"); + } +} diff --git a/test/InterfaceId/AddressListInterfaceId.t.sol b/test/InterfaceId/AddressListInterfaceId.t.sol index 92ba907d..ed76aef0 100644 --- a/test/InterfaceId/AddressListInterfaceId.t.sol +++ b/test/InterfaceId/AddressListInterfaceId.t.sol @@ -8,6 +8,7 @@ import {RuleInterfaceId} from "RuleEngine/modules/library/RuleInterfaceId.sol"; import {AddressListInterfaceId} from "src/rules/interfaces/library/AddressListInterfaceId.sol"; import {IAddressListInterfaceIdHelper, IAddressListAllFunctions} from "src/mocks/IAddressListInterfaceIdHelper.sol"; +import {IAddressListBatchQuery, IAddressListPolarity} from "src/rules/interfaces/IAddressList.sol"; import {IIdentityRegistryContains} from "src/rules/interfaces/IIdentityRegistry.sol"; import {RuleWhitelist} from "src/rules/validation/deployment/RuleWhitelist.sol"; @@ -16,6 +17,7 @@ import {RuleBlacklist} from "src/rules/validation/deployment/RuleBlacklist.sol"; import {RuleBlacklistOwnable2Step} from "src/rules/validation/deployment/RuleBlacklistOwnable2Step.sol"; import {RuleSpenderWhitelist} from "src/rules/validation/deployment/RuleSpenderWhitelist.sol"; import {RuleSpenderWhitelistOwnable2Step} from "src/rules/validation/deployment/RuleSpenderWhitelistOwnable2Step.sol"; +import {RuleReceiverWhitelist} from "src/rules/validation/deployment/RuleReceiverWhitelist.sol"; import {RuleWhitelistWrapper} from "src/rules/validation/deployment/RuleWhitelistWrapper.sol"; import {RuleMaxTotalSupply} from "src/rules/validation/deployment/RuleMaxTotalSupply.sol"; import {TotalSupplyMock} from "src/mocks/TotalSupplyMock.sol"; @@ -54,17 +56,122 @@ contract AddressListInterfaceIdTest is Test, HelperContract { /** * @notice Guards the reason the flat-helper pattern is required: `type(IAddressList).interfaceId` - * omits `contains(address)`, inherited from `IIdentityRegistryContains`, so it must NOT - * be used for the ERC-165 check. + * omits every selector it inherits, so it must NOT be used for the ERC-165 check. + * @dev `IAddressList` now inherits from **two** parents — `IIdentityRegistryContains` for + * `contains(address)` and `IAddressListBatchQuery` for `areAddressesListed(address[])` — + * so the naive id omits both. That makes the point more sharply than before: the omission + * grows silently every time a selector is factored out into a parent interface, which is + * exactly why the flattened constant exists. */ function test_NaiveInterfaceIdIsWrongAndMustNotBeUsed() public view { bytes4 naive = helper.getIAddressListInterfaceId(); bytes4 full = AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID; assertTrue(naive != full, "naive id unexpectedly equals the full id"); - // The difference is exactly the inherited selector, `contains(address)`. - assertEq(naive ^ full, helper.getIIdentityRegistryContainsInterfaceId()); - assertEq(naive ^ full, IIdentityRegistryContains.contains.selector); + // The difference is exactly the two inherited selectors. + assertEq( + naive ^ full, + IIdentityRegistryContains.contains.selector ^ AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + ); + } + + /*////////////////////////////////////////////////////////////// + THE BATCH-QUERY SUB-INTERFACE (NM-18) + //////////////////////////////////////////////////////////////*/ + + /** + * @notice The sub-interface id is the selector of its single function. + * @dev `IAddressListBatchQuery` declares one function and inherits nothing, so unlike the full + * hierarchy it has no omitted-parent trap and the literal is safe to state. + */ + function test_BatchQueryInterfaceIdIsTheSingleSelector() public pure { + assertEq(AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID, bytes4(0x20e8e17a)); + assertEq( + AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID, + IAddressListBatchQuery.areAddressesListed.selector + ); + assertEq( + AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID, type(IAddressListBatchQuery).interfaceId + ); + } + + /*////////////////////////////////////////////////////////////// + THE POLARITY INTERFACE (NM-20) + //////////////////////////////////////////////////////////////*/ + + /// @notice The polarity id is the selector of its single function. + function test_PolarityInterfaceIdIsTheSingleSelector() public pure { + assertEq(AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID, bytes4(0xdc4efe10)); + assertEq(AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID, IAddressListPolarity.isAllowList.selector); + assertEq(AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID, type(IAddressListPolarity).interfaceId); + } + + /** + * @notice Polarity is a SEPARATE id from membership, which is the whole point. + * @dev If the two were the same interface, an allow-list and a deny-list would be + * indistinguishable again — a consumer needs to require both and then read the answer. + */ + function test_PolarityIsIndependentOfMembership() public pure { + assertTrue( + AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID + != AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID + ); + assertTrue( + AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID + != AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID + ); + } + + /// @notice Each rule declares the polarity it actually has, and advertises the interface. + function test_RulesDeclareTheirPolarityHonestly() public { + bytes4 polarity = AddressListInterfaceId.IADDRESS_LIST_POLARITY_INTERFACE_ID; + + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleWhitelist whitelist = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, false); + RuleBlacklist blacklist = new RuleBlacklist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + RuleSpenderWhitelist spender = new RuleSpenderWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + vm.stopPrank(); + + assertTrue(IERC165(address(whitelist)).supportsInterface(polarity), "whitelist advertises polarity"); + assertTrue(whitelist.isAllowList(), "whitelist is an allow-list"); + + assertTrue(IERC165(address(blacklist)).supportsInterface(polarity), "blacklist advertises polarity"); + assertFalse(blacklist.isAllowList(), "blacklist is a deny-list"); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + RuleReceiverWhitelist receiver = new RuleReceiverWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + assertTrue(IERC165(address(receiver)).supportsInterface(polarity), "receiver whitelist advertises polarity"); + assertTrue(receiver.isAllowList(), "receiver whitelist is an allow-list"); + + // Deliberate abstention: its set is spenders, not holders, so polarity alone would mislead. + assertFalse( + IERC165(address(spender)).supportsInterface(polarity), + "RuleSpenderWhitelist must NOT declare holder polarity" + ); + } + + /// @notice The sub-interface is a strict subset: the full id contains its selector. + function test_BatchQueryIsASubsetOfTheFullInterface() public view { + bytes4 full = AddressListInterfaceId.IADDRESS_LIST_INTERFACE_ID; + bytes4 sub = AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID; + assertTrue(full != sub, "the wrapper must not be able to confuse the two"); + // Removing the sub-interface selector from the flattened id leaves the other seven. + assertTrue((full ^ sub) != full, "the full id must actually include the sub-interface selector"); + } + + /// @notice Every rule usable as a wrapper child advertises the sub-interface, not just the full one. + function test_AddressListRulesAdvertiseTheBatchQuerySubInterface() public { + bytes4 sub = AddressListInterfaceId.IADDRESS_LIST_BATCH_QUERY_INTERFACE_ID; + + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleWhitelist whitelist = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, false); + RuleBlacklist blacklist = new RuleBlacklist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + RuleSpenderWhitelist spender = new RuleSpenderWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + vm.stopPrank(); + + assertTrue(IERC165(address(whitelist)).supportsInterface(sub), "RuleWhitelist"); + assertTrue(IERC165(address(blacklist)).supportsInterface(sub), "RuleBlacklist"); + assertTrue(IERC165(address(spender)).supportsInterface(sub), "RuleSpenderWhitelist"); } /*////////////////////////////////////////////////////////////// diff --git a/test/InterfaceId/ComplianceInterfaceId.t.sol b/test/InterfaceId/ComplianceInterfaceId.t.sol new file mode 100644 index 00000000..c8567dbb --- /dev/null +++ b/test/InterfaceId/ComplianceInterfaceId.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ComplianceInterfaceId} from "RuleEngine/modules/library/ComplianceInterfaceId.sol"; + +import {IERC3643ComplianceFull} from "src/mocks/IERC3643ComplianceFull.sol"; + +/** + * @title ComplianceInterfaceIdTest + * @notice Pins the ERC-3643 ICompliance interface ID the operation rules advertise. + * @dev RuleEngine v3.0.0-rc6 derives {ComplianceInterfaceId-ERC3643_COMPLIANCE_INTERFACE_ID} from + * its own interface hierarchy instead of hardcoding it, so a refactor upstream -- such as the + * rc6 split of the binding functions into `ITokenBinding` -- can now move the value silently. + * These assertions are the guard: the constant must stay equal to the flattened redeclaration + * in {IERC3643ComplianceFull} and to the literal wire value. + */ +contract ComplianceInterfaceIdTest is Test { + function testConstantMatchesFlattenedInterface() public pure { + assertEq( + ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID, + type(IERC3643ComplianceFull).interfaceId, + "upstream derivation diverged from the flattened ERC-3643 ICompliance surface" + ); + } + + function testConstantMatchesWireValue() public pure { + assertEq(ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID, bytes4(0x3144991c), "wire value moved"); + } +} diff --git a/test/RuleChainlinkPoR/RuleChainlinkPoRERC3643.t.sol b/test/RuleChainlinkPoR/RuleChainlinkPoRERC3643.t.sol new file mode 100644 index 00000000..fa850db3 --- /dev/null +++ b/test/RuleChainlinkPoR/RuleChainlinkPoRERC3643.t.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {HelperContract} from "../HelperContract.sol"; +import {AggregatorV3Mock} from "src/mocks/AggregatorV3Mock.sol"; +import {TotalSupplyMock} from "src/mocks/TotalSupplyMock.sol"; +import {AggregatorV3Interface} from "src/rules/interfaces/AggregatorV3Interface.sol"; +import {RuleChainlinkPoR} from "src/rules/validation/deployment/RuleChainlinkPoR.sol"; +import {RuleChainlinkPoRERC3643} from "src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol"; +import { + RuleChainlinkPoRERC3643Ownable2Step +} from "src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol"; + +/** + * @title RuleChainlinkPoRERC3643Unit + * @notice Unit coverage for the ERC-3643 Proof-of-Reserve variants, in the default profile. + * @dev The end-to-end proof runs against the genuine vendored token in + * `test/ERC3643Real/ERC3643RealTokenChainlinkPoR.t.sol` under `FOUNDRY_PROFILE=erc3643`, which + * `forge test` and `forge coverage` do not include. These tests exercise the same override with + * mocks so the variants are covered by the ordinary run too. + * + * The rule is notified as an ERC-3643 token would notify it: the supply is set to its POST-mint + * value first, then the write hook is called. + */ +contract RuleChainlinkPoRERC3643Unit is Test, HelperContract { + uint256 private constant RESERVES = 1000; + + TotalSupplyMock private token; + AggregatorV3Mock private feed; + + function setUp() public { + token = new TotalSupplyMock(); + feed = new AggregatorV3Mock(0, int256(RESERVES)); + } + + function _accessControlVariant() private returns (RuleChainlinkPoRERC3643) { + return + new RuleChainlinkPoRERC3643( + DEFAULT_ADMIN_ADDRESS, address(token), 0, AggregatorV3Interface(address(feed)), 0 + ); + } + + function _ownableVariant() private returns (RuleChainlinkPoRERC3643Ownable2Step) { + return new RuleChainlinkPoRERC3643Ownable2Step( + DEFAULT_ADMIN_ADDRESS, address(token), 0, AggregatorV3Interface(address(feed)), 0 + ); + } + + /*////////////////////////////////////////////////////////////// + ENFORCEMENT (POST-MINT) + //////////////////////////////////////////////////////////////*/ + + function testNotifyAcceptsAMintThatLandsExactlyOnTheReserves() public { + RuleChainlinkPoRERC3643 rule = _accessControlVariant(); + token.setTotalSupply(RESERVES); // the mint has already happened + rule.transferred(ZERO_ADDRESS, ADDRESS1, RESERVES); + } + + function testNotifyRejectsAMintThatLandsAboveTheReserves() public { + RuleChainlinkPoRERC3643 rule = _accessControlVariant(); + token.setTotalSupply(RESERVES + 1); + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + function testNotifyAcceptsTransfersAndBurnsWhateverTheReserves() public { + RuleChainlinkPoRERC3643 rule = _accessControlVariant(); + feed.setAnswer(0); + token.setTotalSupply(RESERVES); + + rule.transferred(ADDRESS1, ADDRESS2, 10); // transfer + rule.transferred(ADDRESS1, ZERO_ADDRESS, 10); // burn + rule.transferred(ADDRESS3, ADDRESS1, ADDRESS2, 10); // delegated transfer + } + + function testDelegatedNotifyIsRePhasedToo() public { + RuleChainlinkPoRERC3643 rule = _accessControlVariant(); + token.setTotalSupply(RESERVES); + rule.transferred(ADDRESS3, ZERO_ADDRESS, ADDRESS1, RESERVES); + + token.setTotalSupply(RESERVES + 1); + vm.expectRevert(); + rule.transferred(ADDRESS3, ZERO_ADDRESS, ADDRESS1, 1); + } + + /*////////////////////////////////////////////////////////////// + THE READ PATH IS NOT RE-PHASED + //////////////////////////////////////////////////////////////*/ + + function testReadPathStillProjectsThePendingAmount() public { + RuleChainlinkPoRERC3643 rule = _accessControlVariant(); + token.setTotalSupply(RESERVES); + + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), CODE_RESERVES_EXCEEDED); + assertFalse(rule.canTransfer(ZERO_ADDRESS, ADDRESS1, 1)); + + token.setTotalSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, RESERVES), TRANSFER_OK); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, RESERVES + 1), CODE_RESERVES_EXCEEDED); + } + + /// @notice Pre-flight and enforcement must agree on the same mint, as the ERC-3643 token calls both. + function testPreFlightAndEnforcementAgree() public { + RuleChainlinkPoRERC3643 rule = _accessControlVariant(); + + // Pre-flight, before the mint: supply 0, asking for the full reserves. + token.setTotalSupply(0); + assertTrue(rule.canTransfer(ZERO_ADDRESS, ADDRESS1, RESERVES)); + + // Enforcement, after the mint: supply is now RESERVES. + token.setTotalSupply(RESERVES); + rule.transferred(ZERO_ADDRESS, ADDRESS1, RESERVES); + } + + /*////////////////////////////////////////////////////////////// + CONTRAST WITH THE STOCK RULE + //////////////////////////////////////////////////////////////*/ + + function testStockRuleDoubleCountsWhereTheVariantDoesNot() public { + RuleChainlinkPoR stock = + new RuleChainlinkPoR(DEFAULT_ADMIN_ADDRESS, address(token), 0, AggregatorV3Interface(address(feed)), 0); + RuleChainlinkPoRERC3643 variant = _accessControlVariant(); + + token.setTotalSupply(RESERVES); + + vm.expectRevert(); + stock.transferred(ZERO_ADDRESS, ADDRESS1, RESERVES); + + variant.transferred(ZERO_ADDRESS, ADDRESS1, RESERVES); + } + + /*////////////////////////////////////////////////////////////// + OWNABLE2STEP VARIANT + //////////////////////////////////////////////////////////////*/ + + function testOwnableVariantEnforcesIdentically() public { + RuleChainlinkPoRERC3643Ownable2Step rule = _ownableVariant(); + + token.setTotalSupply(RESERVES); + rule.transferred(ZERO_ADDRESS, ADDRESS1, RESERVES); + + token.setTotalSupply(RESERVES + 1); + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + function testOwnableVariantKeepsItsOwnerGatedConfiguration() public { + RuleChainlinkPoRERC3643Ownable2Step rule = _ownableVariant(); + + vm.prank(ATTACKER); + vm.expectRevert(); + rule.setMaxStalenessSeconds(1 days); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.setMaxStalenessSeconds(1 days); + assertEq(rule.maxStalenessSeconds(), 1 days); + } + + function testBothVariantsReportTheSameBackedSupply() public { + (uint8 codeA, uint256 backedA) = _accessControlVariant().maxBackedSupply(); + (uint8 codeB, uint256 backedB) = _ownableVariant().maxBackedSupply(); + assertEq(codeA, 0); + assertEq(codeA, codeB); + assertEq(backedA, RESERVES); + assertEq(backedA, backedB); + } +} diff --git a/test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol b/test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol index e7e2d386..d6f35a24 100644 --- a/test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol +++ b/test/RuleChainlinkPoR/RuleChainlinkPoRUnit.t.sol @@ -296,6 +296,58 @@ contract RuleChainlinkPoRUnit is Test, HelperContract { assertEq(resUint8, CODE_RESERVES_FEED_UNAVAILABLE); } + /*////////////////////////////////////////////////////////////// + FUTURE-DATED ROUND (NM-10 REGRESSION) + //////////////////////////////////////////////////////////////*/ + + /** + * @notice A round stamped in the future is a malformed answer, not a fresh one. + * @dev THE REGRESSION: the staleness comparison was guarded by `block.timestamp > updatedAt` to keep + * the subtraction from underflowing, which silently accepted ANY future timestamp -- a feed + * frozen on an old reserve answer could keep authorising mints until that timestamp elapsed. + */ + function testDetectRestriction_FutureDatedRoundBlocksMint() public { + feed.setUpdatedAt(block.timestamp + 1); + token.setTotalSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), CODE_RESERVES_ANSWER_INVALID); + assertFalse(rule.canTransfer(ZERO_ADDRESS, ADDRESS1, 1)); + } + + function testDetectRestriction_FutureDatedRoundIsRejectedEvenWithStalenessDisabled() public { + // `maxStalenessSeconds == 0` disables FRESHNESS checking. It must not also disable the + // malformed-answer check, or an operator who opts out of staleness opts into forged timestamps. + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.setMaxStalenessSeconds(0); + + feed.setUpdatedAt(block.timestamp + 3650 days); + token.setTotalSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), CODE_RESERVES_ANSWER_INVALID); + } + + function testDetectRestriction_RoundAtExactlyTheCurrentBlockIsAccepted() public { + // The boundary: `updatedAt == block.timestamp` is the normal case for a just-published round. + feed.setUpdatedAt(block.timestamp); + token.setTotalSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), TRANSFER_OK); + } + + function testMaxBackedSupply_ReportsTheFutureDatedRound() public { + // The preview accessor must agree with what a mint would return, and must not revert. + feed.setUpdatedAt(block.timestamp + 1); + (uint8 code, uint256 backed) = rule.maxBackedSupply(); + assertEq(code, CODE_RESERVES_ANSWER_INVALID); + assertEq(backed, 0); + } + + function testTransferred_FutureDatedRoundRevertsTheMint() public { + // Enforcement, not just the view: the write hook must reject the mint. + feed.setUpdatedAt(block.timestamp + 1); + token.setTotalSupply(0); + vm.prank(address(token)); + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + function testDetectRestriction_ZeroReserveBlocksAnyMint() public { feed.setAnswer(0); token.setTotalSupply(0); diff --git a/test/RuleConditionalTransferLight/Ownable/ConditionalTransferOwnable2StepBindingAuthorization.t.sol b/test/RuleConditionalTransferLight/Ownable/ConditionalTransferOwnable2StepBindingAuthorization.t.sol index 4e22dc05..a3c5f144 100644 --- a/test/RuleConditionalTransferLight/Ownable/ConditionalTransferOwnable2StepBindingAuthorization.t.sol +++ b/test/RuleConditionalTransferLight/Ownable/ConditionalTransferOwnable2StepBindingAuthorization.t.sol @@ -15,12 +15,12 @@ import { * @dev These hooks had no coverage before: the only test naming * `RuleConditionalTransferLightMultiTokenOwnable2Step` was an ERC-165 support check, which never * reaches an access-control path. Three concrete overrides were therefore unexercised — - * `_authorizeComplianceBindingChange` on the single-token variant, and `_onlyComplianceManager` + * `_authorizeTokenBindingChange` on the single-token variant, and `_onlyComplianceManager` * plus `_authorizeTransferApproval` on the multi-token one. * * Note which entrypoint reaches which hook. `RuleConditionalTransferLightBase` overrides - * `bindToken` with its own `onlyComplianceManager` modifier, so on the single-token rule the - * only route to `_authorizeComplianceBindingChange` is the inherited `unbindToken`. + * `bindToken` with its own `onlyTokenBindingManager` modifier, so on the single-token rule the + * only route to `_authorizeTokenBindingChange` is the inherited `unbindToken`. */ contract ConditionalTransferOwnable2StepBindingAuthorizationTest is Test { address constant OWNER = address(0xA11CE); @@ -39,7 +39,7 @@ contract ConditionalTransferOwnable2StepBindingAuthorizationTest is Test { } /*////////////////////////////////////////////////////////////// - SINGLE TOKEN -- _authorizeComplianceBindingChange + SINGLE TOKEN -- _authorizeTokenBindingChange //////////////////////////////////////////////////////////////*/ function testSingleUnbindTokenRejectsNonOwner() public { diff --git a/test/RuleConditionalTransferLight/RuleConditionalTransferLightApproveAndTransfer.t.sol b/test/RuleConditionalTransferLight/RuleConditionalTransferLightApproveAndTransfer.t.sol index 21e296dc..9da0ff96 100644 --- a/test/RuleConditionalTransferLight/RuleConditionalTransferLightApproveAndTransfer.t.sol +++ b/test/RuleConditionalTransferLight/RuleConditionalTransferLightApproveAndTransfer.t.sol @@ -36,6 +36,87 @@ contract RuleConditionalTransferLightApproveAndTransfer is Test, HelperContract assertEq(rule.approvedCount(ADDRESS1, ADDRESS2, 10), 0); } + /*////////////////////////////////////////////////////////////// + NM-17: THE APPROVAL MUST BE CONSUMED + //////////////////////////////////////////////////////////////*/ + + /** + * @notice A token that never calls back leaves the helper's approval unconsumed, and the helper + * must reject that rather than complete. + * @dev THE REGRESSION. The helper inverts CEI on purpose: it records the approval BEFORE + * `safeTransferFrom` so the token's compliance callback can consume it. Nothing used to + * verify the callback happened. A plain ERC-20 bound with `bindToken`, or a RuleEngine never + * bound or since unbound, therefore completed the transfer and left the approval standing — + * indistinguishable from an operator-created one, and enough to authorise a later, + * never-approved transfer of exactly `(from, to, value)`. + */ + function testRevertsWhenTheTokenDoesNotCallBack() public { + MockERC20WithTransferContext silentToken = new MockERC20WithTransferContext("Silent", "SIL"); + // Deliberately NOT `setRule`: this token moves value and tells nobody. + silentToken.mint(ADDRESS1, 100); + + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleConditionalTransferLight silentRule = new RuleConditionalTransferLight(DEFAULT_ADMIN_ADDRESS); + silentRule.bindToken(address(silentToken)); + vm.stopPrank(); + + vm.prank(ADDRESS1); + silentToken.approve(address(silentRule), 10); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + vm.expectRevert( + abi.encodeWithSelector( + RuleConditionalTransferLight_ApprovalNotConsumed.selector, + address(silentToken), + ADDRESS1, + ADDRESS2, + uint256(10) + ) + ); + silentRule.approveAndTransferIfAllowed(ADDRESS1, ADDRESS2, 10); + + // The whole call reverted, so no residual approval and no value moved. + assertEq(silentRule.approvedCount(ADDRESS1, ADDRESS2, 10), 0, "no approval may be left behind"); + assertEq(silentToken.balanceOf(ADDRESS1), 100, "the transfer was rolled back"); + assertEq(silentToken.balanceOf(ADDRESS2), 0); + } + + /** + * @notice The post-condition compares against the count BEFORE the helper ran, not against zero. + * @dev An operator may legitimately hold outstanding approvals for the same tuple. The helper adds + * one, the callback consumes one, and the pre-existing approvals must survive untouched. + */ + function testPreExistingApprovalsSurviveTheHelper() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + rule.approveTransfer(ADDRESS1, ADDRESS2, 10); + rule.approveTransfer(ADDRESS1, ADDRESS2, 10); + vm.stopPrank(); + assertEq(rule.approvedCount(ADDRESS1, ADDRESS2, 10), 2); + + vm.prank(ADDRESS1); + token.approve(address(rule), 10); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.approveAndTransferIfAllowed(ADDRESS1, ADDRESS2, 10); + + assertEq(rule.approvedCount(ADDRESS1, ADDRESS2, 10), 2, "the operator's own approvals are untouched"); + assertEq(token.balanceOf(ADDRESS2), 10); + } + + /// @notice The normal direct-binding flow still works: the callback consumes exactly one approval. + function testDirectBindingFlowStillConsumesExactlyOne() public { + vm.prank(ADDRESS1); + token.approve(address(rule), 20); + + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + rule.approveAndTransferIfAllowed(ADDRESS1, ADDRESS2, 10); + rule.approveAndTransferIfAllowed(ADDRESS1, ADDRESS2, 10); + vm.stopPrank(); + + assertEq(rule.approvedCount(ADDRESS1, ADDRESS2, 10), 0); + assertEq(token.balanceOf(ADDRESS2), 20); + } + function testApproveAndTransferIfAllowedRevertsWhenNoTokenBound() public { RuleConditionalTransferLight freshRule = new RuleConditionalTransferLight(DEFAULT_ADMIN_ADDRESS); vm.expectRevert(RuleConditionalTransferLight_TokenNotBound.selector); diff --git a/test/RuleConditionalTransferLightMultiToken/MultiTokenGuardReverts.t.sol b/test/RuleConditionalTransferLightMultiToken/MultiTokenGuardReverts.t.sol new file mode 100644 index 00000000..d671305f --- /dev/null +++ b/test/RuleConditionalTransferLightMultiToken/MultiTokenGuardReverts.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {HelperContract} from "../HelperContract.sol"; +import {MockERC20WithTransferContext} from "src/mocks/MockERC20WithTransferContext.sol"; +import {RuleConditionalTransferLightMultiToken} from "src/rules/operation/RuleConditionalTransferLightMultiToken.sol"; + +/** + * @title MultiTokenGuardReverts + * @notice The reject side of every guard in {RuleConditionalTransferLightMultiTokenBase}. + * @dev These four branches were the only uncovered ones in `src/` — each guard's accept path was + * exercised, its `require` never taken. A rule whose whole purpose is to refuse transfers needs + * its refusals asserted, not just its permissions: a guard that has never been observed to + * reject is a guard nobody has tested. + */ +contract MultiTokenGuardReverts is Test, HelperContract { + /// @dev Re-declared locally: `HelperContract` cannot inherit the multi-token invariant storage + /// alongside the single-token one (`OPERATOR_ROLE` and the code constants clash). + error RuleConditionalTransferLightMultiToken_InvalidToken(); + error RuleConditionalTransferLightMultiToken_InsufficientAllowance( + address token, address from, uint256 allowance, uint256 value + ); + error RuleConditionalTransferLightMultiToken_TransferExecutorUnauthorized(address account); + + RuleConditionalTransferLightMultiToken private rule; + MockERC20WithTransferContext private boundToken; + MockERC20WithTransferContext private strangerToken; + + function setUp() public { + boundToken = new MockERC20WithTransferContext("Bound", "BND"); + strangerToken = new MockERC20WithTransferContext("Stranger", "STR"); + + rule = new RuleConditionalTransferLightMultiToken(DEFAULT_ADMIN_ADDRESS); + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.bindToken(address(boundToken)); + boundToken.setRule(address(rule)); + + boundToken.mint(ADDRESS1, 100); + strangerToken.mint(ADDRESS1, 100); + } + + /// @notice L137: `approveAndTransferIfAllowed` refuses a token that was never bound. + function testApproveAndTransferRejectsAnUnboundToken() public { + vm.prank(ADDRESS1); + strangerToken.approve(address(rule), 10); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + vm.expectRevert(RuleConditionalTransferLightMultiToken_InvalidToken.selector); + rule.approveAndTransferIfAllowed(address(strangerToken), ADDRESS1, ADDRESS2, 10); + } + + /// @notice L143: `approveAndTransferIfAllowed` refuses when the holder's allowance is short. + function testApproveAndTransferRejectsAnInsufficientAllowance() public { + vm.prank(ADDRESS1); + boundToken.approve(address(rule), 4); // less than the 10 requested + + vm.prank(DEFAULT_ADMIN_ADDRESS); + vm.expectRevert( + abi.encodeWithSelector( + RuleConditionalTransferLightMultiToken_InsufficientAllowance.selector, + address(boundToken), + ADDRESS1, + uint256(4), + uint256(10) + ) + ); + rule.approveAndTransferIfAllowed(address(boundToken), ADDRESS1, ADDRESS2, 10); + + // The rejection is total: no approval was recorded and no value moved. + assertEq(rule.approvedCount(address(boundToken), ADDRESS1, ADDRESS2, 10), 0); + assertEq(boundToken.balanceOf(ADDRESS2), 0); + } + + /// @notice L371: `cancelTransferApproval` refuses a token that was never bound. + function testCancelTransferApprovalRejectsAnUnboundToken() public { + vm.prank(DEFAULT_ADMIN_ADDRESS); + vm.expectRevert(RuleConditionalTransferLightMultiToken_InvalidToken.selector); + rule.cancelTransferApproval(address(strangerToken), ADDRESS1, ADDRESS2, 10); + } + + /// @notice L440: the execution hook refuses a caller that is not a bound token. + /// @dev This rule is direct-binding only, so the executor check *is* the token check: approval + /// consumption is keyed on `msg.sender`. An unbound caller must never consume one. + function testTransferredRejectsACallerThatIsNotABoundToken() public { + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.approveTransfer(address(boundToken), ADDRESS1, ADDRESS2, 10); + + vm.prank(ATTACKER); + vm.expectRevert( + abi.encodeWithSelector( + RuleConditionalTransferLightMultiToken_TransferExecutorUnauthorized.selector, ATTACKER + ) + ); + rule.transferred(ADDRESS1, ADDRESS2, 10); + + // The approval survives the rejected attempt. + assertEq(rule.approvedCount(address(boundToken), ADDRESS1, ADDRESS2, 10), 1); + } +} diff --git a/test/RuleConditionalTransferLightMultiToken/RuleConditionalTransferLightMultiToken.t.sol b/test/RuleConditionalTransferLightMultiToken/RuleConditionalTransferLightMultiToken.t.sol index 9cbd9505..82120616 100644 --- a/test/RuleConditionalTransferLightMultiToken/RuleConditionalTransferLightMultiToken.t.sol +++ b/test/RuleConditionalTransferLightMultiToken/RuleConditionalTransferLightMultiToken.t.sol @@ -7,6 +7,13 @@ import {RuleConditionalTransferLightMultiToken} from "src/rules/operation/RuleCo import {MockERC20WithTransferContext} from "src/mocks/MockERC20WithTransferContext.sol"; contract RuleConditionalTransferLightMultiTokenTest is Test, HelperContract { + /// @dev Re-declared locally: `HelperContract` cannot inherit the multi-token invariant storage + /// alongside the single-token one (`OPERATOR_ROLE` and the code constants clash), which is the + /// same reason `MultiTokenSurface.t.sol` re-declares its errors. + error RuleConditionalTransferLightMultiToken_ApprovalNotConsumed( + address token, address from, address to, uint256 value + ); + RuleConditionalTransferLightMultiToken private rule; MockERC20WithTransferContext private tokenA; MockERC20WithTransferContext private tokenB; @@ -29,6 +36,53 @@ contract RuleConditionalTransferLightMultiTokenTest is Test, HelperContract { tokenB.mint(ADDRESS1, 100); } + /** + * @notice NM-17: a bound token that never calls back leaves the helper's approval unconsumed. + * @dev Same inverted-CEI shape as the single-token rule: `approveAndTransferIfAllowed` records the + * approval before `safeTransferFrom` so the compliance callback can consume it, and nothing + * used to check the callback happened. Here the token is bound but has no rule set, so it + * moves value silently — the helper must now reject rather than complete and leave a + * spendable approval for `(tokenC, from, to, value)`. + */ + function testApproveAndTransferRevertsWhenTheTokenDoesNotCallBack() public { + MockERC20WithTransferContext silentToken = new MockERC20WithTransferContext("Silent", "SIL"); + // Bound to the rule, but deliberately NOT `setRule`: it tells nobody. + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.bindToken(address(silentToken)); + silentToken.mint(ADDRESS1, 100); + + vm.prank(ADDRESS1); + silentToken.approve(address(rule), 10); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + vm.expectRevert( + abi.encodeWithSelector( + RuleConditionalTransferLightMultiToken_ApprovalNotConsumed.selector, + address(silentToken), + ADDRESS1, + ADDRESS2, + uint256(10) + ) + ); + rule.approveAndTransferIfAllowed(address(silentToken), ADDRESS1, ADDRESS2, 10); + + assertEq(rule.approvedCount(address(silentToken), ADDRESS1, ADDRESS2, 10), 0, "no residual approval"); + assertEq(silentToken.balanceOf(ADDRESS1), 100, "the transfer was rolled back"); + } + + /// @notice NM-17: a token that does call back is unaffected, and the count is per-token. + function testApproveAndTransferStillWorksAndIsPerToken() public { + vm.prank(ADDRESS1); + tokenA.approve(address(rule), 10); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.approveAndTransferIfAllowed(address(tokenA), ADDRESS1, ADDRESS2, 10); + + assertEq(tokenA.balanceOf(ADDRESS2), 10); + assertEq(rule.approvedCount(address(tokenA), ADDRESS1, ADDRESS2, 10), 0); + assertEq(rule.approvedCount(address(tokenB), ADDRESS1, ADDRESS2, 10), 0, "token B untouched"); + } + function testApprovalForTokenADoesNotAuthorizeTokenB() public { vm.prank(DEFAULT_ADMIN_ADDRESS); rule.approveTransfer(address(tokenA), ADDRESS1, ADDRESS2, 10); diff --git a/test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol b/test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol new file mode 100644 index 00000000..c4bdbbe4 --- /dev/null +++ b/test/RuleIdentityRegistry/RuleIdentityRegistryDelegation.t.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {HelperContract} from "../HelperContract.sol"; +import {IdentityRegistryExtraCheckHarness} from "src/mocks/harness/IdentityRegistryDelegationHarness.sol"; +import {IdentityRegistryMock} from "src/mocks/IdentityRegistryMock.sol"; + +/** + * @title RuleIdentityRegistryDelegation + * @notice The `transferFrom` path must always consult the direct restriction check, whether or not a + * registry is configured and whether or not the transfer is a burn (Nethermind AuditAgent + * NM-3; the mirror of `CLAUDE_ANALYSIS.md` F-2 on {RuleSanctionsListBase}). + * @dev The subclass under test adds a registry-independent check. Before the fix, + * `_detectTransferRestrictionFrom` returned `TRANSFER_OK` outright when the registry was unset + * or `to == address(0)`, so the subclass's check applied to `transfer` but not to + * `transferFrom`. `testExtraCheckAppliesToTransferFromWithNoRegistry` and + * `testExtraCheckAppliesToBurnFrom` fail against that implementation and are the reason the + * restructure exists. + */ +contract RuleIdentityRegistryDelegation is Test, HelperContract { + address private constant BLOCKED = address(0xB10C); + address private constant UNVERIFIED = address(98); + + IdentityRegistryMock private registry; + + function setUp() public { + registry = new IdentityRegistryMock(); + registry.setVerified(ADDRESS1, true); + registry.setVerified(ADDRESS2, true); + registry.setVerified(ADDRESS3, true); + registry.setVerified(BLOCKED, true); + } + + function _withoutRegistry() internal returns (IdentityRegistryExtraCheckHarness) { + return new IdentityRegistryExtraCheckHarness(DEFAULT_ADMIN_ADDRESS, ZERO_ADDRESS, false, false, BLOCKED); + } + + function _withRegistry(bool checkSpender_) internal returns (IdentityRegistryExtraCheckHarness) { + return + new IdentityRegistryExtraCheckHarness( + DEFAULT_ADMIN_ADDRESS, address(registry), false, checkSpender_, BLOCKED + ); + } + + /*////////////////////////////////////////////////////////////// + No registry configured + //////////////////////////////////////////////////////////////*/ + + function testExtraCheckAppliesToTransferWithNoRegistry() public { + // This direction always worked: the direct path calls the hook unconditionally. + IdentityRegistryExtraCheckHarness rule = _withoutRegistry(); + assertEq(rule.detectTransferRestriction(BLOCKED, ADDRESS2, 10), rule.CODE_EXTRA_BLOCKED()); + } + + function testExtraCheckAppliesToTransferFromWithNoRegistry() public { + // THE REGRESSION: with the early return in place this returned TRANSFER_OK, so `transfer` + // and `transferFrom` disagreed about the same pair of addresses. + IdentityRegistryExtraCheckHarness rule = _withoutRegistry(); + assertEq( + rule.detectTransferRestrictionFrom(ADDRESS3, BLOCKED, ADDRESS2, 10), + rule.CODE_EXTRA_BLOCKED(), + "transferFrom must reach the same hook as transfer" + ); + assertFalse(rule.canTransferFrom(ADDRESS3, BLOCKED, ADDRESS2, 10)); + } + + function testTheTwoEntrypointsAgreeWithNoRegistry() public { + IdentityRegistryExtraCheckHarness rule = _withoutRegistry(); + assertEq( + rule.detectTransferRestriction(ADDRESS1, BLOCKED, 10), + rule.detectTransferRestrictionFrom(ADDRESS3, ADDRESS1, BLOCKED, 10), + "the receiver leg must be screened identically on both paths" + ); + // An unrelated pair is still unrestricted; the rule is not simply rejecting everything. + assertEq(rule.detectTransferRestrictionFrom(ADDRESS3, ADDRESS1, ADDRESS2, 10), TRANSFER_OK); + } + + /*////////////////////////////////////////////////////////////// + Burn (to == address(0)) + //////////////////////////////////////////////////////////////*/ + + function testExtraCheckAppliesToBurnFrom() public { + // THE SECOND REGRESSION: the burn early return skipped the delegation too, so a subclass + // check on a burning `from` applied to `burn` but not to `burnFrom`. + IdentityRegistryExtraCheckHarness rule = _withRegistry(false); + assertEq(rule.detectTransferRestriction(BLOCKED, ZERO_ADDRESS, 10), rule.CODE_EXTRA_BLOCKED()); + assertEq( + rule.detectTransferRestrictionFrom(ADDRESS3, BLOCKED, ZERO_ADDRESS, 10), + rule.CODE_EXTRA_BLOCKED(), + "burnFrom must reach the same hook as burn" + ); + } + + function testBurnStaysExemptFromTheSpenderCheck() public { + // Delegating the burn must NOT expose it to the opt-in spender check: ERC-3643 states that + // burn bypasses all eligibility checks. + IdentityRegistryExtraCheckHarness rule = _withRegistry(true); + assertEq(rule.detectTransferRestrictionFrom(UNVERIFIED, ADDRESS1, ZERO_ADDRESS, 10), TRANSFER_OK); + } + + /*////////////////////////////////////////////////////////////// + Registry configured + //////////////////////////////////////////////////////////////*/ + + function testExtraCheckStillAppliesWithARegistry() public { + IdentityRegistryExtraCheckHarness rule = _withRegistry(false); + assertEq(rule.detectTransferRestriction(BLOCKED, ADDRESS2, 10), rule.CODE_EXTRA_BLOCKED()); + assertEq(rule.detectTransferRestrictionFrom(ADDRESS3, BLOCKED, ADDRESS2, 10), rule.CODE_EXTRA_BLOCKED()); + } + + function testTheSpenderCheckStillTakesPriority() public { + // The registry-driven spender check must still short-circuit ahead of the delegated hook. + IdentityRegistryExtraCheckHarness rule = _withRegistry(true); + assertEq( + rule.detectTransferRestrictionFrom(UNVERIFIED, BLOCKED, ADDRESS2, 10), CODE_ADDRESS_SPENDER_NOT_VERIFIED + ); + } + + function testBaseScreeningIsUnchanged() public { + IdentityRegistryExtraCheckHarness rule = _withRegistry(false); + // ERC-3643: only the receiver must be verified. + assertEq(rule.detectTransferRestriction(ADDRESS1, UNVERIFIED, 10), CODE_ADDRESS_TO_NOT_VERIFIED); + assertEq(rule.detectTransferRestriction(UNVERIFIED, ADDRESS2, 10), TRANSFER_OK); + assertEq(rule.detectTransferRestrictionFrom(ADDRESS3, ADDRESS1, ADDRESS2, 10), TRANSFER_OK); + // Mint is screened on the receiver only; an unverified minter is not blocked. + assertEq(rule.detectTransferRestrictionFrom(UNVERIFIED, ZERO_ADDRESS, ADDRESS2, 10), TRANSFER_OK); + } +} diff --git a/test/RuleMaxTotalSupply/RuleMaxTotalSupplyERC3643.t.sol b/test/RuleMaxTotalSupply/RuleMaxTotalSupplyERC3643.t.sol new file mode 100644 index 00000000..eb63da7f --- /dev/null +++ b/test/RuleMaxTotalSupply/RuleMaxTotalSupplyERC3643.t.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {HelperContract} from "../HelperContract.sol"; +import {TotalSupplyMock} from "src/mocks/TotalSupplyMock.sol"; +import {RuleMaxTotalSupply} from "src/rules/validation/deployment/RuleMaxTotalSupply.sol"; +import {RuleMaxTotalSupplyERC3643} from "src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol"; +import { + RuleMaxTotalSupplyERC3643Ownable2Step +} from "src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol"; + +/** + * @title RuleMaxTotalSupplyERC3643Unit + * @notice Unit coverage for the ERC-3643 supply-cap variants, in the default profile. + * @dev The end-to-end proof runs against the genuine vendored token in + * `test/ERC3643Real/ERC3643RealTokenMaxTotalSupply.t.sol` under `FOUNDRY_PROFILE=erc3643`, which + * `forge test` and `forge coverage` do not include. These tests exercise the same override with + * a mock so the variants are covered by the ordinary run too. + * + * The rule is notified as an ERC-3643 token would notify it: the supply is set to its POST-mint + * value first, then the write hook is called. + */ +contract RuleMaxTotalSupplyERC3643Unit is Test, HelperContract { + uint256 private constant CAP = 1000; + + TotalSupplyMock private token; + + function setUp() public { + token = new TotalSupplyMock(); + } + + function _accessControlVariant() private returns (RuleMaxTotalSupplyERC3643) { + return new RuleMaxTotalSupplyERC3643(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + } + + function _ownableVariant() private returns (RuleMaxTotalSupplyERC3643Ownable2Step) { + return new RuleMaxTotalSupplyERC3643Ownable2Step(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + } + + /*////////////////////////////////////////////////////////////// + ENFORCEMENT (POST-MINT) + //////////////////////////////////////////////////////////////*/ + + function testNotifyAcceptsAMintThatLandsExactlyOnTheCap() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + token.setTotalSupply(CAP); // the mint has already happened + rule.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + } + + function testNotifyRejectsAMintThatLandsAboveTheCap() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + token.setTotalSupply(CAP + 1); + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + function testNotifyAcceptsTransfersAndBurnsRegardlessOfSupply() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + token.setTotalSupply(CAP * 10); // already far over the cap + + rule.transferred(ADDRESS1, ADDRESS2, 10); // transfer + rule.transferred(ADDRESS1, ZERO_ADDRESS, 10); // burn + rule.transferred(ADDRESS3, ADDRESS1, ADDRESS2, 10); // delegated transfer + } + + function testDelegatedNotifyIsRePhasedToo() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + token.setTotalSupply(CAP); + rule.transferred(ADDRESS3, ZERO_ADDRESS, ADDRESS1, CAP); + + token.setTotalSupply(CAP + 1); + vm.expectRevert(); + rule.transferred(ADDRESS3, ZERO_ADDRESS, ADDRESS1, 1); + } + + function testLoweringTheCapBelowTheSupplyBlocksFurtherMints() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + token.setTotalSupply(CAP); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.setMaxTotalSupply(CAP / 2); + + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + /*////////////////////////////////////////////////////////////// + THE READ PATH IS NOT RE-PHASED + //////////////////////////////////////////////////////////////*/ + + function testReadPathStillProjectsThePendingAmount() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + token.setTotalSupply(CAP); + + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, 1), CODE_MAX_TOTAL_SUPPLY_EXCEEDED); + assertFalse(rule.canTransfer(ZERO_ADDRESS, ADDRESS1, 1)); + + token.setTotalSupply(0); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, CAP), TRANSFER_OK); + assertEq(rule.detectTransferRestriction(ZERO_ADDRESS, ADDRESS1, CAP + 1), CODE_MAX_TOTAL_SUPPLY_EXCEEDED); + } + + /// @notice Pre-flight and enforcement must agree on the same mint, as the ERC-3643 token calls both. + function testPreFlightAndEnforcementAgree() public { + RuleMaxTotalSupplyERC3643 rule = _accessControlVariant(); + + token.setTotalSupply(0); + assertTrue(rule.canTransfer(ZERO_ADDRESS, ADDRESS1, CAP)); + + token.setTotalSupply(CAP); + rule.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + } + + /*////////////////////////////////////////////////////////////// + CONTRAST WITH THE STOCK RULE + //////////////////////////////////////////////////////////////*/ + + function testStockRuleDoubleCountsWhereTheVariantDoesNot() public { + RuleMaxTotalSupply stock = new RuleMaxTotalSupply(DEFAULT_ADMIN_ADDRESS, address(token), CAP); + RuleMaxTotalSupplyERC3643 variant = _accessControlVariant(); + + token.setTotalSupply(CAP); + + vm.expectRevert(); + stock.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + + variant.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + } + + /*////////////////////////////////////////////////////////////// + OWNABLE2STEP VARIANT + //////////////////////////////////////////////////////////////*/ + + function testOwnableVariantEnforcesIdentically() public { + RuleMaxTotalSupplyERC3643Ownable2Step rule = _ownableVariant(); + + token.setTotalSupply(CAP); + rule.transferred(ZERO_ADDRESS, ADDRESS1, CAP); + + token.setTotalSupply(CAP + 1); + vm.expectRevert(); + rule.transferred(ZERO_ADDRESS, ADDRESS1, 1); + } + + function testOwnableVariantKeepsItsOwnerGatedConfiguration() public { + RuleMaxTotalSupplyERC3643Ownable2Step rule = _ownableVariant(); + + vm.prank(ATTACKER); + vm.expectRevert(); + rule.setMaxTotalSupply(1); + + vm.prank(DEFAULT_ADMIN_ADDRESS); + rule.setMaxTotalSupply(1); + assertEq(rule.maxTotalSupply(), 1); + } +} diff --git a/test/ThreatModel/ThreatModelTests.t.sol b/test/ThreatModel/ThreatModelTests.t.sol index c618e877..ec609f45 100644 --- a/test/ThreatModel/ThreatModelTests.t.sol +++ b/test/ThreatModel/ThreatModelTests.t.sol @@ -12,6 +12,8 @@ import {IdentityRegistryMock} from "src/mocks/IdentityRegistryMock.sol"; import {TotalSupplyMock} from "src/mocks/TotalSupplyMock.sol"; import {MockERC20WithTransferContext} from "src/mocks/MockERC20WithTransferContext.sol"; +import {RuleSpenderWhitelist} from "src/rules/validation/deployment/RuleSpenderWhitelist.sol"; +import {RuleBlacklist} from "src/rules/validation/deployment/RuleBlacklist.sol"; import {RuleWhitelist} from "src/rules/validation/deployment/RuleWhitelist.sol"; import {RuleWhitelistWrapper} from "src/rules/validation/deployment/RuleWhitelistWrapper.sol"; import {RuleMaxTotalSupply} from "src/rules/validation/deployment/RuleMaxTotalSupply.sol"; @@ -618,13 +620,20 @@ contract ThreatModelTests is Test, HelperContract { } /** - * @notice WW-2: unlike `RuleEngineBase`, the wrapper does not ERC-165-check that a child - * rule implements `IAddressList`. Adding a conformant `IRule` that is not an - * address list bricks every transfer check that has to scan past the first child. - * Note the early-exit in `_detectTransferRestrictionForTargets`: a pair already - * resolved by an earlier child still succeeds, so the breakage is input-dependent. + * @notice WW-2: **FIXED.** The wrapper now ERC-165-checks its children, so a conformant `IRule` + * that is not an address list is rejected at `addRule` instead of being accepted and + * bricking later transfer checks. + * @dev This test formerly asserted the broken behaviour and was named `..._CurrentBehaviour`: + * the wrapper accepted `RuleMaxTotalSupply` as a child, and any check whose targets were + * not already resolved by an earlier child reverted on the blind `areAddressesListed` call. + * The early exit in `_detectTransferRestrictionForTargets` made that input-dependent — + * `(ADDRESS1, ADDRESS2)` still passed while `(ADDRESS1, ADDRESS3)` reverted — which is what + * made it hard to notice. + * + * The guard requires {IAddressListBatchQuery}, the single function the wrapper actually + * calls, rather than the whole of `IAddressList`. Nethermind AuditAgent NM-18, audit F-5. */ - function test_WW2_NonAddressListChildRuleBricksWrapper_CurrentBehaviour() public { + function test_WW2_NonAddressListChildRuleIsRejectedAtAddRule() public { TotalSupplyMock token = new TotalSupplyMock(); vm.startPrank(DEFAULT_ADMIN_ADDRESS); RuleWhitelist childA = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, false); @@ -637,16 +646,95 @@ contract ThreatModelTests is Test, HelperContract { // RuleMaxTotalSupply is a valid IRule but exposes no `areAddressesListed`. RuleMaxTotalSupply notAnAddressList = new RuleMaxTotalSupply(DEFAULT_ADMIN_ADDRESS, address(token), 1000); + vm.expectRevert( + abi.encodeWithSelector(RuleWhitelistWrapper_ChildIsNotAnAddressList.selector, address(notAnAddressList)) + ); wrapper.addRule(IRule(address(notAnAddressList))); vm.stopPrank(); - // Both endpoints resolved by childA: the early-exit never reaches the broken child. - assertEq(wrapper.detectTransferRestriction(ADDRESS1, ADDRESS2, 10), TRANSFER_OK); + // The wrapper is intact: the pair that used to revert now answers normally. + assertEq(wrapper.detectTransferRestriction(ADDRESS1, ADDRESS3, 10), CODE_ADDRESS_TO_NOT_WHITELISTED); + assertEq(wrapper.rulesCount(), 1, "the bad child was never added"); + } - // ADDRESS3 is listed nowhere, so the scan continues into the broken child and reverts - // instead of returning CODE_ADDRESS_TO_NOT_WHITELISTED. - vm.expectRevert(); - wrapper.detectTransferRestriction(ADDRESS1, ADDRESS3, 10); + /** + * @notice WW-2: the guard also refuses a nested wrapper, which would otherwise brick the parent. + * @dev `RuleWhitelistWrapper` aggregates children but does not itself implement + * `areAddressesListed`, so it cannot be a child of another wrapper. Before the guard that + * configuration was accepted and reverted every transfer through the parent; now it is + * refused up front. Enabling nesting is a separate change (NM-19). + */ + function test_WW2_NestedWrapperIsRejectedAtAddRule() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleWhitelistWrapper inner = new RuleWhitelistWrapper(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, true); + RuleWhitelistWrapper outer = new RuleWhitelistWrapper(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, true); + + vm.expectRevert(abi.encodeWithSelector(RuleWhitelistWrapper_ChildIsNotAnAddressList.selector, address(inner))); + outer.addRule(IRule(address(inner))); + vm.stopPrank(); + } + + /** + * @notice WW-2 / NM-20: **FIXED.** A deny-list child is now refused at `addRule`. + * @dev This test formerly asserted the opposite and was named `..._CurrentBehaviour`: `RuleBlacklist` + * answers `areAddressesListed` just as faithfully as a whitelist and advertises the same + * interface ids, so the ERC-165 guard added for NM-18 could not tell them apart — the wrapper + * accepted it and then reported blacklisted addresses as eligible investors, `isVerified` + * included. + * + * Polarity is now declared rather than inferred: {IAddressListPolarity} adds `isAllowList()`, + * the wrapper requires it via ERC-165 and refuses any child answering `false`. Membership and + * meaning are separate questions, so they need separate interfaces. + */ + function test_WW2_DenyListChildIsRejectedAtAddRule() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleBlacklist denyList = new RuleBlacklist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + denyList.addAddress(ATTACKER); + assertFalse(denyList.isAllowList(), "a blacklist declares itself a deny-list"); + + RuleWhitelistWrapper wrapper = new RuleWhitelistWrapper(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, true); + vm.expectRevert(abi.encodeWithSelector(RuleWhitelistWrapper_ChildIsNotAnAllowList.selector, address(denyList))); + wrapper.addRule(IRule(address(denyList))); + vm.stopPrank(); + + assertEq(wrapper.rulesCount(), 0, "the deny-list was never added"); + // The inversion this finding described can no longer be configured. + assertFalse(wrapper.isVerified(ATTACKER)); + } + + /** + * @notice WW-2 / NM-20: a rule that declines to declare polarity is refused too. + * @dev `RuleSpenderWhitelist` deliberately does not implement {IAddressListPolarity}. Its set IS an + * allow-list, so declaring `true` would be honest about polarity and still wrong — the listed + * addresses are permitted *spenders*, not permitted *holders*, and the wrapper would read them + * as eligible transfer participants. Withholding the declaration is what makes the wrapper's + * fail-closed check refuse it: absence is a refusal, never an assumed allow-list. + */ + function test_WW2_ChildDecliningToDeclarePolarityIsRejected() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleSpenderWhitelist spenderList = new RuleSpenderWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + RuleWhitelistWrapper wrapper = new RuleWhitelistWrapper(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, true); + + vm.expectRevert( + abi.encodeWithSelector(RuleWhitelistWrapper_ChildDoesNotDeclarePolarity.selector, address(spenderList)) + ); + wrapper.addRule(IRule(address(spenderList))); + vm.stopPrank(); + } + + /// @notice WW-2 / NM-20: genuine allow-lists are still accepted, so the guard is not simply refusing all. + function test_WW2_AllowListChildrenAreStillAccepted() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleWhitelist allowList = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, false); + allowList.addAddress(ADDRESS1); + assertTrue(allowList.isAllowList(), "a whitelist declares itself an allow-list"); + + RuleWhitelistWrapper wrapper = new RuleWhitelistWrapper(DEFAULT_ADMIN_ADDRESS, FORWARDER, false, true); + wrapper.addRule(IRule(address(allowList))); + vm.stopPrank(); + + assertEq(wrapper.rulesCount(), 1); + assertTrue(wrapper.isVerified(ADDRESS1)); } /** diff --git a/test/TransferContext/OverloadParity.t.sol b/test/TransferContext/OverloadParity.t.sol index 24e3327d..0235bae1 100644 --- a/test/TransferContext/OverloadParity.t.sol +++ b/test/TransferContext/OverloadParity.t.sol @@ -62,9 +62,22 @@ interface INFTAdapterRule { * struct entrypoints on EVERY rule that inherits {RuleNFTAdapter}, closing the residual * coverage gap in `RuleNFTAdapter` / `RuleTransferValidation`. * @dev The property under test is **parity**: `RuleNFTAdapter` exists only to re-expose the same - * restriction logic under extra signatures, ignoring `tokenId`. So for every rule and every - * input, the `tokenId` overload MUST be indistinguishable from its fungible counterpart, and - * the `ctx` entrypoints MUST dispatch to the same internal hooks. Any divergence is a bug. + * restriction logic under extra signatures, ignoring `tokenId`. So for every rule, entrypoints + * that describe the SAME transfer MUST return the same answer, and any divergence is a bug. + * + * "The same transfer" is the subtlety, because the interfaces signal a direct transfer + * differently — this is what NM-6 turned on, and stating it loosely is what hid the gap: + * + * | Interface | A direct transfer arrives as | A delegated one as | + * |---|---|---| + * | CMTAT 3-arg / 4-arg | 3-arg, or `spender == address(0)` | `spender != 0`, any value | + * | ERC-7943 5-arg | `spender == from` (the spec calls it "owner/operator") | `spender != from` | + * | {ITransferContext} | `sender == from`, or `sender == 0` | `sender != from` | + * + * So `4-arg(spender == from)` and `5-arg(spender == from)` describe DIFFERENT transfers and are + * expected to differ; `test_NM6_CmtatFourArgPathKeepsScreeningASelfSpender` pins that on purpose. + * Everything that does describe the same transfer must agree, which is what + * `_assertSelfSpenderIsDirect` adds to the original two cases (`sender == 0`, `sender != from`). * * This also pins threat `AC-5`: the `ctx` entrypoints are `external` with no access control on * validation rules. That is acceptable precisely because they are view-only — an unprivileged @@ -154,6 +167,49 @@ contract OverloadParity is Test, HelperContract { ); } + /** + * @dev NM-6: `spender == from` is an OWNER-INITIATED transfer, and every entrypoint whose + * interface reports the initiator (the ERC-7943 spender-aware overloads and both + * {ITransferContext} structs) must route it to the DIRECT hook — the same answer a plain + * `transfer` gets. Before the fix the ERC-7943 overloads called the spender-aware hook + * unconditionally, so an owner-initiated ERC-721 `transferFrom` was screened as delegated + * while the identical `ctx` call was not. + */ + function _assertSelfSpenderIsDirect(address rule, address from, address to, uint256 value, string memory w) + internal + { + INFTAdapterRule r = INFTAdapterRule(rule); + uint8 directCode = r.detectTransferRestriction(from, to, value); + + assertEq( + r.detectTransferRestrictionFrom(from, from, to, TOKEN_ID, value), + directCode, + string.concat(w, ": ERC-7943 detectTransferRestrictionFrom(spender==from) must equal the direct code") + ); + assertEq( + r.canTransferFrom(from, from, to, TOKEN_ID, value), + r.canTransfer(from, to, value), + string.concat(w, ": ERC-7943 canTransferFrom(spender==from) must equal canTransfer") + ); + + bool direct = _try3(rule, from, to, value); + assertEq( + _try5Nft(rule, from, from, to, value), + direct, + string.concat(w, ": ERC-7943 transferred(spender==from) must match transferred(from,to,value)") + ); + assertEq( + _tryFungibleCtx(rule, from, from, to, value), + direct, + string.concat(w, ": FungibleContext(sender==from) must match transferred(from,to,value)") + ); + assertEq( + _tryMultiCtx(rule, from, from, to, value), + direct, + string.concat(w, ": MultiTokenContext(sender==from) must match transferred(from,to,value)") + ); + } + /// @dev Runs both parity checks for an allowed pair and a blocked pair. function _assertParity( address rule, @@ -169,6 +225,9 @@ contract OverloadParity is Test, HelperContract { _assertReadParity(rule, spender, badFrom, badTo, 10, string.concat(what, " [blocked]")); _assertWriteParity(rule, spender, badFrom, badTo, 10, string.concat(what, " [blocked]")); + + _assertSelfSpenderIsDirect(rule, okFrom, okTo, 10, string.concat(what, " [self-spender, allowed]")); + _assertSelfSpenderIsDirect(rule, badFrom, badTo, 10, string.concat(what, " [self-spender, blocked]")); } /*////////////////////////////////////////////////////////////// @@ -220,6 +279,67 @@ contract OverloadParity is Test, HelperContract { _assertWriteParity(address(rule), ADDRESS3, ADDRESS1, ADDRESS2, 10, "RuleSpenderWhitelist [ok spender]"); _assertReadParity(address(rule), ATTACKER, ADDRESS1, ADDRESS2, 10, "RuleSpenderWhitelist [bad spender]"); _assertWriteParity(address(rule), ATTACKER, ADDRESS1, ADDRESS2, 10, "RuleSpenderWhitelist [bad spender]"); + + // NM-6. ADDRESS1 is NOT on the spender whitelist, so this is the rule where the self-spender + // routing is observable rather than merely tidy. + _assertSelfSpenderIsDirect(address(rule), ADDRESS1, ADDRESS2, 10, "RuleSpenderWhitelist [self-spender]"); + } + + /** + * @notice NM-6: an owner moving their own tokens is never blocked by the spender whitelist, + * whichever spender-reporting entrypoint the token uses. + * @dev This rule documents that direct transfers are always allowed and only delegated ones are + * screened. An owner-initiated ERC-721 `transferFrom` arrives as `spender == from` per the + * ERC-7943 interface ("the address performing the transfer (owner/operator)"), so routing it + * to the spender-aware hook contradicted that contract. + */ + function test_NM6_SelfSpenderIsNotScreenedByTheSpenderWhitelist() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleSpenderWhitelist rule = new RuleSpenderWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + rule.addAddress(ADDRESS3); + vm.stopPrank(); + + // ADDRESS1 is not a whitelisted spender, but it owns the tokens. + assertFalse(rule.isAddressListed(ADDRESS1), "precondition: owner is not a listed spender"); + + assertEq( + rule.detectTransferRestrictionFrom(ADDRESS1, ADDRESS1, ADDRESS2, TOKEN_ID, 10), + TRANSFER_OK, + "owner-initiated ERC-7943 transfer must not be screened as delegated" + ); + assertTrue(_try5Nft(address(rule), ADDRESS1, ADDRESS1, ADDRESS2, 10), "write path must accept it too"); + assertTrue(_tryFungibleCtx(address(rule), ADDRESS1, ADDRESS1, ADDRESS2, 10), "ctx path already accepted it"); + + // A genuine delegated transfer by the same unlisted address is still rejected: the fix + // narrows the screen to what it was always documented to cover, it does not remove it. + assertEq( + rule.detectTransferRestrictionFrom(ADDRESS1, ADDRESS3, ADDRESS2, TOKEN_ID, 10), + rule.CODE_ADDRESS_SPENDER_NOT_WHITELISTED(), + "an unlisted spender acting for someone else must still be blocked" + ); + assertFalse(_try5Nft(address(rule), ADDRESS1, ADDRESS3, ADDRESS2, 10), "and blocked on the write path"); + } + + /** + * @notice NM-6, the deliberate asymmetry: the CMTAT 4-arg path is NOT normalised. + * @dev It signals a direct transfer with `spender == address(0)` and the 3-arg overload, so + * `spender == from` there means the caller explicitly named a spender. The ERC-7943 and + * `ctx` interfaces have no zero-sentinel convention, which is why only they normalise. + * Pinned so nobody "aligns" the two and silently disables spender screening on the main path. + */ + function test_NM6_CmtatFourArgPathKeepsScreeningASelfSpender() public { + vm.startPrank(DEFAULT_ADMIN_ADDRESS); + RuleSpenderWhitelist rule = new RuleSpenderWhitelist(DEFAULT_ADMIN_ADDRESS, FORWARDER); + rule.addAddress(ADDRESS3); + vm.stopPrank(); + + assertEq( + rule.detectTransferRestrictionFrom(ADDRESS1, ADDRESS1, ADDRESS2, 10), + rule.CODE_ADDRESS_SPENDER_NOT_WHITELISTED(), + "the 4-arg CMTAT path screens whatever spender it is given" + ); + // ...while a plain transfer on that path carries no spender and passes. + assertEq(rule.detectTransferRestriction(ADDRESS1, ADDRESS2, 10), TRANSFER_OK); } function test_Parity_RuleSanctionsList() public { diff --git a/test/Version.t.sol b/test/Version.t.sol index cdb5dcf3..49a37ee4 100644 --- a/test/Version.t.sol +++ b/test/Version.t.sol @@ -8,6 +8,10 @@ import {RuleBlacklist} from "src/rules/validation/deployment/RuleBlacklist.sol"; import {RuleSanctionsList} from "src/rules/validation/deployment/RuleSanctionsList.sol"; import {ISanctionsList} from "src/rules/interfaces/ISanctionsList.sol"; import {RuleMaxTotalSupply} from "src/rules/validation/deployment/RuleMaxTotalSupply.sol"; +import {RuleMaxTotalSupplyERC3643} from "src/rules/validation/deployment/RuleMaxTotalSupplyERC3643.sol"; +import { + RuleMaxTotalSupplyERC3643Ownable2Step +} from "src/rules/validation/deployment/RuleMaxTotalSupplyERC3643Ownable2Step.sol"; import {TotalSupplyMock} from "src/mocks/TotalSupplyMock.sol"; import {RuleMaxBalance} from "src/rules/validation/deployment/RuleMaxBalance.sol"; import {RuleMaxBalanceOwnable2Step} from "src/rules/validation/deployment/RuleMaxBalanceOwnable2Step.sol"; @@ -21,13 +25,17 @@ import {RuleSpenderWhitelist} from "src/rules/validation/deployment/RuleSpenderW import {RuleReceiverWhitelist} from "src/rules/validation/deployment/RuleReceiverWhitelist.sol"; import {RuleIdentityRegistry} from "src/rules/validation/deployment/RuleIdentityRegistry.sol"; import {RuleChainlinkPoR} from "src/rules/validation/deployment/RuleChainlinkPoR.sol"; +import {RuleChainlinkPoRERC3643} from "src/rules/validation/deployment/RuleChainlinkPoRERC3643.sol"; +import { + RuleChainlinkPoRERC3643Ownable2Step +} from "src/rules/validation/deployment/RuleChainlinkPoRERC3643Ownable2Step.sol"; import {IdentityRegistryWhitelist} from "src/registry/IdentityRegistryWhitelist.sol"; import {AggregatorV3Interface} from "src/rules/interfaces/AggregatorV3Interface.sol"; import {AggregatorV3Mock} from "src/mocks/AggregatorV3Mock.sol"; import {TotalSupplyDecimalsMock} from "src/mocks/TotalSupplyDecimalsMock.sol"; contract VersionTest is Test, HelperContract { - string constant EXPECTED_VERSION = "0.5.0"; + string constant EXPECTED_VERSION = "0.6.0"; function testVersionRuleWhitelist() public { RuleWhitelist rule = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, ZERO_ADDRESS, true, false); @@ -100,6 +108,37 @@ contract VersionTest is Test, HelperContract { assertEq(rule.version(), EXPECTED_VERSION); } + function testVersionRuleMaxTotalSupplyERC3643() public { + TotalSupplyDecimalsMock token = new TotalSupplyDecimalsMock(18); + RuleMaxTotalSupplyERC3643 rule = new RuleMaxTotalSupplyERC3643(DEFAULT_ADMIN_ADDRESS, address(token), 1000); + assertEq(rule.version(), EXPECTED_VERSION); + } + + function testVersionRuleMaxTotalSupplyERC3643Ownable2Step() public { + TotalSupplyDecimalsMock token = new TotalSupplyDecimalsMock(18); + RuleMaxTotalSupplyERC3643Ownable2Step rule = + new RuleMaxTotalSupplyERC3643Ownable2Step(DEFAULT_ADMIN_ADDRESS, address(token), 1000); + assertEq(rule.version(), EXPECTED_VERSION); + } + + function testVersionRuleChainlinkPoRERC3643() public { + TotalSupplyDecimalsMock token = new TotalSupplyDecimalsMock(18); + AggregatorV3Mock feed = new AggregatorV3Mock(8, 1000 * 1e8); + RuleChainlinkPoRERC3643 rule = new RuleChainlinkPoRERC3643( + DEFAULT_ADMIN_ADDRESS, address(token), 18, AggregatorV3Interface(address(feed)), 1 days + ); + assertEq(rule.version(), EXPECTED_VERSION); + } + + function testVersionRuleChainlinkPoRERC3643Ownable2Step() public { + TotalSupplyDecimalsMock token = new TotalSupplyDecimalsMock(18); + AggregatorV3Mock feed = new AggregatorV3Mock(8, 1000 * 1e8); + RuleChainlinkPoRERC3643Ownable2Step rule = new RuleChainlinkPoRERC3643Ownable2Step( + DEFAULT_ADMIN_ADDRESS, address(token), 18, AggregatorV3Interface(address(feed)), 1 days + ); + assertEq(rule.version(), EXPECTED_VERSION); + } + function testVersionRuleConditionalTransferLightMultiToken() public { RuleConditionalTransferLightMultiToken rule = new RuleConditionalTransferLightMultiToken(DEFAULT_ADMIN_ADDRESS); assertEq(rule.version(), EXPECTED_VERSION); diff --git a/test/VirtualHooks/BatchGuardPointerVirtual.t.sol b/test/VirtualHooks/BatchGuardPointerVirtual.t.sol new file mode 100644 index 00000000..e143e6d5 --- /dev/null +++ b/test/VirtualHooks/BatchGuardPointerVirtual.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {RuleAddressSetInternal} from "src/rules/validation/abstract/RuleAddressSet/RuleAddressSetInternal.sol"; + +/** + * @notice A-1/E-1: the batch zero-address guard is passed to `AddressSetBatchLib` as an **internal function + * pointer**, and must stay overridable. + * @dev Two things this pins, neither of which a compile-only check would catch: + * + * 1. `_requireNotZeroAddress` is `virtual`. Removing the keyword breaks this file's compilation, because + * the harness below declares `override`. + * 2. Virtual dispatch actually reaches the override **through the function pointer**. Solidity resolves an + * internal function pointer at the point of assignment, so it is not obvious that an override installed + * by a derived contract is the one `addBatch` ends up calling. It is — asserted here rather than + * assumed, because a silently shadowed override would leave the guard looking extensible while the base + * implementation kept running. + */ +contract BatchGuardPointerHarness is RuleAddressSetInternal { + error OverrideWasReached(); + + function _requireNotZeroAddress(address) internal pure override { + revert OverrideWasReached(); + } + + function addAddressesPublic(address[] calldata targets) external returns (uint256 added, uint256 skipped) { + return _addAddresses(targets); + } +} + +contract BatchGuardPointerVirtual is Test { + function testOverrideIsReachedThroughTheFunctionPointer() public { + BatchGuardPointerHarness harness = new BatchGuardPointerHarness(); + address[] memory targets = new address[](1); + targets[0] = address(0x1234); + + vm.expectRevert(BatchGuardPointerHarness.OverrideWasReached.selector); + harness.addAddressesPublic(targets); + } +}