diff --git a/.gitignore b/.gitignore index d9e0058..810321e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ docs/ # Dotenv file .env + +history diff --git a/.gitmodules b/.gitmodules index 415e443..4d17ab7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/RuleEngine"] + path = lib/RuleEngine + url = https://github.com/CMTA/RuleEngine diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3ae4d61 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,220 @@ +# DocumentEngine — Agent Guide + +> **Note — keep in sync:** `AGENTS.md` and `CLAUDE.md` must always be **identical**. +> Any edit to one must be applied verbatim to the other. + +> **Note — commit messages:** After each group of modifications or each feature +> added, always provide a **one-line GitHub commit message** (Conventional-Commits +> style, e.g. `feat: add token binding`, `fix: correct event args`, `docs: update README`). + +## What this project is + +`DocumentEngine` is a standalone smart contract that manages documents on-chain +through **ERC-1643** on behalf of **several** other smart contracts (e.g. CMTAT +tokens). Using an external engine keeps each token small and lets one operator +manage documents for a whole fleet of tokens. + +A document is `{ string uri, bytes32 documentHash, uint256 lastModified }`, +addressed by a `bytes32` name. + +## Key concepts + +- **Two management paths (both active at once):** + - **Admin path** — `DOCUMENT_MANAGER_ROLE`. Address-scoped overloads + (`setDocument(address,...)`, `removeDocument(address,...)`, batch variants) + manage documents for any contract. + - **Bound-token path** — the standard single-arg `IERC1643` functions + (`setDocument(name,uri,hash)`, `removeDocument(name)`) let a bound token manage + its **own** namespace (`_msgSender()`). Bind via the shared `ITokenBinding` + surface: `bindToken(token)` / `unbindToken(token)` / `isTokenBound(token)`, + implemented **once** for both deployments by `TokenBindingModule` — a single + allowlist, NOT a role (there is no `TOKEN_CONTRACT_ROLE`). Binding is authorized + by each deployment's document-management hook (DOCUMENT_MANAGER_ROLE / owner). + NOTE: RuleEngine's `ERC3643ComplianceExtendedModule` is intentionally **not** + reused for binding — it is an `IERC3643Compliance`, which would drag in + transfer-compliance callbacks (`canTransfer`/`transferred`/`created`/`destroyed`) + irrelevant to a document engine. See the README rationale section. +- **Events (ERC-1643 emission responsibility):** this engine is a *shared, + multi-token* manager, so it emits **only** the address-carrying extension events + `DocumentUpdatedForSubject` / `DocumentRemovedForSubject` (param `subject`) and + **not** the base `DocumentUpdated` / `DocumentRemoved` (those carry no address and + are the token contract's responsibility). Extension declared in + `src/interfaces/IERC1643MultiDocument.sol`; rationale in `ERC-1643-proposition.md`. +- **Errors live on interfaces, not on `DocumentEngineInvariant`.** Each specification + error is declared by the interface defining its condition — `ERC1643InvalidName` / + `ERC1643MissingDocument` on `IERC1643`, `MultiDocumentInvalidSubject` on + `IERC1643MultiDocument`, `TokenBindingInvalidToken` on `ITokenBinding` — so an ABI + generated from an interface carries its errors and each is obtained exactly once + (the multi-subject draft's "MUST NOT declare them twice", also a compile error). + `DocumentEngineInvariant` keeps only errors no interface defines. +- **Token binding is idempotent and rejects `address(0)`:** `bindToken` / `unbindToken` + write and emit `TokenBindingSet` only on an actual change, so every event is a real + transition; a repeat call succeeds silently. `address(0)` reverts + `TokenBindingInvalidToken()`. +- **ERC-1643 conformance:** `setDocument` reverts `ERC1643InvalidName()` on + `name == 0`; `removeDocument` reverts `ERC1643MissingDocument()` on a missing doc; + `supportsInterface` advertises `IERC1643` + `IERC1643MultiDocument` + `ITokenBinding` + (both deployments). The base id is for a **token** checking that the single-argument + endpoints exist before wiring itself to the engine — it does **not** mean documents + should be read from the engine's address, since those functions are `_msgSender()`-scoped. + Both errors are declared by `IERC1643` itself since CMTAT `v3.3.0-rc2` (still true in + `v3.3.0-rc3`) — do **not** + re-declare them in `DocumentEngineInvariant` (duplicate declaration = compile error, + and the multi-subject draft forbids it). +- **`getDocument` returns flat values**, `(string uri, bytes32 documentHash, + uint256 lastModified)`, never the `Document` struct — the struct is storage-only. + A struct return prepends an offset word to the returndata while leaving the selector + and `type(IERC1643).interfaceId` unchanged, so the mismatch is invisible to ERC-165 + and a spec-conformant consumer silently mis-decodes. Pinned by + `testGetDocumentReturnsFlatErc1643Abi`. +- **ERC-2771:** meta-transaction (gasless) support; `_msgSender()` is used everywhere. +- **Access control:** `DEFAULT_ADMIN_ROLE` implicitly has every role (see the + `hasRole` override). +- **No documentation pointers in contract comments.** Never write `See doc/…` or a `.md` path in + `src/` — docs move, deployed source does not, and a reader on a block explorer has neither. State + the conclusion in the comment instead, and keep it short; the derivation belongs in `doc/` with no + cross-reference either way. NatSpec links that resolve inside the source (`{_removeDocument}`) are + fine. Exempt: tests/mocks, and citations of audit records by **bare filename + finding ID** + (`CLAUDE_ANALYSIS.md (H-1)`) — those are immutable and survive a move. +- **Every `internal` function is `virtual`.** Not just the `_authorize*` hooks — the document + write/read paths (`_setDocument`, `_removeDocument`, `_removeDocumentName`, `_getDocument`), the + binding internals (`_setTokenBinding`, `_checkTokenBound`) and the ERC-2771 context trio are all + overridable. It costs nothing at runtime (bytecode is byte-identical with and without the keyword), + so **keep new internal functions `virtual`**; `OverridingDocumentEngine` in the test suite fails to + compile if one loses it. +- **Flexible access control (CMTAT / RuleEngine pattern):** restricted functions + use the `onlyDocumentManager` / `onlyBoundToken` modifiers, which delegate to + overridable `internal virtual` hooks `_authorizeDocumentManagement()` (per + deployment: `DOCUMENT_MANAGER_ROLE` / owner) and + `_authorizeBoundTokenDocumentManagement()` (implemented once by + `TokenBindingModule` → allowlist check). Keep the management implementation + separate from the authorization logic — change *who* is authorized via a hook, not by + editing the management functions. +- **CMTAT integration:** since CMTAT v3, a token uses the engine via CMTAT's + `DocumentEngineModule` and `setDocumentEngine(engine)` (reads/writes are forwarded + keyed by the token address). Standard CMTAT standalone tokens store documents + on-chain instead and do **not** use this engine. + +## File tree + +``` +src/ +├── DocumentEngineBase.sol # Abstract base: ERC-1643 document logic + storage, +│ # both management paths, batch functions, modifiers, +│ # and the ABSTRACT _authorize* hooks (no access control) +├── DocumentEngine.sol # Deployment #1: role-based access control +│ # (AccessControlEnumerable, DOCUMENT_MANAGER_ROLE, +│ # _authorizeDocumentManagement, hasRole), ERC-2771, +│ # supportsInterface, constructor +├── DocumentEngineOwnable.sol # Deployment #2: Ownable2Step (single owner) instead of +│ # roles; document mgmt + binding are owner-only +├── DocumentEngineInvariant.sol # Non-specification errors ONLY (InvalidInputLength, +│ # AdminWithAddressZeroNotAllowed). Every spec error is +│ # declared by its own interface — see the note below. +│ # NO access-control specifics +├── interfaces/ +│ ├── IERC8303.sol # ERC-8303 "Contract Version" interface (id 0x54fd4d50) +│ ├── IERC1643MultiDocument.sol # Multi-token ERC-1643 extension (address-scoped fns + +│ │ # DocumentUpdatedForSubject / DocumentRemovedForSubject + +│ │ # MultiDocumentInvalidSubject) +│ └── ITokenBinding.sol # Shared binding surface: bindToken / unbindToken / +│ # isTokenBound + TokenBindingSet + TokenBindingInvalidToken +└── modules/ + ├── VersionModule.sol # Version module: implements ERC-8303 version() + ERC-165, + │ # holds the VERSION constant (currently "0.4.0") + └── TokenBindingModule.sol # Shared token-binding allowlist (ITokenBinding) + NotBoundToken; + # wires the bound-token hook; used by both deployments + +script/ +├── DeployDocumentEngine.s.sol # Deploy role-based DocumentEngine (env: DOCUMENT_ENGINE_ADMIN, +│ # DOCUMENT_ENGINE_FORWARDER); run()=env, deploy(admin,fwd)=testable +└── DeployDocumentEngineOwnable.s.sol # Deploy Ownable variant (env: DOCUMENT_ENGINE_OWNER, _FORWARDER) + +test/ +├── DocumentEngine.t.sol # Foundry tests: deploy, access control, admin + bound-token +│ # paths, batch ops (incl. name==0 / missing-doc guards), +│ # ERC-8303 + interface discovery, event emission (asserts the +│ # base events are NOT emitted), msg.sender-scoped reads, +│ # enumeration, fuzz round-trip/isolation, CMTAT integration +│ # (CMTATDocumentEngineMock), flexible-auth override (OpenDocumentEngine) +├── DocumentEngineOwnable.t.sol # Tests for the Ownable2Step deployment (owner path, +│ # token binding, two-step ownership, ERC-8303) +└── Deploy.t.sol # Tests for the deployment scripts (deploy() state + run() env) +``` + +**Contract split (CMTAT module/deployment pattern):** `DocumentEngineBase` holds +the document logic and abstract `_authorize*` hooks; each deployment contract +supplies the concrete access control. There are two deployments — +`DocumentEngine` (role-based, `AccessControlEnumerable`) and +`DocumentEngineOwnable` (`Ownable2Step`). Add new management logic in the base; +change *who* is authorized in a deployment (implement the `_authorize*` hooks). + +Other important files: + +- `foundry.toml` — solc `0.8.34`, `evm_version = prague` (required by CMTAT v3). + Sources declare `pragma solidity ^0.8.24` — the real `src/` floor, set by OpenZeppelin's + `AccessControlEnumerable`/`EnumerableSet` and CMTAT's `draft-IERC1643` since `v3.3.0-rc3`. + Building the tests needs `≥ 0.8.27` (CMTAT's `require(cond, CustomError())` is via-ir-only + before then). Keep the pragma honest: if a dependency raises its floor, raise ours to match. +- `remappings.txt` — `CMTAT/`, `RuleEngine/`, `OZ/`, `@openzeppelin/contracts-upgradeable/`. +- `CHANGELOG.md` — semver history; update on every release (current: `v0.4.0`). +- `ERC-1643-proposition.md` — proposed optional multi-token events / extension. +- `README.md` — **short** entry point only: what the engine is, quick start, the two management + paths, the CMTAT wiring, the two integrator caveats, deploy. Keep it short; new prose belongs in + the full document. +- `doc/README.md` — the specification / full reference (Surya schema, ERC-165 rationale, version + compatibility matrix, tooling). This is where the old root README moved. +- `doc/img/` — PlantUML **sources** (`*.puml`) plus their rendered `*.png`. Five diagrams, split by + audience: `cmtat-write-simple` / `cmtat-read-simple` are the **short** pair, used in *both* + `README.md` and `doc/README.md`; `cmtat-integration-architecture` (topology), + `documentengine-contract-structure` (inheritance) and `cmtat-integration-sequence` (full call + flow with every revert branch) belong to `doc/README.md` only — keep them out of the root README. + One diagram, one job: when a schema needs a legend to stay legible, split it instead. Only images + are embedded, never the source. Re-render with `plantuml -tpng doc/img/.puml`, and look + at the PNG: PlantUML draws syntax/deprecation warnings *into* the image and still exits 0. +- `doc/` — Surya output in `doc/surya/{surya_graph,surya_inheritance,surya_report}`, one file per + `.sol` in `src/` (9 each), regenerated by the three scripts in `doc/script/` — run them from that + directory, **graph first** (it creates the scratch `docOut/`; the report script's `mkdir` lacks + `-p`). Patch `surya/lib/graph.js` before regenerating or every contract calling `super.()` + yields a silent 0-byte PNG; see the Surya section in `doc/README.md`. Also coverage, and + `doc/audits/` — the security overview (`AUDIT_OVERVIEW.md`) plus versioned + static-analysis output under `doc/audits/tools/vX.Y.Z//`, each with a + `*-report.md` (summary table prepended) and a `*-report-feedback.md` triaging + every finding, plus a `claude/` section holding the AI-assisted code-quality review. + Both Aderyn `0.6.5` (0 High · 6 Low) and Slither `0.11.5` + (0 High · 0 Med · 0 Low · 2 Info) were run for `v0.4.0` — nothing to fix in either. + Slither's dependency filter must be `lib` (Foundry layout); `--filter-paths` fails + open, so an entry matching nothing silently pulls the vendored tree into scope. + `doc/audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md` is the code-quality review (not a security audit) — read + its "left as is" rows before proposing an optimisation: `unchecked {++i}` (0 gas on solc + 0.8.34), `string calldata` on the admin `setDocument` (49 gas *worse*), and extracting the + duplicated ERC-2771 overrides (impossible — C3 linearization) are all measured dead ends. +- **Open items live in `doc/audits/AUDIT_OVERVIEW.md`** under *Known open items*, with stable + `OPEN-n` ids that the audit reports cite. There is no `IMPROVEMENT.md` — it was folded in. + Update that table when an item is fixed (move the record to `CHANGELOG.md`) or when review + surfaces a new one; keep the existing ids stable so the citations stay valid. +- `lib/` — submodules: `CMTAT`, `RuleEngine`, `openzeppelin-contracts(-upgradeable)`, `forge-std`. + +## Dependencies (tested versions) + +- CMTAT `v3.3.0-rc3`, RuleEngine `v3.0.0-rc5` (binding-pattern reference only; compliance module not reused) +- OpenZeppelin Contracts / Contracts Upgradeable `v5.7.0` +- Solidity `0.8.34`, Foundry + +## Common commands + +```bash +forge build # compile +forge test # run the test suite +forge fmt # format +forge test --gas-report +``` + +## Conventions + +- The `VERSION` constant (in `src/modules/VersionModule.sol`, exposed via + ERC-8303 `version()`) must match the latest `CHANGELOG.md` entry on release. +- Bump `MAJOR` on incompatible proxy-storage / external-library or API changes, + `MINOR` for backward-compatible features, `PATCH` for backward-compatible fixes. +- A bound token can only ever affect its **own** document namespace — never break + that isolation. diff --git a/CHANGELOG.md b/CHANGELOG.md index 199d877..0335095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,120 @@ # CHANGELOG -Please follow https://changelog.md/ conventions. +Please follow [https://changelog.md](https://changelog.md) conventions and the other conventions below + +## Semantic Version 2.0.0 + +Given a version number MAJOR.MINOR.PATCH, increment the: + +1. MAJOR version when the new version makes: + - Incompatible proxy **storage** change internally or through the upgrade of an external library (OpenZeppelin) + - A significant change in external APIs (public/external functions) or in the internal architecture +2. MINOR version when the new version adds functionality in a backward compatible manner +3. PATCH version when the new version makes backward compatible bug fixes + +See [https://semver.org](https://semver.org) + +## Type of changes + +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +Reference: [keepachangelog.com/en/1.1.0/](https://keepachangelog.com/en/1.1.0/) + +## Checklist + +> Before a new release, perform the following tasks + +- Code: Update the version name in the `Version` core module, variable VERSION +- Run the formatter + +> forge fmt + +- Package + - Run `npm audit fix` + +- Documentation + - Perform a code coverage and update the files in the corresponding directory [./doc/coverage](./doc/coverage) (`forge coverage --report lcov --report-file /tmp/lcov-full.info`, then `lcov --extract /tmp/lcov-full.info 'src/*' -o doc/coverage/lcov.info` and `genhtml doc/coverage/lcov.info --output-directory doc/coverage/coverage`; the `src/*` filter keeps `test/` and `script/` out of the published figure) + - Perform an audit with several audit tools (Aderyn and Slither), update the report in the corresponding directory [./doc/audits/tools](./doc/audits/tools) + - Update surya doc by running the 3 scripts in [./doc/script](./doc/script) + - Update changelog + +## v0.4.0 + +Targets **CMTAT `v3.3.0-rc3`** — see the [compatibility matrix](./doc/README.md#version-compatibility) for which CMTAT release each version of this engine is built against. + +> **Versioning note.** `getDocument` changes shape relative to `v0.3.0`, which the convention above classifies as a MAJOR bump. `MINOR` is used because the project is still in its `0.x` line, where a `1.0.0` would wrongly signal a stable, audited release. Treat this release as breaking for any consumer decoding `getDocument`. + +### Changed + +- **Dependencies** + - Upgrade CMTAT `v2.5.0-rc0` → [`v3.3.0-rc3`](https://github.com/CMTA/CMTAT/releases/tag/v3.3.0-rc3) (`lib/CMTAT` → `658672f190d56d3f61663a7d6d51962b8980df70`). Development passed through `v3.3.0-rc1` and `v3.3.0-rc2`. rc1 is **not** compatible with the code as shipped here, because it declares neither the ERC-1643 errors nor the flat `getDocument` return (see below); rc2 and rc3 are interchangeable for this engine — between them, the whole document surface (`draft-IERC1643.sol`, `IDocumentEngine.sol`, `DocumentEngineModule.sol`, `DocumentERC1643Module.sol`) changed only its pragma, `^0.8.20` → `^0.8.24`. Both are therefore listed as the supported range, verified by building and running the full suite against each (74/74 on both). Nothing below rc2 works: `v3.0.0`/`v3.1.0`/`v3.2.0` return a `Document` struct and declare no interface errors, and they predate CMTAT's token-side `DocumentEngineModule` entirely. + - Upgrade OpenZeppelin Contracts (and Contracts Upgradeable) `v5.0.2` → [`v5.7.0`](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.7.0). `v5.7.0` deprecates `EnumerableSet.at()` in favour of `pos()` (the old name clashes with a keyword scheduled for Solidity); `at()` remains as a forwarding alias, and this engine has no call sites either way. The only exposure is inherited — `AccessControlEnumerable.getRoleMember` switched to `pos()` internally, with no change to its signature, selector or behaviour. Verified: `DocumentEngine`'s runtime code is **byte-identical** across `v5.6.1` and `v5.7.0` (8436 bytes; only the CBOR metadata trailer moves, because the source text of `AccessControlEnumerable.sol` changed), and `DocumentEngineOwnable`'s bytecode is unchanged including metadata. + - Add [CMTA/RuleEngine](https://github.com/CMTA/RuleEngine) [`v3.0.0-rc5`](https://github.com/CMTA/RuleEngine/releases/tag/v3.0.0-rc5) as a submodule (binding-pattern reference; see [Why not reuse RuleEngine's compliance module?](./doc/README.md#why-not-reuse-ruleengines-erc-3643-compliance-module) — its `ERC3643ComplianceExtendedModule` is not reused) + - `foundry.lock` now records every submodule by tag; all five entries had gone stale since `v0.3.0`. +- **Toolchain**: bump Solidity `0.8.26` → `0.8.34` and `evm_version` `cancun` → `prague` to match CMTAT v3 (CMTAT uses `require(cond, CustomError())`, which needs solc ≥ 0.8.27) +- **Code-quality review** (`doc/audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md`) — 14 findings, none a vulnerability. Six implemented: + - **Gas, `_removeDocumentName`**: the `_documentNames[subject]` mapping slot was re-hashed on every loop iteration; cached as a storage pointer. Measured **−2200 gas** on a 20-entry full scan. + - **Gas, `_removeDocument`**: the whole `Document` (URI included) was copied to memory to be read three times; now read through a storage pointer. A further **−645 gas**. Combined, removal is **−2845 gas (−3.3 %)** worst case. The emit must stay ahead of the `delete` — verified by mutating the order and confirming `testRemoveDocumentEmitsForSubjectEvent` fails. Side effect: Slither's `incorrect-equality` (Medium) and `timestamp` (Low) stopped firing on the unchanged `doc.lastModified == 0`, taking it from 4 results to 2. **Not a fix** — both were already false positives and the detector merely loses the taint through a storage pointer. + - **`hasRole` NatSpec**: documented that a role is **unrevokable from the default admin** — `revokeRole` succeeds, emits `RoleRevoked` and drops `getRoleMemberCount`, yet the admin keeps the access. Not a privilege issue (an admin can re-grant itself anything) but the call misreports. Pinned by the new `testRevokingRoleFromDefaultAdminDoesNotRemoveAccess`. + - **`DocumentEngineInvariant`**: the error-location comment misattributed `NotBoundToken(address)` to `ITokenBinding`; it is declared by `TokenBindingModule`. + - **Documentation pointers removed from contract comments.** Three comments referenced `doc/ERCSpecification…`; documentation moves but deployed source does not, and this repo had already renamed that file once (`ERC-1643-proposition.md` → `erc-draft_multi_document_management.md`), leaving a dangling README link behind. Someone reading verified source on an explorer has the comment and not the file. All three pointers are gone and each comment is now **shorter**, not longer — the `IERC1643MultiDocument` header dropped from 10 lines to 9 by replacing an enumeration that gestured at the draft's rationale with the one operative fact: `subject` need not be a token. + - **All 12 `internal` functions are now `virtual`** (`_setDocument`, `_removeDocument`, `_removeDocumentName`, `_getDocument`, `_setTokenBinding`, `_checkTokenBound`, and the ERC-2771 context trio in both deployments), resolving an inconsistency where `TokenBindingModule` exposed its public surface for override while `DocumentEngineBase` exposed nothing but its two abstract hooks. A deployment can now override the document write/read paths and the binding check, matching what CMTAT's equivalent module allows. **Runtime cost is zero:** the executable bytecode of both deployments is byte-identical before and after (7457 / 6111 bytes, metadata trailer excluded). Guarded by `OverridingDocumentEngine` + `testInternalHooksAreVirtualAndOverridesAreReached` — removing `virtual` from any of the three overridden hooks fails the build (`Error (4334): Trying to override non-virtual function`). + + Notable non-changes, recorded so they are not re-raised: `unchecked { ++i }` buys **0 gas** on solc 0.8.34 (measured); `string calldata` on the admin `setDocument` is **49 gas worse** than `memory` (measured); and the duplicated ERC-2771 context overrides **cannot** be extracted into a shared module — C3 linearization forces each deployment to re-state them, proven by compiler error. +- **Style pass across `src/` and `script/` — behaviour-preserving.** Brought the sources in line with the Solidity style guide: functions reordered by visibility group (external → public → internal, `view`/`pure` last within each), so the `_authorize*` hooks and the ERC-2771 context overrides now follow the public API instead of preceding it; every brace-less global import replaced by a named one (which required adding the previously implicit `Context` and `AccessControl` imports, since a named import no longer re-exports a dependency's own imports); and NatSpec completed with a `@param` per argument and a `@return` per return value. No signature, visibility, body or storage layout changed — verified by an unchanged per-contract function set, a clean `forge build`, and 72/72 tests passing. +- **Source pragma raised `^0.8.20` → `^0.8.24`** across `src/`, `script/` and `test/`. This is a correction, not a new restriction: `^0.8.20` had become an over-promise, advertising a range the sources could not actually compile in. OpenZeppelin's `AccessControlEnumerable.sol` and `EnumerableSet.sol` are `^0.8.24`, and CMTAT `v3.3.0-rc3` moved `draft-IERC1643.sol` to `^0.8.24` as well, so every contract in `src/` now transitively requires it — `forge build --use 0.8.23` fails to resolve a compiler. `0.8.24` is the real `src/` floor; the full project including the CMTAT-importing tests needs `0.8.27`, because `require(cond, CustomError())` is restricted to the via-ir pipeline before then. Deployed bytecode is unaffected — the pinned compiler is still `0.8.34`. +- **`IERC1643` (CMTAT v3) breaking changes** + - `getDocument` keeps returning `(string uri, bytes32 documentHash, uint256 lastModified)` — the flat ERC-1643 ABI — on **both** overloads, `getDocument(bytes32)` and `getDocument(address subject, bytes32)`. CMTAT `v3.3.0-rc1` briefly replaced this with a `Document` struct and `v3.3.0-rc2` reverted it; this engine follows rc2/rc3, so relative to `v0.3.0` the external shape is unchanged. + + The distinction is worth recording because it is invisible to interface detection: return types are not part of a function signature, so both shapes share the same selectors and the same `type(IERC1643).interfaceId` (`0xecfecec8`). A consumer built from the specification ABI decodes a struct return as garbage *without reverting* — `uri` becomes binary junk, `documentHash` becomes `0x…60`, and `lastModified` becomes the real hash as a `uint256`. `getDocument` is now covered by `testGetDocumentReturnsFlatErc1643Abi`, which inspects the returndata directly since ERC-165 structurally cannot. + - The `Document` struct and the `DocumentUpdated`/`DocumentRemoved` events are now provided by `IERC1643`; the duplicate local declarations were removed from `DocumentEngineInvariant`. The struct is retained internally for storage only. + - `ERC1643InvalidName()` / `ERC1643MissingDocument()` are likewise declared by `IERC1643` as of CMTAT `v3.3.0-rc2` and are **not** re-declared here. The multi-subject draft requires a contract implementing both interfaces to obtain each error exactly once ("MUST NOT declare them twice"), and re-declaring is a compile error. Selectors, and hence revert data, are unchanged. The same principle was applied to every other error: `MultiDocumentInvalidSubject()` moved to `IERC1643MultiDocument` and `TokenBindingInvalidToken()` is declared on `ITokenBinding`, so an ABI generated from an interface carries its errors. `DocumentEngineInvariant` now holds only `InvalidInputLength` and `AdminWithAddressZeroNotAllowed`, which no interface defines. + - Import path moved: `CMTAT/interfaces/engine/draft-IERC1643.sol` → `CMTAT/interfaces/tokenization/draft-IERC1643.sol`. + +- **`ERC1643InvalidSubject()` renamed to `MultiDocumentInvalidSubject()`** and moved from `DocumentEngineInvariant` to `IERC1643MultiDocument`, matching the multi-subject draft. **This changes the error selector**, so integrators decoding this revert must update. + + The draft's rule is that an error is prefixed by the proposal that *defines* its condition, not by the one it sits next to. The null-`subject` condition cannot arise in ERC-1643 at all — its `setDocument` has no `subject` argument, so the subject is implicitly the contract itself, which is never the null address — so borrowing the `ERC1643` prefix named the error after a standard in which it is unreachable. The two genuinely-shared errors keep their prefix for the opposite reason. +- **Token binding rejects `address(0)` and is idempotent.** `bindToken` / `unbindToken` now revert `TokenBindingInvalidToken()` on the null address — which can never call the engine, so binding it granted nothing while still emitting an event indexers key on — and write plus emit `TokenBindingSet` **only when the binding actually changes**. A repeated call still succeeds, since the caller's intent already holds, but emits nothing, so every event in the log is a real transition and an indexer never has to de-duplicate. + +### Added + +- **Bound-token document management**: implement the now-mandatory `IERC1643.setDocument(name, uri, hash)` and `removeDocument(name)`, gated by the `onlyBoundToken` modifier and scoped to the caller (`_msgSender()`) own namespace. A token bound with `bindToken(token)` (see the shared binding module below) manages its own documents and can never affect another contract's documents. The admin overloads (explicit `address`, `DOCUMENT_MANAGER_ROLE`) are unchanged, so both systems work side by side. (RuleEngine's `ERC3643ComplianceExtendedModule` was evaluated for the binding but intentionally not reused — see the README.) +- **Optional multi-token events**: alongside the standard `IERC1643` events, the engine now also emits `DocumentUpdatedForContract` / `DocumentRemovedForContract`, which carry the `smartContract` (token) address so off-chain indexers can tell which contract a document belongs to during multi-contract operations. See [`erc-draft_multi_document_management.md`](./doc/ERCSpecification/erc-draft_multi_document_management.md) for the proposed optional standard extension. +- **Flexible access control (CMTAT / RuleEngine pattern)**: the restricted functions use the `onlyDocumentManager` / `onlyBoundToken` modifiers, which delegate to overridable `internal virtual` authorization hooks `_authorizeDocumentManagement()` / `_authorizeBoundTokenDocumentManagement()`. Each deployment implements the admin hook (`DOCUMENT_MANAGER_ROLE` or `owner`); the bound-token hook is implemented once by `TokenBindingModule` (the shared allowlist). This separates the document-management implementation from the authorization logic. +- **Split into a base contract and a deployment contract** (CMTAT module/deployment pattern): the document-management logic and storage now live in the new abstract `DocumentEngineBase` (with abstract `_authorize*` hooks), while `DocumentEngine` is the deployment contract that defines the access control (`AccessControl`, the concrete hooks and `hasRole`) and the ERC-2771 wiring. The deployable `DocumentEngine` API and behavior are unchanged. +- **Version module implementing ERC-8303**: the version is now exposed through a dedicated `VersionModule` (`src/modules/VersionModule.sol`) implementing the `IERC8303` interface (`src/interfaces/IERC8303.sol`). It adds a standard `version()` view function (in addition to the existing public `VERSION` constant) and advertises ERC-8303 via ERC-165 (`supportsInterface(0x54fd4d50) == true`). `DocumentEngine` combines the module's `supportsInterface` with the access-control base. +- **Second deployment `DocumentEngineOwnable`** (`src/DocumentEngineOwnable.sol`): an alternative deployment that uses OpenZeppelin `Ownable2Step` (single owner, two-step transfer) instead of role-based access control, reusing the same `DocumentEngineBase` logic and the shared `TokenBindingModule`. Both document management and token binding are restricted to the `owner`. + +### Changed (access control) + +- `DocumentEngine` now inherits **`AccessControlEnumerable`** instead of `AccessControl`, adding on-chain enumeration of role members (`getRoleMember`, `getRoleMemberCount`) and advertising `IAccessControlEnumerable` via ERC-165. Default authorization behavior is unchanged. +- Moved the `DOCUMENT_MANAGER_ROLE` constant out of the shared `DocumentEngineInvariant` and into the role-based `DocumentEngine`, so `DocumentEngineInvariant` (and the `DocumentEngineOwnable` deployment) no longer carry access-control-specific constants. The invariant now holds only the shared errors. + +### Fixed (ERC-1643 conformance) + +Aligned the implementation with the updated [ERC-1643](./doc/ERCSpecification/erc-1643.md) (which now folds in the multi-token extension and the emission-responsibility rules): + +- **Emission responsibility.** As a shared, multi-token manager the engine now emits **only** the address-carrying extension events and **no longer** emits the base `DocumentUpdated` / `DocumentRemoved` events (the spec's `MUST NOT` for a shared manager — those events carry no `subject` and belong on the token contract). +- **Extension events/interface.** Renamed the multi-token events to the standard `DocumentUpdatedForSubject` / `DocumentRemovedForSubject` (parameter `subject`), and introduced the `IERC1643MultiDocument` interface (`src/interfaces/IERC1643MultiDocument.sol`) that the base now implements — the address-scoped `getDocument` / `getAllDocuments` / `setDocument` / `removeDocument`. +- **Input validation.** `setDocument` now reverts `ERC1643InvalidName()` when `name == bytes32(0)` and `MultiDocumentInvalidSubject()` when `subject == address(0)` (the multi-subject draft's null-namespace guard); `removeDocument` now reverts `ERC1643MissingDocument()` for a non-existent document (previously it silently emitted a spurious removal event). See [`erc-draft_multi_document_management.md`](./doc/ERCSpecification/erc-draft_multi_document_management.md) for the corresponding multi-subject draft. +- **ERC-165 discovery.** `supportsInterface` now returns `true` for `type(IERC1643).interfaceId`, `type(IERC1643MultiDocument).interfaceId` and `type(ITokenBinding).interfaceId` (both deployments). + + The base id is advertised because the engine implements the base single-argument functions, and because a **token** uses it: before wiring itself to the engine with `setDocumentEngine(engine)`, or before forwarding `setDocument(name, uri, hash)`, it can confirm through ERC-165 that those endpoints exist. It does **not** mean documents should be read from the engine's address — the base functions are `_msgSender()`-scoped, so a third-party read returns the caller's own empty namespace. Documented in the README and asserted by `testBaseERC1643IsAdvertisedButReadsAreCallerScoped`. + +### Added (token binding) + +- **Shared `ITokenBinding` interface + `TokenBindingModule`.** `bindToken(token)` / `unbindToken(token)` / `isTokenBound(token)` + `TokenBindingSet` event (`src/interfaces/ITokenBinding.sol`), implemented once for both deployments by `src/modules/TokenBindingModule.sol` — a single **allowlist**, not a role. Both deployments now share the exact same binding mechanism (same functions, event, and `NotBoundToken` revert on an unbound write) and advertise `type(ITokenBinding).interfaceId` via ERC-165. The role deployment **no longer uses `TOKEN_CONTRACT_ROLE`** (removed) — binding is authorized by the document-management hook (`DOCUMENT_MANAGER_ROLE`, or the `owner` in `DocumentEngineOwnable`). + +### Notes / bottlenecks + +- **Subject-side emission is CMTAT `v3.3.0-rc2` or later.** rc2 made `DocumentEngineModule` re-emit the standard `DocumentUpdated` / `DocumentRemoved` on the **token's own address** after forwarding to the engine, and revert with `CMTAT_DocumentEngineModule_NoDocumentEngine` when no engine is set. Combined with this engine emitting only the address-carrying `*ForSubject` events, the subject-initiated call topology is fully conformant with the multi-subject draft's *Emission Responsibility* rules. The **admin path remains non-conformant by construction** — a write sent straight to the engine has no execution point in the subject, so the subject emits nothing. See `OPEN-2` in [`AUDIT_OVERVIEW.md`](./doc/audits/AUDIT_OVERVIEW.md). +- Open items are tracked under *Known open items* in [`AUDIT_OVERVIEW.md`](./doc/audits/AUDIT_OVERVIEW.md): the most severe is admin-path call topology (`OPEN-2`); also authorization granularity (`OPEN-1`) and enumeration cost (`OPEN-4`). +- CMTAT v3 no longer ships a *standalone* token that consumes an external document engine through its constructor; the standard token stores documents on-chain (`DocumentERC1643Module`). External-engine integration now goes through CMTAT's `DocumentEngineModule` (`setDocumentEngine`). The test suite was updated to exercise this real integration path via a minimal token built on `DocumentEngineModule`. ## v0.3.0 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3ae4d61 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,220 @@ +# DocumentEngine — Agent Guide + +> **Note — keep in sync:** `AGENTS.md` and `CLAUDE.md` must always be **identical**. +> Any edit to one must be applied verbatim to the other. + +> **Note — commit messages:** After each group of modifications or each feature +> added, always provide a **one-line GitHub commit message** (Conventional-Commits +> style, e.g. `feat: add token binding`, `fix: correct event args`, `docs: update README`). + +## What this project is + +`DocumentEngine` is a standalone smart contract that manages documents on-chain +through **ERC-1643** on behalf of **several** other smart contracts (e.g. CMTAT +tokens). Using an external engine keeps each token small and lets one operator +manage documents for a whole fleet of tokens. + +A document is `{ string uri, bytes32 documentHash, uint256 lastModified }`, +addressed by a `bytes32` name. + +## Key concepts + +- **Two management paths (both active at once):** + - **Admin path** — `DOCUMENT_MANAGER_ROLE`. Address-scoped overloads + (`setDocument(address,...)`, `removeDocument(address,...)`, batch variants) + manage documents for any contract. + - **Bound-token path** — the standard single-arg `IERC1643` functions + (`setDocument(name,uri,hash)`, `removeDocument(name)`) let a bound token manage + its **own** namespace (`_msgSender()`). Bind via the shared `ITokenBinding` + surface: `bindToken(token)` / `unbindToken(token)` / `isTokenBound(token)`, + implemented **once** for both deployments by `TokenBindingModule` — a single + allowlist, NOT a role (there is no `TOKEN_CONTRACT_ROLE`). Binding is authorized + by each deployment's document-management hook (DOCUMENT_MANAGER_ROLE / owner). + NOTE: RuleEngine's `ERC3643ComplianceExtendedModule` is intentionally **not** + reused for binding — it is an `IERC3643Compliance`, which would drag in + transfer-compliance callbacks (`canTransfer`/`transferred`/`created`/`destroyed`) + irrelevant to a document engine. See the README rationale section. +- **Events (ERC-1643 emission responsibility):** this engine is a *shared, + multi-token* manager, so it emits **only** the address-carrying extension events + `DocumentUpdatedForSubject` / `DocumentRemovedForSubject` (param `subject`) and + **not** the base `DocumentUpdated` / `DocumentRemoved` (those carry no address and + are the token contract's responsibility). Extension declared in + `src/interfaces/IERC1643MultiDocument.sol`; rationale in `ERC-1643-proposition.md`. +- **Errors live on interfaces, not on `DocumentEngineInvariant`.** Each specification + error is declared by the interface defining its condition — `ERC1643InvalidName` / + `ERC1643MissingDocument` on `IERC1643`, `MultiDocumentInvalidSubject` on + `IERC1643MultiDocument`, `TokenBindingInvalidToken` on `ITokenBinding` — so an ABI + generated from an interface carries its errors and each is obtained exactly once + (the multi-subject draft's "MUST NOT declare them twice", also a compile error). + `DocumentEngineInvariant` keeps only errors no interface defines. +- **Token binding is idempotent and rejects `address(0)`:** `bindToken` / `unbindToken` + write and emit `TokenBindingSet` only on an actual change, so every event is a real + transition; a repeat call succeeds silently. `address(0)` reverts + `TokenBindingInvalidToken()`. +- **ERC-1643 conformance:** `setDocument` reverts `ERC1643InvalidName()` on + `name == 0`; `removeDocument` reverts `ERC1643MissingDocument()` on a missing doc; + `supportsInterface` advertises `IERC1643` + `IERC1643MultiDocument` + `ITokenBinding` + (both deployments). The base id is for a **token** checking that the single-argument + endpoints exist before wiring itself to the engine — it does **not** mean documents + should be read from the engine's address, since those functions are `_msgSender()`-scoped. + Both errors are declared by `IERC1643` itself since CMTAT `v3.3.0-rc2` (still true in + `v3.3.0-rc3`) — do **not** + re-declare them in `DocumentEngineInvariant` (duplicate declaration = compile error, + and the multi-subject draft forbids it). +- **`getDocument` returns flat values**, `(string uri, bytes32 documentHash, + uint256 lastModified)`, never the `Document` struct — the struct is storage-only. + A struct return prepends an offset word to the returndata while leaving the selector + and `type(IERC1643).interfaceId` unchanged, so the mismatch is invisible to ERC-165 + and a spec-conformant consumer silently mis-decodes. Pinned by + `testGetDocumentReturnsFlatErc1643Abi`. +- **ERC-2771:** meta-transaction (gasless) support; `_msgSender()` is used everywhere. +- **Access control:** `DEFAULT_ADMIN_ROLE` implicitly has every role (see the + `hasRole` override). +- **No documentation pointers in contract comments.** Never write `See doc/…` or a `.md` path in + `src/` — docs move, deployed source does not, and a reader on a block explorer has neither. State + the conclusion in the comment instead, and keep it short; the derivation belongs in `doc/` with no + cross-reference either way. NatSpec links that resolve inside the source (`{_removeDocument}`) are + fine. Exempt: tests/mocks, and citations of audit records by **bare filename + finding ID** + (`CLAUDE_ANALYSIS.md (H-1)`) — those are immutable and survive a move. +- **Every `internal` function is `virtual`.** Not just the `_authorize*` hooks — the document + write/read paths (`_setDocument`, `_removeDocument`, `_removeDocumentName`, `_getDocument`), the + binding internals (`_setTokenBinding`, `_checkTokenBound`) and the ERC-2771 context trio are all + overridable. It costs nothing at runtime (bytecode is byte-identical with and without the keyword), + so **keep new internal functions `virtual`**; `OverridingDocumentEngine` in the test suite fails to + compile if one loses it. +- **Flexible access control (CMTAT / RuleEngine pattern):** restricted functions + use the `onlyDocumentManager` / `onlyBoundToken` modifiers, which delegate to + overridable `internal virtual` hooks `_authorizeDocumentManagement()` (per + deployment: `DOCUMENT_MANAGER_ROLE` / owner) and + `_authorizeBoundTokenDocumentManagement()` (implemented once by + `TokenBindingModule` → allowlist check). Keep the management implementation + separate from the authorization logic — change *who* is authorized via a hook, not by + editing the management functions. +- **CMTAT integration:** since CMTAT v3, a token uses the engine via CMTAT's + `DocumentEngineModule` and `setDocumentEngine(engine)` (reads/writes are forwarded + keyed by the token address). Standard CMTAT standalone tokens store documents + on-chain instead and do **not** use this engine. + +## File tree + +``` +src/ +├── DocumentEngineBase.sol # Abstract base: ERC-1643 document logic + storage, +│ # both management paths, batch functions, modifiers, +│ # and the ABSTRACT _authorize* hooks (no access control) +├── DocumentEngine.sol # Deployment #1: role-based access control +│ # (AccessControlEnumerable, DOCUMENT_MANAGER_ROLE, +│ # _authorizeDocumentManagement, hasRole), ERC-2771, +│ # supportsInterface, constructor +├── DocumentEngineOwnable.sol # Deployment #2: Ownable2Step (single owner) instead of +│ # roles; document mgmt + binding are owner-only +├── DocumentEngineInvariant.sol # Non-specification errors ONLY (InvalidInputLength, +│ # AdminWithAddressZeroNotAllowed). Every spec error is +│ # declared by its own interface — see the note below. +│ # NO access-control specifics +├── interfaces/ +│ ├── IERC8303.sol # ERC-8303 "Contract Version" interface (id 0x54fd4d50) +│ ├── IERC1643MultiDocument.sol # Multi-token ERC-1643 extension (address-scoped fns + +│ │ # DocumentUpdatedForSubject / DocumentRemovedForSubject + +│ │ # MultiDocumentInvalidSubject) +│ └── ITokenBinding.sol # Shared binding surface: bindToken / unbindToken / +│ # isTokenBound + TokenBindingSet + TokenBindingInvalidToken +└── modules/ + ├── VersionModule.sol # Version module: implements ERC-8303 version() + ERC-165, + │ # holds the VERSION constant (currently "0.4.0") + └── TokenBindingModule.sol # Shared token-binding allowlist (ITokenBinding) + NotBoundToken; + # wires the bound-token hook; used by both deployments + +script/ +├── DeployDocumentEngine.s.sol # Deploy role-based DocumentEngine (env: DOCUMENT_ENGINE_ADMIN, +│ # DOCUMENT_ENGINE_FORWARDER); run()=env, deploy(admin,fwd)=testable +└── DeployDocumentEngineOwnable.s.sol # Deploy Ownable variant (env: DOCUMENT_ENGINE_OWNER, _FORWARDER) + +test/ +├── DocumentEngine.t.sol # Foundry tests: deploy, access control, admin + bound-token +│ # paths, batch ops (incl. name==0 / missing-doc guards), +│ # ERC-8303 + interface discovery, event emission (asserts the +│ # base events are NOT emitted), msg.sender-scoped reads, +│ # enumeration, fuzz round-trip/isolation, CMTAT integration +│ # (CMTATDocumentEngineMock), flexible-auth override (OpenDocumentEngine) +├── DocumentEngineOwnable.t.sol # Tests for the Ownable2Step deployment (owner path, +│ # token binding, two-step ownership, ERC-8303) +└── Deploy.t.sol # Tests for the deployment scripts (deploy() state + run() env) +``` + +**Contract split (CMTAT module/deployment pattern):** `DocumentEngineBase` holds +the document logic and abstract `_authorize*` hooks; each deployment contract +supplies the concrete access control. There are two deployments — +`DocumentEngine` (role-based, `AccessControlEnumerable`) and +`DocumentEngineOwnable` (`Ownable2Step`). Add new management logic in the base; +change *who* is authorized in a deployment (implement the `_authorize*` hooks). + +Other important files: + +- `foundry.toml` — solc `0.8.34`, `evm_version = prague` (required by CMTAT v3). + Sources declare `pragma solidity ^0.8.24` — the real `src/` floor, set by OpenZeppelin's + `AccessControlEnumerable`/`EnumerableSet` and CMTAT's `draft-IERC1643` since `v3.3.0-rc3`. + Building the tests needs `≥ 0.8.27` (CMTAT's `require(cond, CustomError())` is via-ir-only + before then). Keep the pragma honest: if a dependency raises its floor, raise ours to match. +- `remappings.txt` — `CMTAT/`, `RuleEngine/`, `OZ/`, `@openzeppelin/contracts-upgradeable/`. +- `CHANGELOG.md` — semver history; update on every release (current: `v0.4.0`). +- `ERC-1643-proposition.md` — proposed optional multi-token events / extension. +- `README.md` — **short** entry point only: what the engine is, quick start, the two management + paths, the CMTAT wiring, the two integrator caveats, deploy. Keep it short; new prose belongs in + the full document. +- `doc/README.md` — the specification / full reference (Surya schema, ERC-165 rationale, version + compatibility matrix, tooling). This is where the old root README moved. +- `doc/img/` — PlantUML **sources** (`*.puml`) plus their rendered `*.png`. Five diagrams, split by + audience: `cmtat-write-simple` / `cmtat-read-simple` are the **short** pair, used in *both* + `README.md` and `doc/README.md`; `cmtat-integration-architecture` (topology), + `documentengine-contract-structure` (inheritance) and `cmtat-integration-sequence` (full call + flow with every revert branch) belong to `doc/README.md` only — keep them out of the root README. + One diagram, one job: when a schema needs a legend to stay legible, split it instead. Only images + are embedded, never the source. Re-render with `plantuml -tpng doc/img/.puml`, and look + at the PNG: PlantUML draws syntax/deprecation warnings *into* the image and still exits 0. +- `doc/` — Surya output in `doc/surya/{surya_graph,surya_inheritance,surya_report}`, one file per + `.sol` in `src/` (9 each), regenerated by the three scripts in `doc/script/` — run them from that + directory, **graph first** (it creates the scratch `docOut/`; the report script's `mkdir` lacks + `-p`). Patch `surya/lib/graph.js` before regenerating or every contract calling `super.()` + yields a silent 0-byte PNG; see the Surya section in `doc/README.md`. Also coverage, and + `doc/audits/` — the security overview (`AUDIT_OVERVIEW.md`) plus versioned + static-analysis output under `doc/audits/tools/vX.Y.Z//`, each with a + `*-report.md` (summary table prepended) and a `*-report-feedback.md` triaging + every finding, plus a `claude/` section holding the AI-assisted code-quality review. + Both Aderyn `0.6.5` (0 High · 6 Low) and Slither `0.11.5` + (0 High · 0 Med · 0 Low · 2 Info) were run for `v0.4.0` — nothing to fix in either. + Slither's dependency filter must be `lib` (Foundry layout); `--filter-paths` fails + open, so an entry matching nothing silently pulls the vendored tree into scope. + `doc/audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md` is the code-quality review (not a security audit) — read + its "left as is" rows before proposing an optimisation: `unchecked {++i}` (0 gas on solc + 0.8.34), `string calldata` on the admin `setDocument` (49 gas *worse*), and extracting the + duplicated ERC-2771 overrides (impossible — C3 linearization) are all measured dead ends. +- **Open items live in `doc/audits/AUDIT_OVERVIEW.md`** under *Known open items*, with stable + `OPEN-n` ids that the audit reports cite. There is no `IMPROVEMENT.md` — it was folded in. + Update that table when an item is fixed (move the record to `CHANGELOG.md`) or when review + surfaces a new one; keep the existing ids stable so the citations stay valid. +- `lib/` — submodules: `CMTAT`, `RuleEngine`, `openzeppelin-contracts(-upgradeable)`, `forge-std`. + +## Dependencies (tested versions) + +- CMTAT `v3.3.0-rc3`, RuleEngine `v3.0.0-rc5` (binding-pattern reference only; compliance module not reused) +- OpenZeppelin Contracts / Contracts Upgradeable `v5.7.0` +- Solidity `0.8.34`, Foundry + +## Common commands + +```bash +forge build # compile +forge test # run the test suite +forge fmt # format +forge test --gas-report +``` + +## Conventions + +- The `VERSION` constant (in `src/modules/VersionModule.sol`, exposed via + ERC-8303 `version()`) must match the latest `CHANGELOG.md` entry on release. +- Bump `MAJOR` on incompatible proxy-storage / external-library or API changes, + `MINOR` for backward-compatible features, `PATCH` for backward-compatible fixes. +- A bound token can only ever affect its **own** document namespace — never break + that isolation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a037f33..0861887 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ There are many ways to contribute to CMTAT Contracts. ## Opening an issue -You can [open an issue] to suggest a feature, a difficulty you have or report a minor bug. For serious bugs in an audited version please do not open an issue, instead refer to our [security policy] for appropriate steps. See [SECURITY.md](./SECURITY.MD). +You can [open an issue](https://github.com/CMTA/DocumentEngine/issues) to suggest a feature, a difficulty you have or report a minor bug. For serious bugs in an audited version please do not open an issue, instead refer to our [security policy](./SECURITY.md) for appropriate steps. Before opening an issue, be sure to search through the existing open and closed issues, and consider posting a comment in one of those instead. diff --git a/README.md b/README.md index 380053e..02920bc 100644 --- a/README.md +++ b/README.md @@ -1,220 +1,121 @@ -# DocumentEngine (ERC-1643) +# DocumentEngine (ERC-1643) -> This project has not been audited yet, please use at your own risk. For any questions, please contact [admin@cmta.ch](mailto:admin@cmta.ch). -> +A standalone contract that stores **[ERC-1643](https://github.com/ethereum/EIPs/issues/1643) documents on-chain on behalf of other contracts** — typically [CMTAT](https://github.com/CMTA/CMTAT) tokens. One engine serves a whole fleet: each subject gets its own namespace, keyed by its address, and can never reach another's. -The `DocumentEngine` is an external contract to manage documents through [*ERC-1643*](https://github.com/ethereum/EIPs/issues/1643), a standard proposition to manage document on-chain. This standard is notably used by [ERC-1400](https://github.com/ethereum/eips/issues/1411) from Polymath. +A document is `{ string uri, bytes32 documentHash, uint256 lastModified }`, addressed by a `bytes32` name. -The documentEngine is planned to be used by other smart contract,e.g CMTAT token, to store documents on their behalf. +Why use an external engine rather than storing documents in the token: -The ERC-1643 defines a document with three attributes: +- keeps the token's bytecode small; +- lets one operator manage documents for many tokens; +- documents can be updated without touching the token. -- A short name (represented as a `bytes32`) -- A generic URI (represented as a `string`) that could point to a website or other document portal. -- The hash of the document contents associated with it on-chain. +**Specification and full reference: [`doc/README.md`](./doc/README.md).** -A smart contract needs only to implement two functions from this standard, available in the interface [IERC1643](./contracts/interfaces/engined/draft-IERC1643.sol) to get the documents from the documentEngine. +> This project has not been audited yet, please use at your own risk. For any questions, please contact [admin@cmta.ch](mailto:admin@cmta.ch). -```solidity -interface IERC1643 { -function getDocument(bytes32 _name) external view returns (string memory , bytes32, uint256); -function getAllDocuments() external view returns (bytes32[] memory); -} -``` +## Quick start -Use an external contract for your smart contract provides two advantages: +```bash +git clone --recurse-submodules https://github.com/CMTA/DocumentEngine +cd DocumentEngine +forge build +forge test +``` -- Reduce code size of your smart contract -- Allow to manage documents for several different smart contracts +Requires [Foundry](https://getfoundry.sh) and Solidity `0.8.34` (`evm_version = prague`). Sources declare `pragma ^0.8.24`; building the tests needs `≥ 0.8.27`. -Warning: +## Two ways to manage documents -Since this engine allows to set documents for several different smart contracts, the functions to set documents take one supplementary arguments than defined in the ERC-1643. +Both are active at once. -IERC1643 +**Admin path** — a document manager writes for *any* subject, passing the address explicitly: ```solidity -function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external; +documentEngine.setDocument(address(token), name, uri, documentHash); +documentEngine.removeDocument(address(token), name); ``` -DocumentEngine +**Bound-token path** — a *bound* token manages its **own** documents through the standard single-argument ERC-1643 functions (`msg.sender` is the subject): ```solidity -function setDocument(address smartContract,bytes32 name_,string memory uri_, bytes32 documentHash_) +documentEngine.bindToken(address(token)); // once, by the document manager +// then, called by the token itself: +documentEngine.setDocument(name, uri, documentHash); ``` +Binding is a single allowlist shared by both deployments — **not** a role. +## Two deployments -## Schema - -### Inheritance - -![surya_inheritance_DocumentEngine.sol](./doc/surya/surya_inheritance/surya_inheritance_DocumentEngine.sol.png) - - - -### Graph - -![surya_graph_DocumentEngine.sol](./doc/surya/surya_graph/surya_graph_DocumentEngine.sol.png) - - - -![surya_graph_DocumentEngineInvariant.sol](./doc/surya/surya_graph/surya_graph_DocumentEngineInvariant.sol.png) - -## Surya Description Report - -### Contracts Description Table - -| Contract | Type | Bases | | | -| :----------------: | :------------------: | :----------------------------------------------: | :------------: | :-----------: | -| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | -| | | | | | -| **DocumentEngine** | Implementation | IERC1643, DocumentEngineInvariant, AccessControl | | | -| └ | | Public ❗️ | 🛑 | NO❗️ | -| └ | setDocument | Public ❗️ | 🛑 | onlyRole | -| └ | removeDocument | External ❗️ | 🛑 | onlyRole | -| └ | batchSetDocuments | External ❗️ | 🛑 | onlyRole | -| └ | batchSetDocuments | External ❗️ | 🛑 | onlyRole | -| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyRole | -| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyRole | -| └ | getDocument | External ❗️ | | NO❗️ | -| └ | getDocument | External ❗️ | | NO❗️ | -| └ | getAllDocuments | External ❗️ | | NO❗️ | -| └ | getAllDocuments | External ❗️ | | NO❗️ | -| └ | hasRole | Public ❗️ | | NO❗️ | -| └ | _getDocument | Internal 🔒 | | | -| └ | _removeDocumentName | Internal 🔒 | 🛑 | | -| └ | _removeDocument | Internal 🔒 | 🛑 | | -| └ | _setDocument | Internal 🔒 | 🛑 | | - - -### Legend - -| Symbol | Meaning | -| :----: | ------------------------- | -| 🛑 | Function can modify state | -| 💵 | Function is payable | - - - -## Gasless support (ERC-2771) - -The DocumentEngine supports client-side gasless transactions using the [Gas Station Network](https://docs.opengsn.org/#the-problem) (GSN) pattern, the main open standard for transfering fee payment to another account than that of the transaction issuer. The contract uses the OpenZeppelin contract `ERC2771ContextUpgradeable`, which allows a contract to get the original client with `_msgSender()` instead of the fee payer given by `msg.sender` while allowing upgrades on the main contract (see *Deployment via a proxy* above). - -At deployment, the parameter `forwarder` inside the constructor has to be set with the defined address of the forwarder. Please note that the forwarder can not be changed after deployment. - -Please see the OpenGSN [documentation](https://docs.opengsn.org/contracts/#receiving-a-relayed-call) for more details on what is done to support GSN in the contract. - - +| Contract | Access control | Document management + binding restricted to | +| --- | --- | --- | +| `DocumentEngine` | `AccessControlEnumerable` | `DOCUMENT_MANAGER_ROLE` | +| `DocumentEngineOwnable` | `Ownable2Step` | `owner` | -## Dependencies +They share all the logic (`DocumentEngineBase`, `TokenBindingModule`, `VersionModule`) and differ only in *who* is authorized. Authorization goes through an overridable `internal virtual` hook, so a subclass changes who may write without touching the management functions. -The toolchain includes the following components, where the versions are the latest ones that we tested: +## Using it with a CMTAT token -- Foundry -- Solidity 0.8.26 (via solc-js) -- OpenZeppelin Contracts (submodule) [v5.0.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.2) -- Tests - - [CMTAT v2.5.0-rc0](https://github.com/CMTA/CMTAT/releases/tag/v2.5.0-rc0) - - OpenZeppelin Contracts Upgradeable(submodule) [v5.0.2](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.0.2) +Two independent steps, and it is easy to do only one: `bindToken` on the **engine** authorises the token, `setDocumentEngine` on the **token** tells it where to forward. Bind without wiring and the token has nowhere to send; wire without binding and the forwarded call reverts `NotBoundToken`. -## Tools - -### Prettier - -```bash -npx prettier --write --plugin=prettier-plugin-solidity 'src/**/*.sol' -``` - -### Slither +```solidity +documentEngine.bindToken(address(token)); // engine's document manager +token.setDocumentEngine(documentEngine); // token's document manager -```bash -slither . --checklist --filter-paths "openzeppelin-contracts|test|CMTAT|forge-std" > slither-report.md +token.setDocument(bytes32("prospectus"), "ipfs://...", keccak256(bytes(content))); ``` -### Surya - -See [./doc/script](./doc/script) +### Writing a document -### Foundry +![Writing a document through a CMTAT token](./doc/img/cmtat-write-simple.png) -Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. +### Reading a document -Foundry consists of: +![Reading a document from a CMTAT token or the engine](./doc/img/cmtat-read-simple.png) -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +For the full flow — the wiring steps, every revert branch, and the admin path — see [the detailed sequence](./doc/README.md#integration-with-cmtat) in the documentation. -#### Documentation +## Two things integrators must know -https://book.getfoundry.sh/ +**Read through the subject, not the engine.** As the read diagram shows, the single-argument `getDocument(name)` is `msg.sender`-scoped, so a third party calling it on the engine reads *its own* — empty — namespace, with no revert. Read through the token, or use the address-scoped `getDocument(subject, name)`. -#### Usage +**The admin path emits nothing on the subject.** A write sent straight to the engine (`setDocument(subject, …)`, rather than through the token as above) has no execution point in the token, so only the engine's `DocumentUpdatedForSubject` fires. When consumers watch the token's address, use the bound-token path. Tracked as `OPEN-2` in [`doc/audits/AUDIT_OVERVIEW.md`](./doc/audits/AUDIT_OVERVIEW.md). -##### Coverage +## Deploy ```bash -$ forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage -``` - -##### Gas report +# role-based +DOCUMENT_ENGINE_ADMIN=0x… DOCUMENT_ENGINE_FORWARDER=0x… \ + forge script script/DeployDocumentEngine.s.sol --rpc-url $RPC_URL --broadcast -```bash -$ forge test --gas-report +# owner-based +DOCUMENT_ENGINE_OWNER=0x… DOCUMENT_ENGINE_FORWARDER=0x… \ + forge script script/DeployDocumentEngineOwnable.s.sol --rpc-url $RPC_URL --broadcast ``` -##### Build +The forwarder enables ERC-2771 gasless calls and is **immutable**; pass `address(0)` to disable. Use a keystore or hardware wallet for real deployments, not a raw private key. -```shell -$ forge build -``` +## More -##### Test +| | | +| --- | --- | +| Specification / full reference | [`doc/README.md`](./doc/README.md) | +| Security overview & open items | [`doc/audits/AUDIT_OVERVIEW.md`](./doc/audits/AUDIT_OVERVIEW.md) | +| Static analysis & code-quality reports | [`doc/audits/tools/`](./doc/audits/tools) | +| Release history | [`CHANGELOG.md`](./CHANGELOG.md) | +| Reporting a vulnerability | [`SECURITY.md`](./SECURITY.md) | +| Diagrams (Surya, PlantUML) | [`doc/surya/`](./doc/surya), [`doc/img/`](./doc/img) | -```shell -$ forge test -``` - -##### Format +## Compatibility -```shell -$ forge fmt -``` +| DocumentEngine | Compatible CMTAT | Tested against | +| -------------- | ---------------- | -------------- | +| **v0.4.0** (current) | `v3.3.0-rc2` – `v3.3.0-rc3` | v3.3.0-rc3 | +| v0.3.0 and earlier | v2.5.0-rc0 | v2.5.0-rc0 | -##### Gas Snapshots - -```shell -$ forge snapshot -``` - -##### Anvil - -```shell -$ anvil -``` - -##### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -##### Cast - -```shell -$ cast -``` - -##### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +The range is closed at both ends on purpose. CMTAT's `IERC1643` changed shape inside a single minor line — `getDocument` returns a `Document` struct up to `v3.3.0-rc1` and the three flat values from `v3.3.0-rc2` — so anything below rc2 does not compile, and a newer CMTAT is not assumed compatible until it has been tested. Full detail, including the Solidity and OpenZeppelin columns: [version compatibility](./doc/README.md#version-compatibility). ## Intellectual property -The code is copyright (c) Capital Market and Technology Association, 2018-2024, and is released under [Mozilla Public License 2.0](https://github.com/CMTA/CMTAT/blob/master/LICENSE.md). +The code is copyright (c) Capital Market and Technology Association, 2018-2026, and is released under [Mozilla Public License 2.0](https://github.com/CMTA/CMTAT/blob/master/LICENSE.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5a3e608 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +To report a security vulnerability in this project, please see instruction in [CMTA/CMTAT/SECURITY.md](https://github.com/CMTA/CMTAT/blob/master/SECURITY.md) diff --git a/doc/ERCSpecification/erc-1643.md b/doc/ERCSpecification/erc-1643.md new file mode 100644 index 0000000..83af8c6 --- /dev/null +++ b/doc/ERCSpecification/erc-1643.md @@ -0,0 +1,180 @@ +--- +eip: 1643 +title: Document Management for Security Tokens +description: Interface to attach, update, remove, and enumerate legal or operational documents for token contracts. +author: Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep), Ryan Sauge (@rya-sge) +discussions-to: https://ethereum-magicians.org/t/erc-1643-document-management-standard-erc-1400/27437 +status: Draft +type: Standards Track +category: ERC +created: 2018-09-09 +--- + +## Abstract + +This ERC defines a standard interface for associating documents with a token contract and for notifying off-chain systems when those documents change. Documents can represent legal agreements, offering materials, disclosures, or other issuer-provided references needed for security token operations. + +## Motivation + +Security tokens commonly represent assets with legal rights and obligations that depend on external documents. Wallets, exchanges, custodians, and compliance tools need a predictable way to discover those documents and track updates. + +Without a standard, each implementation exposes different storage and retrieval methods, increasing integration cost and operational risk. A common interface allows ecosystem participants to read and monitor document metadata consistently. + +Within security token frameworks, the document management component highlights that security tokens usually have associated documentation such as offering documents and legend details. The ability to set, remove, and retrieve these documents, with events emitted on those actions, allows investors and integrators to remain up to date. + +This ERC intentionally does not define an on-chain mechanism for investors to attest they have read or agreed to any document. + +Although originally designed for security tokens built on [ERC-20](./eip-20.md) as part of a broader real-world asset token suite, this interface is not restricted to that context. It can be adopted by any token standard, including [ERC-721](./eip-721.md) non-fungible tokens and [ERC-1155](./eip-1155.md) multi-tokens, as well as by decentralized applications, vaults, and any other on-chain product that requires structured document management. + +Historically, this proposal was authored as part of a broader security token standards suite that had not yet been merged in this repository when this proposal was added. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", and "MAY" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +Implementations MUST support querying and subscribing to updates on any relevant documentation for the security. + +A document entry is identified by a name (`bytes32`) and stores: + +- A URI (`string`) pointing to the document location. +- A content hash (`bytes32`) for integrity checks. +- A last-modified timestamp (`uint256`) set when the entry is written. + +### Interface + +```solidity +/// @title IERC1643 Document Management +interface IERC1643 { + /// @notice Emitted when a document is created or updated. + /// @param name Identifier of the document. + /// @param uri Document location. + /// @param documentHash Hash of the document contents. + event DocumentUpdated(bytes32 indexed name, string uri, bytes32 documentHash); + + /// @notice Emitted when a document is removed. + /// @param name Identifier of the document. + /// @param uri Document location at the time of removal. + /// @param documentHash Hash of the document contents at the time of removal. + event DocumentRemoved(bytes32 indexed name, string uri, bytes32 documentHash); + + /// @notice Reverts when `setDocument` is called with `name == bytes32(0)`. + error ERC1643InvalidName(); + + /// @notice Reverts when `removeDocument` is called for a missing document. + error ERC1643MissingDocument(); + + /// @notice Creates or updates a document entry. + /// @dev MUST emit `DocumentUpdated` on success. + /// @param name Identifier of the document. + /// @param uri Document location. + /// @param documentHash Hash of the document contents. + function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external; + + /// @notice Removes an existing document entry. + /// @dev MUST emit `DocumentRemoved` on success. + /// @param name Identifier of the document to remove. + function removeDocument(bytes32 name) external; + + /// @notice Returns metadata for a document identified by `name`. + /// @param name Identifier of the document. + /// @return uri Document location. + /// @return documentHash Hash of the document contents. + /// @return lastModified Last update timestamp. + function getDocument(bytes32 name) + external + view + returns (string memory uri, bytes32 documentHash, uint256 lastModified); + + /// @notice Returns all document names currently tracked by the contract. + /// @return documentNames Names of all documents that are currently set. + function getAllDocuments() external view returns (bytes32[] memory documentNames); +} +``` + +### Interface Detection ([ERC-165](./eip-165.md)) + +Implementations SHOULD support ERC-165 interface detection. + +When ERC-165 is implemented, `supportsInterface` SHOULD return `true` for `type(IERC1643).interfaceId` and for the ERC-165 interface id. + +### Function Requirements + +- `getDocument`: + - MUST return the latest values for the provided document name. + - MUST return empty values when the entry does not exist (`""`, `bytes32(0)`, `0`). + - MUST NOT revert solely because the entry does not exist. + - Implementations SHOULD ensure that a stored entry always has a non-zero `lastModified`, so that `lastModified == 0` identifies an absent entry. Since `uri` and `documentHash` MAY both be empty for a stored document, they cannot distinguish an absent entry from one stored with empty metadata, and `lastModified` is the only value that can. Integrators should treat this as advisory rather than guaranteed when interacting with deployments predating this guidance. + +- `setDocument`: + - MUST create a new entry when `name` is not present. + - MUST overwrite the existing entry when `name` already exists. + - MUST update the stored last-modified timestamp. + - MUST emit `DocumentUpdated` after state changes. + - MUST revert if the update cannot be persisted. + - SHOULD revert when `name == bytes32(0)` to avoid ambiguous/default-key usage. + - `uri` and `documentHash` MAY be empty (`""` and `bytes32(0)`), depending on issuer workflow and document lifecycle stage. + - Implementations MAY decide to reject empty `uri` and/or empty `documentHash` based on policy requirements. + - Implementations SHOULD use the custom error defined in the interface (`ERC1643InvalidName()`) when rejecting `name == bytes32(0)`. + - Implementations MAY use different error names/signatures than those shown in this specification. + +- `removeDocument`: + - MUST remove the entry identified by `name`. + - MUST emit `DocumentRemoved` with the removed metadata. + - MUST revert if removal cannot be completed. + - Implementations SHOULD use the custom error defined in the interface (`ERC1643MissingDocument()`) when the named document does not exist. + - Implementations MAY use different error names/signatures than those shown in this specification. + +- `getAllDocuments`: + - MUST include every document name added by `setDocument` and not removed by `removeDocument`. + - MUST NOT include removed document names. + - The order of the returned names is unspecified. Removing a document MAY change the position of unrelated names, so consumers MUST NOT rely on a stable ordering, and MUST NOT treat a change in position as a change to a document. + +## Rationale + +- The standard uses `bytes32` names to keep keys compact and deterministic, while leaving naming conventions to implementations. A URI-based pointer is used instead of on-chain document storage to avoid high gas costs and to support existing off-chain document systems. +- Including a document hash enables clients to verify that fetched off-chain content matches issuer-published metadata. +- Emitting update and removal events supports indexing and near-real-time monitoring without repeated full-state polling. +- While a human-readable document title cannot always be represented directly in `bytes32` without hashing or canonicalization, `bytes32` remains practical for on-chain identifiers because fixed-size values can be compared directly (`a == b`). By contrast, `string` comparisons generally require hashing (for example, `keccak256(bytes(s))`), which increases contract code size and gas usage when repeated comparisons are needed on-chain, such as locating and removing a document name from an array. + +## Backwards Compatibility + +This ERC is additive and does not alter base token transfer semantics. It can be implemented alongside existing token standards and permissioning systems without changing their core behavior. + +## Test Cases + +Implementations should verify at least the following: + +- Adding a new document and reading it through `getDocument`. +- Reading a name that was never set, confirming `getDocument` returns empty values (`""`, `bytes32(0)`, `0`) and does not revert. +- Updating an existing document and validating changed URI/hash/timestamp. +- Removing a document and ensuring it is no longer returned by `getAllDocuments`. +- Emission of `DocumentUpdated` on create/update and `DocumentRemoved` on delete. +- Enumeration consistency after multiple add/update/remove operations. + +## Reference Implementation + +The interface is provided in [the reference interface](../assets/eip-1643/src/erc-1643/IERC1643.sol). A reusable abstract module implementing the full interface is provided in [the reference module](../assets/eip-1643/src/erc-1643/ERC1643.sol). + +Example integrations attaching the module to [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md) tokens are provided in [the ERC-20 example](../assets/eip-1643/src/ERC20DocumentToken.sol) and [the ERC-721 example](../assets/eip-1643/src/ERC721DocumentToken.sol). + +These examples use the OpenZeppelin library and restrict document mutation to the contract owner. They are provided for educational purposes only and have not been audited. + +The module maintains: + +- Mapping from `bytes32` name to document metadata. +- Array/set for enumeration of active names. +- Index tracking to support O(1) removals from the enumeration set. + +## Security Considerations + +- Document URIs may reference mutable off-chain content. Consumers are strongly encouraged to verify content using the published `documentHash` and trusted retrieval channels. +- Implementations should protect `setDocument` and `removeDocument` with appropriate authorization, otherwise unauthorized actors can modify legal or operational references. +- Applications should treat event streams as advisory and reconcile against on-chain state when correctness is critical. +- `getAllDocuments` returns the entire set of names in a single call, and its response grows with the number of stored documents. It is the only enumeration path defined here, so a contract holding a large document set may produce a call that exceeds the gas limit an `eth_call` provider applies, leaving no standard way to enumerate. Implementations expecting large sets should consider exposing an additional paginated accessor alongside this interface. +- Document names may not always fit cleanly into `bytes32`, especially for long legal titles. Implementations should avoid lossy truncation of human-readable names; using a deterministic hash-based identifier (for example, the document content hash or a hash of a canonical full title) as the `bytes32` name is a safer alternative. +- The custom errors `ERC1643InvalidName()` and `ERC1643MissingDocument()` are defined in this interface but were absent from the earlier draft text of this proposal. Older implementations may not define these errors and may instead revert with strings or implementation-specific error patterns. Integrators should not assume all [ERC-1643](./eip-1643.md) contracts expose identical revert data. +- ERC-165 interface detection was also not part of the earlier ERC-1643 draft text. Older implementations may not expose `supportsInterface` for ERC-165 or `IERC1643`, so integrators should treat ERC-165 support as optional when interacting with legacy deployments. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/doc/ERCSpecification/erc-8303-draft.md b/doc/ERCSpecification/erc-8303-draft.md new file mode 100644 index 0000000..15be396 --- /dev/null +++ b/doc/ERCSpecification/erc-8303-draft.md @@ -0,0 +1,131 @@ +--- +eip: 8303 +title: Contract Version +description: Interface for exposing a contract implementation version string +author: Ryan Sauge (@rya-sge) +discussions-to: https://ethereum-magicians.org/t/erc-8303-contract-version/28795 +status: Draft +type: Standards Track +category: ERC +created: 2026-02-12 +--- + +## Abstract + +This ERC defines a minimal interface to expose a contract version string through a standardized `version()` view function. The design is based on the version pattern used by [ERC-3643](./eip-3643.md), while remaining token-agnostic and applicable to other smart contract domains, including DeFi applications such as lending protocols. + +## Motivation + +Integrators frequently need a simple, on-chain way to identify which contract implementation they interact with. A standardized version function improves: + +- integration safety (feature gating by version), +- operations (faster incident triage), +- governance and migration tracking (upgrade visibility), +- ecosystem tooling interoperability. + +It is also useful for end-users, developers, and security auditors to identify which version of a codebase is currently used by a deployed contract. + +The same requirement appears in permissioned token systems ([ERC-3643](./eip-3643.md)) and in DeFi systems where contracts evolve over time. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). + +### Interface + +```solidity +interface IERC8303 { + /// @notice Returns the implementation version string. + /// @return The version value (for example "1.0.0"). + function version() external view returns (string memory); +} +``` + +### Required Behavior + +1. **Version read** + - `version()` MUST be a view function. + - `version()` MUST NOT revert under normal operation. + - `version()` MUST return a non-empty string. + +2. **Version meaning** + - Returned values SHOULD be stable and machine-comparable by off-chain tooling. + - Returned values SHOULD follow a Semantic Versioning 2.0.0-like format: `MAJOR.MINOR.PATCH` using decimal integers (for example `1.0.0`, `3.2.1`). + - The canonical recommended pattern is `^[0-9]+\.[0-9]+\.[0-9]+$`. + - Implementations MAY define their own versioning policy, but SHOULD document it publicly. + +3. **Deployment model compatibility** + - This interface is compatible with immutable deployments and proxy-based upgradeable deployments. + - In upgradeable systems, `version()` SHOULD reflect the active implementation seen by users and integrators. + +### [ERC-165](./eip-165.md) + +Implementations SHOULD support [ERC-165](./eip-165.md) interface discovery for this interface. + +If an implementation supports [ERC-165](./eip-165.md), `supportsInterface(type(IERC8303).interfaceId)` MUST return `true`. + +- The interface id for `IERC8303` is `0x54fd4d50`. + +### Compatibility Note for ERC-3643 Integrations + +Integrators MAY treat legacy ERC-3643 token contracts exposing a compatible `version()` function as implementing this ERC even if they do not advertise ERC-165 support. + +## Rationale + +- **Minimal scope**: A single function maximizes adoption and keeps gas/runtime complexity negligible. +- **ERC-3643 alignment**: Reuses a proven pattern already used in regulated token implementations. +- **Token-agnostic design**: The interface applies to token contracts and non-token contracts alike. +- **Optional ERC-165**: ERC-165 support is recommended but not required, lowering the adoption barrier for contracts that do not implement interface discovery. When ERC-165 is supported, advertising this interface is mandatory to ensure consistent detection by integrators. +- **`string` over `bytes32`**: A human-readable string is preferred to a fixed-size bytes32 for legibility in explorers and tooling, at the cost of marginally higher gas for the return value. + +## Backwards Compatibility + +This ERC is fully additive. Contracts already exposing `version()` are naturally compatible if they match the interface signature. + +## Test Cases + +The following test cases apply to any conforming implementation. + +1. `version()` MUST NOT revert. +2. `version()` MUST return a non-empty string. +3. `version()` MUST return the version string declared by the implementation (e.g. `"1.0.0"`). +4. If the contract supports [ERC-165](./eip-165.md), `supportsInterface(0x54fd4d50)` MUST return `true`. +5. If the contract supports [ERC-165](./eip-165.md), `supportsInterface(0xffffffff)` MUST return `false`. + +## Reference Implementation + +Reference implementations are provided in the assets folder: the [interface](../assets/erc-8303/src/IERC8303.sol) and a [base implementation](../assets/erc-8303/src/ERC8303.sol), along with usage examples for [ERC-20](../assets/erc-8303/src/examples/ERC20VersionedExample.sol) and [ERC-721](../assets/erc-8303/src/examples/ERC721VersionedExample.sol) tokens. These examples are provided for educational purposes only and are not audited. + +```solidity +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.0; + +import "./IERC8303.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +contract ERC8303Example is IERC8303, ERC165 { + function version() external pure override returns (string memory) { + return "1.0.0"; + } + + function supportsInterface(bytes4 interfaceId) + public + view + override + returns (bool) + { + return interfaceId == type(IERC8303).interfaceId + || super.supportsInterface(interfaceId); + } +} +``` + +## Security Considerations + +- `version()` is metadata and must not be used as a sole authorization primitive. +- In upgradeable systems, governance controls remain the trust anchor; version reporting does not prevent malicious upgrades. +- Integrators should combine version checks with other trust signals (governance model, audits, deployment provenance). + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/doc/ERCSpecification/erc-draft_multi_document_management.md b/doc/ERCSpecification/erc-draft_multi_document_management.md new file mode 100644 index 0000000..47bc57c --- /dev/null +++ b/doc/ERCSpecification/erc-draft_multi_document_management.md @@ -0,0 +1,268 @@ +--- +title: Multi-Subject Document Management +description: Interface for a contract that attaches, updates, removes, and enumerates documents on behalf of multiple subject contracts. +author: Ryan Sauge (@rya-sge) +discussions-to: https://ethereum-magicians.org/t/erc-1643-document-management-standard-erc-1400/27437 +status: Draft +type: Standards Track +category: ERC +created: 2026-07-28 +requires: 165, 1643 +--- + +## Abstract + +This ERC defines an interface for a contract that stores and manages documents on behalf of **several other contracts**, called *subjects*. Every function is scoped by a `subject` address, and every event carries that address, so a single management contract can serve many subjects while remaining observable and operable per subject. + +It is a companion to [ERC-1643](./eip-1643.md), which defines the equivalent per-contract interface. A subject is not required to implement ERC-1643, or any other particular interface, to have documents managed on its behalf. + +## Motivation + +[ERC-1643](./eip-1643.md) associates documents with the contract that exposes the interface. Its `DocumentUpdated` and `DocumentRemoved` events carry only `(name, uri, documentHash)` — no address — so consumers attribute an event to the address that emitted it. This is unambiguous when a single contract both exposes the interface and stores its own documents. + +A common operational and gas optimization is to **delegate** document management to a separate contract. When that contract is dedicated to one subject, nothing new is needed: it effectively is that subject's ERC-1643 implementation, and its events are unambiguous. But when **one management contract serves many subjects**, the per-contract events break down — a consumer watching the management contract cannot tell which subject a change belongs to, and the management contract has no compliant way to report per-subject changes at all. + +Shared document management is worth supporting directly. An issuer operating many tokens, funds, or vaults typically maintains one document library and one set of operators, and duplicating that storage and access-control logic into every subject contract is redundant and expensive. What the shared case needs is an address in the event and an address in the function signature. That is the whole of this proposal. + +The scope is deliberately broader than tokens. A subject is any contract that documents can belong to — an [ERC-20](./eip-20.md) or [ERC-721](./eip-721.md) token, an [ERC-1155](./eip-1155.md) multi-token, a vault, or any other on-chain product. Nothing in this interface inspects the subject or calls into it. + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +A document entry is identified by a `subject` (`address`) together with a name (`bytes32`), and stores: + +- A URI (`string`) pointing to the document location. +- A content hash (`bytes32`) for integrity checks. +- A last-modified timestamp (`uint256`) set when the entry is written. + +This is the [ERC-1643](./eip-1643.md) data model, extended with the `subject` address. Entries under different subjects are independent: a `name` written for one subject MUST NOT affect the entry stored under the same `name` for any other subject. + +### Interface + +This interface is declared **independently of `IERC1643`** — it does not inherit it — so a management contract can implement the address-scoped surface without being forced to implement the per-contract single-argument functions. + +A contract MAY implement both, in which case it implements, and advertises, each interface separately. + +Two of the three errors below, `ERC1643InvalidName` and `ERC1643MissingDocument`, are **the same errors defined by [ERC-1643](./eip-1643.md)** — same names, same signatures, hence the same 4-byte selectors — reused deliberately so that a caller sees identical revert data for identical conditions whether it is talking to an ERC-1643 contract or to a management contract. They are restated here rather than inherited because this interface does not inherit `IERC1643`; a contract implementing both obtains each error once, from `IERC1643`, and MUST NOT declare them twice. + +`MultiDocumentInvalidSubject` has no ERC-1643 counterpart: the condition it reports cannot arise there, because ERC-1643's `setDocument` has no `subject` argument — its subject is implicitly the contract itself, which is never the null address. + +```solidity +/// @title IERC1643MultiDocument Multi-Subject Document Management +interface IERC1643MultiDocument { + /// @notice Emitted when a document is created or updated for `subject`. + /// @param subject Address of the contract the document belongs to. + /// @param name Identifier of the document. + /// @param uri Document location. + /// @param documentHash Hash of the document contents. + event DocumentUpdatedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash); + + /// @notice Emitted when a document is removed for `subject`. + /// @param subject Address of the contract the document belonged to. + /// @param name Identifier of the document. + /// @param uri Document location at the time of removal. + /// @param documentHash Hash of the document contents at the time of removal. + event DocumentRemovedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash); + + /// @notice Reverts when `setDocument` or `removeDocument` is called with `subject == address(0)`. + /// @dev Specific to this proposal; has no ERC-1643 counterpart. + error MultiDocumentInvalidSubject(); + + /// @notice Reverts when `setDocument` is called with `name == bytes32(0)`. + /// @dev Same error as defined by ERC-1643, reused unchanged. + error ERC1643InvalidName(); + + /// @notice Reverts when `removeDocument` is called for a missing document. + /// @dev Same error as defined by ERC-1643, reused unchanged. + error ERC1643MissingDocument(); + + /// @notice Creates or updates a document entry for `subject`. + /// @dev MUST emit `DocumentUpdatedForSubject` on success. + /// @param subject Address of the contract the document belongs to. + /// @param name Identifier of the document. + /// @param uri Document location. + /// @param documentHash Hash of the document contents. + function setDocument(address subject, bytes32 name, string calldata uri, bytes32 documentHash) external; + + /// @notice Removes an existing document entry for `subject`. + /// @dev MUST emit `DocumentRemovedForSubject` on success. + /// @param subject Address of the contract the document belongs to. + /// @param name Identifier of the document to remove. + function removeDocument(address subject, bytes32 name) external; + + /// @notice Returns metadata for the document identified by `name` belonging to `subject`. + /// @param subject Address of the contract the documents belong to. + /// @param name Identifier of the document. + /// @return uri Document location. + /// @return documentHash Hash of the document contents. + /// @return lastModified Last update timestamp. + function getDocument(address subject, bytes32 name) + external + view + returns (string memory uri, bytes32 documentHash, uint256 lastModified); + + /// @notice Returns all document names currently tracked for `subject`. + /// @param subject Address of the contract the documents belong to. + /// @return documentNames Names of all documents that are currently set for `subject`. + function getAllDocuments(address subject) external view returns (bytes32[] memory documentNames); +} +``` + +### Function Requirements + +- `getDocument`: + - MUST return the latest values for the provided `subject` and `name`. + - MUST return empty values when the entry does not exist (`""`, `bytes32(0)`, `0`). + - MUST NOT revert solely because the entry does not exist. + +- `setDocument`: + - MUST create a new entry when `name` is not present for `subject`. + - MUST overwrite the existing entry when `name` already exists for `subject`. + - MUST update the stored last-modified timestamp. + - MUST emit `DocumentUpdatedForSubject` after state changes. + - MUST revert if the update cannot be persisted. + - SHOULD revert when `name == bytes32(0)`, using `ERC1643InvalidName()`. + - `uri` and `documentHash` MAY be empty (`""` and `bytes32(0)`), depending on issuer workflow and document lifecycle stage. Implementations MAY reject empty values based on policy requirements. + +- `removeDocument`: + - MUST remove the entry identified by `subject` and `name`. + - MUST emit `DocumentRemovedForSubject` with the removed metadata. + - MUST revert if removal cannot be completed. + - SHOULD revert when the named document does not exist for `subject`, using `ERC1643MissingDocument()`. + +- `getAllDocuments`: + - MUST include every name added for `subject` by `setDocument` and not removed by `removeDocument`. + - MUST NOT include removed names, or names belonging to any other subject. + +- `setDocument` and `removeDocument` SHOULD revert when `subject == address(0)`, since the null address is never a valid document subject, using `MultiDocumentInvalidSubject()`. + - Guarding the write path is sufficient: when `setDocument` rejects `subject == address(0)`, no document can exist under that subject, so `removeDocument` there already fails with `ERC1643MissingDocument()`. + +- Implementations MAY use different error names/signatures than those shown in this specification. + +### Authorization + +Implementations MUST authorize writes per `subject`, so that a caller cannot create, update, or remove documents for a `subject` it is not permitted to manage. A single management contract holding the document sets of unrelated subjects behind one address makes this the central security property of the proposal; see Security Considerations. + +### Interface Detection ([ERC-165](./eip-165.md)) + +Implementations SHOULD support ERC-165 interface detection. When ERC-165 is implemented, `supportsInterface` SHOULD return `true` for `type(IERC1643MultiDocument).interfaceId` and for the ERC-165 interface id. + +`type(IERC1643MultiDocument).interfaceId` is the XOR of only this interface's own address-scoped functions. Because this interface does not inherit `IERC1643`, and because Solidity excludes inherited selectors from `type(I).interfaceId` in any case, advertising it says nothing about whether the per-contract single-argument functions are implemented. Accordingly, a contract MUST return `true` for `type(IERC1643).interfaceId` only if it also implements those base functions; a management contract exposing only the address-scoped surface MUST NOT advertise `type(IERC1643).interfaceId`. + +Events are not part of any ERC-165 interface id, so `supportsInterface` reflects only which functions a contract implements, not whether it emits the events defined here. + +### Optional: Subject-Side Manager Discovery + +Nothing above lets a consumer that knows only a subject find the management contract holding that subject's documents; the address has to be learned out of band. Subjects MAY close this gap by implementing the following interface. It is **implemented by the subject, not by the management contract**, and is optional for both. + +```solidity +/// @title IMultiDocumentSubject Manager discovery (optional) +interface IMultiDocumentSubject { + /// @notice Emitted when the managing contract changes. + /// @param previousManager Address that previously managed this contract's documents. + /// @param newManager Address that manages this contract's documents from now on. + event DocumentManagerUpdated(address indexed previousManager, address indexed newManager); + + /// @notice Returns the contract managing this contract's documents. + /// @dev MUST NOT revert. MUST emit `DocumentManagerUpdated` when the returned value changes. + /// @return manager Address of the managing contract, or `address(0)` when documents are managed in-contract. + function documentManager() external view returns (address manager); +} +``` + +- `documentManager`: + - MUST return the address of the contract to which this contract's document management is delegated. + - MUST return `address(0)` when document management is not delegated. + - MUST NOT revert. +- A contract that changes the returned value MUST emit `DocumentManagerUpdated`, so a consumer that cached the address learns of the migration. Without this, discovery would be a one-shot read that silently goes stale. +- This interface is declared independently of both `IERC1643` and `IERC1643MultiDocument`, so its ERC-165 id is distinct and advertising it perturbs neither. A subject implementing it SHOULD return `true` for `type(IMultiDocumentSubject).interfaceId`. + +The returned address is an **assertion by the subject**, not a verified link: nothing requires the named contract to acknowledge the relationship, or to have any documents for that subject at all. Consumers MUST treat it as a discovery hint and not as evidence of authorization; see Security Considerations. + +### Relationship to [ERC-1643](./eip-1643.md) + +A subject need not implement ERC-1643. This section applies only when it does, and constrains such deployments; it places no requirement on ERC-1643 itself, which is unchanged by this proposal. + +#### Emission Responsibility + +ERC-1643's `DocumentUpdated` / `DocumentRemoved` carry no address, so consumers attribute them to the address that emitted them. Those events MUST therefore be emitted by the contract that exposes ERC-1643 to consumers — the address consumers are expected to subscribe to. When an ERC-1643 subject delegates to a management contract, the emitter MUST be chosen so per-contract observability is preserved: + +- A management contract **dedicated to a single** subject MAY be that subject's ERC-1643 implementation and MUST emit `DocumentUpdated` / `DocumentRemoved`; those events are unambiguous because only one subject is served. Such a contract does not need this proposal. +- A management contract **serving several** subjects MUST NOT report per-subject changes through ERC-1643's events, since those events cannot identify the subject. In this configuration each subject MUST emit ERC-1643's events for its own documents, and the management contract emits `DocumentUpdatedForSubject` / `DocumentRemovedForSubject` instead, as those carry the `subject` address. +- Implementations SHOULD NOT emit ERC-1643's events from **both** the subject and the management contract; it is redundant and wastes gas. + +#### Call Topology + +A contract can only emit an event in a transaction in which it executes. The requirement that each subject emit ERC-1643's events for its own documents therefore constrains how a write reaches the management contract, not only which contract is nominally responsible for emitting. + +A write that creates, updates, or removes a document for an ERC-1643 subject MUST include an execution point in that subject at which the event is emitted. Two topologies satisfy this: + +- **Subject-initiated.** The subject calls the management contract's address-scoped write and emits ERC-1643's event itself. The management contract emits the corresponding event defined here. +- **Manager-initiated with callback.** An authorized operator calls the management contract, which calls back into the subject through an implementation-defined permissioned hook; the subject emits ERC-1643's event. This proposal does not define the signature of that hook. + +A deployment in which an operator calls `setDocument(address subject, ...)` or `removeDocument(address subject, bytes32 name)` directly, with no execution point in the subject, does **not** satisfy ERC-1643's emission requirement for that subject: a consumer subscribed to the subject never observes the change, so the subject does not support subscribing to updates on its documentation and is not a conformant ERC-1643 implementation in that deployment, even though it exposes the ERC-1643 functions. Note this is a consequence of ERC-1643's own requirements, not an additional obligation imposed here. + +Accordingly, a management contract SHOULD restrict its address-scoped writes to callers for which one of the two topologies above holds — the subject itself, or an operator whose write path calls back into the subject. This is narrower than, and consistent with, the per-`subject` authorization required above: authorization decides *who* may write for a subject, while call topology decides whether that write remains observable on the subject's own address. + +Consumers of this proposal's events are unaffected in either topology: `DocumentUpdatedForSubject` / `DocumentRemovedForSubject` carry the `subject` address and are emitted by the management contract in every case. + +## Rationale + +The `subject` parameter is named generically rather than "token" because the contract documents belong to is not necessarily a token, and because the on-chain identifiers should not hard-code an assumption the interface does not enforce. The event names follow the parameter (`DocumentUpdatedForSubject`), keeping the event and its attribute aligned. + +The interface is declared independently of `IERC1643` rather than inheriting it. Inheritance would force every shared management contract to implement the per-contract single-argument functions, which have no meaningful subject in the shared case, and would invite contracts to advertise an ERC-165 interface id for functions they do not implement. + +Separate address-scoped functions are used instead of overloading the per-contract ones with a subject-carrying variant of the same name because the resulting selectors are distinct either way; declaring them here keeps this proposal self-contained and readable without reference to ERC-1643's interface. + +The data model is inherited from ERC-1643 unchanged — `bytes32` names for compact, directly comparable on-chain identifiers, a URI pointer instead of on-chain storage, and a content hash for integrity — so that a subject can migrate between self-managed and delegated document storage without changing what consumers read. + +Errors are prefixed by the proposal that defines the condition, not by the proposal that declares them. `ERC1643InvalidName` and `ERC1643MissingDocument` keep their ERC-1643 prefix because they are ERC-1643's errors, reused so revert data stays identical across both interfaces; renaming them here would fragment that. `MultiDocumentInvalidSubject` is defined by this proposal alone and is therefore named after it, rather than borrowing a prefix from a standard in which the condition cannot occur. + +Manager discovery is defined here, and as a separate optional interface, for three reasons. It is a function on the *subject*, so folding it into ERC-1643 would grow that proposal with a function about a delegation arrangement ERC-1643 does not itself define. It is meaningful only where delegation exists, which is the subject of this proposal. And keeping it out of `IERC1643MultiDocument` means a management contract is never asked to implement a getter about itself that only its subjects can answer, while the distinct ERC-165 id lets a consumer detect discovery support without inferring anything about either document interface. + +The names `IERC1643MultiDocument`, `MultiDocumentInvalidSubject` and `IMultiDocumentSubject` are all provisional. They record the companion relationship while this proposal is unnumbered, and SHOULD be revisited together to track this proposal's own number once an editor assigns one. The two reused ERC-1643 error names are **not** provisional and are expected to stay as they are. + +## Backwards Compatibility + +This proposal introduces a new interface and does not modify [ERC-1643](./eip-1643.md) or any other proposal. + +- **Function selectors.** `getDocument(address,bytes32)`, `getAllDocuments(address)`, `setDocument(address,bytes32,string,bytes32)` and `removeDocument(address,bytes32)` have different signatures — hence different 4-byte selectors — than their single-argument ERC-1643 counterparts. A contract implementing both exposes both sets side by side with no collision. +- **Events.** `DocumentUpdatedForSubject` / `DocumentRemovedForSubject` are new event topics. ERC-1643's `DocumentUpdated` / `DocumentRemoved` are untouched and keep their exact meaning. +- **ERC-165.** Advertising `type(IERC1643MultiDocument).interfaceId` does not disturb `supportsInterface(type(IERC1643).interfaceId)`. A consumer that only knows ERC-1643 detects it exactly as before. +- **Manager discovery.** `IMultiDocumentSubject` is optional and separate. It adds one selector and one event topic to a subject that chooses to implement it, and is declared independently of both document interfaces, so it perturbs neither interface id. A subject that does not implement it is unaffected, and a consumer that does not know it behaves exactly as before. + +A consumer that only knows ERC-1643 is unaffected: it continues to call the per-contract functions and subscribe to the per-contract events on the address it was directed to watch. The one way to break such a consumer is to direct it at a management contract that emits only the events defined here — a deployment error, addressed by the Emission Responsibility and Call Topology requirements above rather than by the interface itself. + +## Test Cases + +Implementations should verify at least the following: + +- Subject isolation: the same `name` written for two different subjects yields two independent entries, and `getAllDocuments` for one subject never returns the other's names. +- Adding, updating, and removing a document for a subject, and reading it back through `getDocument`. +- Emission of `DocumentUpdatedForSubject` on create/update and `DocumentRemovedForSubject` on delete, each carrying the correct `subject`. +- A caller not authorized for a subject failing to create, update, or remove that subject's documents. +- `setDocument` rejecting `subject == address(0)` and `name == bytes32(0)`. +- `supportsInterface` returning `true` for `type(IERC1643MultiDocument).interfaceId`, and returning `false` for `type(IERC1643).interfaceId` on a contract that implements only the address-scoped surface. +- For a subject implementing `IMultiDocumentSubject`: `documentManager` returning the delegate's address, returning `address(0)` when management is not delegated, and `DocumentManagerUpdated` being emitted with the correct previous and new addresses when the delegate changes. + +## Reference Implementation + +A reference implementation is not yet provided. It is expected to maintain, per subject, a mapping from `bytes32` name to document metadata, a set for enumeration of active names, and index tracking to support O(1) removals — the ERC-1643 reference module's structure, keyed additionally by `subject` — together with the per-`subject` authorization required in the Specification. + +## Security Considerations + +- **Per-subject authorization is the central risk.** A management contract holds the document sets of unrelated subjects behind a single address. If writes are not authorized per `subject`, any caller permitted to write for one subject can modify another subject's legal or operational references. This is a stronger requirement than in the per-contract case, where a contract's own access control naturally scopes to its own documents. +- **Delegation moves the subject's trust boundary.** A subject that delegates document management is only as protected as the management contract's access control: whoever can write for that subject there can change its legal or operational references, regardless of the subject's own permissioning. Delegating to a contract serving several subjects also concentrates the document sets of unrelated parties behind a single address, so a single access-control flaw there is not contained to one subject. Subjects should treat the choice of management contract as a permissioning decision, not merely a storage one. +- **The null subject.** Implementations should reject `subject == address(0)`. This is a data-integrity concern rather than a fund-safety one: subject namespaces are isolated, and the null address cannot call the contract to read documents registered under it, so such entries are inert. They do, however, let callers populate a namespace no contract can ever own, cluttering state and misleading off-chain indexers that key on `subject`. +- **Direct writes can silently bypass subject-side emission.** When a subject implements [ERC-1643](./eip-1643.md), a write sent directly to the management contract without an execution point in the subject leaves the subject's per-contract events unemitted. The failure is quiet in both directions: the write succeeds and the event defined here is emitted, so nothing reverts, while a consumer watching the subject sees no event and concludes the documents are unchanged. Integrators who must rely on the per-contract events should confirm the deployment's write path out of band. +- **Event emission is not discoverable.** Because events are outside ERC-165, `supportsInterface(type(IERC1643MultiDocument).interfaceId)` confirms only that the functions exist, not that the address-carrying events are actually emitted. Integrators relying on those events should confirm emission out of band. +- **`documentManager` is an unverified assertion.** The value is chosen entirely by the subject, and nothing requires the named contract to acknowledge the relationship or to hold any documents for that subject. A compromised or malicious subject can point consumers at an attacker-controlled contract serving fabricated documents, and calling `getAllDocuments(subject)` on the claimed manager does not disprove this — anyone can populate their own contract with entries for any subject. Manager discovery is a convenience for locating a feed, not an authorization or authenticity signal. Consumers making decisions that depend on document contents should verify against the published `documentHash` and, where the stakes justify it, confirm the manager address through the same out-of-band channel they would have used without this interface. +- **A stale cached manager address.** A consumer that reads `documentManager` once and does not watch `DocumentManagerUpdated` may keep following a superseded contract after a migration, seeing a frozen document set with no indication it is no longer current. +- **Mutable off-chain content.** Document URIs may reference content that changes independently of the chain. Consumers are strongly encouraged to verify content against the published `documentHash` and to use trusted retrieval channels. +- **Names may not fit `bytes32`.** Long legal titles should not be lossily truncated; a deterministic hash-based identifier (for example the document content hash, or a hash of a canonical full title) is a safer choice for the `bytes32` name. +- **Events are advisory.** Applications should reconcile event streams against on-chain state when correctness is critical. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..9d465db --- /dev/null +++ b/doc/README.md @@ -0,0 +1,564 @@ +# DocumentEngine (ERC-1643) - Specification + + +The `DocumentEngine` is an external contract to manage documents through [*ERC-1643*](https://github.com/ethereum/EIPs/issues/1643), a proposed standard for managing documents on-chain. [ERC-1400](https://github.com/ethereum/eips/issues/1411) from Polymath builds on it. + +The DocumentEngine is meant to be used by other smart contracts, e.g. a CMTAT token, to store documents on their behalf. + +> This project has not been audited yet, please use at your own risk. For any questions, please contact [admin@cmta.ch](mailto:admin@cmta.ch). + +## Table of contents + +- [Introduction](#introduction) +- [Two ways to manage documents](#two-ways-to-manage-documents) +- [Flexible access control](#flexible-access-control) +- [Why not reuse RuleEngine's ERC-3643 compliance module?](#why-not-reuse-ruleengines-erc-3643-compliance-module) +- [Events](#events) +- [Integration with CMTAT](#integration-with-cmtat) + - [Topology](#topology) + - [Writing a document](#writing-a-document) + - [Reading a document](#reading-a-document) + - [Wiring and the full call flow](#wiring-and-the-full-call-flow) +- [Architecture](#architecture) +- [Version (ERC-8303)](#version-erc-8303) + - [ERC-165: what the engine advertises](#erc-165-what-the-engine-advertises) +- [Schema](#schema) + - [Inheritance](#inheritance) + - [Graph](#graph) +- [Surya Description Report](#surya-description-report) + - [Contracts Description Table](#contracts-description-table) + - [Interfaces](#interfaces) + - [Legend](#legend) +- [Gasless support (ERC-2771)](#gasless-support-erc-2771) +- [Dependencies](#dependencies) + - [Version compatibility](#version-compatibility) +- [Tools](#tools) + - [Formatting (forge fmt)](#formatting-forge-fmt) + - [Static analysis](#static-analysis) + - [Surya](#surya) + - [Foundry](#foundry) +- [Intellectual property](#intellectual-property) + +## Introduction + +The ERC-1643 defines a document with three attributes: + +- A short name (represented as a `bytes32`) +- A generic URI (represented as a `string`) that could point to a website or other document portal. +- The hash of the document contents associated with it on-chain. + +A smart contract needs only to read documents from this standard through the interface [IERC1643](../lib/CMTAT/contracts/interfaces/tokenization/draft-IERC1643.sol) to get the documents from the documentEngine: + +```solidity +interface IERC1643 { + error ERC1643InvalidName(); + error ERC1643MissingDocument(); + + function getDocument(bytes32 name) + external + view + returns (string memory uri, bytes32 documentHash, uint256 lastModified); + function getAllDocuments() external view returns (bytes32[] memory documentNames_); + function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external; + function removeDocument(bytes32 name) external; +} +``` + +> **Note — `getDocument` returns flat values.** CMTAT `v3.3.0-rc1` briefly returned a `Document` struct here; `v3.3.0-rc2` restored the three flat return values mandated by the ERC-1643 ABI, and this engine follows. Return types are not part of a function signature, so both shapes share the same selector and the same `type(IERC1643).interfaceId`: a struct return is undetectable through ERC-165, and a consumer built from the specification ABI decodes it as garbage without reverting. The `Document` struct is kept internally for storage only, and `testGetDocumentReturnsFlatErc1643Abi` pins the wire format. + +Using an external contract for your smart contract provides two advantages: + +- Reduce code size of your smart contract +- Allow to manage documents for several different smart contracts + +## Two ways to manage documents + +The engine supports **two management paths** at the same time: + +**1. Admin path (`DOCUMENT_MANAGER_ROLE`).** Since the engine manages documents for several different smart contracts, the admin functions take one supplementary `address smartContract` argument compared to the ERC-1643: + +```solidity +// DocumentEngine (admin overloads) +function setDocument(address smartContract, bytes32 name_, string memory uri_, bytes32 documentHash_) external; +function removeDocument(address smartContract, bytes32 name_) external; +``` + +**2. Bound-token path.** This implements the standard, single-argument ERC-1643 functions. A token is *bound* to the engine through the shared **`ITokenBinding`** surface — identical across both deployments, so integrators bind/query a token the same way regardless of the access-control model: + +```solidity +documentEngine.bindToken(address(token)); // also: unbindToken(token), isTokenBound(token) +``` + +Both deployments share the exact same binding mechanism — a single allowlist in `TokenBindingModule` (`src/modules/TokenBindingModule.sol`), **not** a role. They expose the same `bindToken` / `unbindToken` / `isTokenBound` functions, emit the same `TokenBindingSet` event, and revert with the same `NotBoundToken` error when a non-bound caller attempts a write. The only difference is *who* may bind: whoever may manage documents in that deployment (the `DOCUMENT_MANAGER_ROLE` holder, or the `owner`), since binding is authorized by the same document-management hook. + +Once bound, the token manages its **own** documents (`msg.sender` is the token); it can never affect another contract's documents: + +```solidity +// DocumentEngine (standard ERC-1643, scoped to msg.sender) +function setDocument(bytes32 name_, string calldata uri_, bytes32 documentHash_) external; +function removeDocument(bytes32 name_) external; +``` + +> This mirrors the RuleEngine *binding* pattern without reusing its `ERC3643ComplianceExtendedModule` — see [Why not reuse RuleEngine's ERC-3643 compliance module?](#why-not-reuse-ruleengines-erc-3643-compliance-module) below. + +## Flexible access control + +Following the CMTAT / [RuleEngine](https://github.com/CMTA/RuleEngine) pattern, the restricted functions do not hardcode a check. They carry a **modifier** (`onlyDocumentManager` / `onlyBoundToken`) that delegates to an **overridable `internal virtual` authorization hook**: + +- the **admin path** delegates to `_authorizeDocumentManagement()`, the one hook each deployment implements (`_checkRole(DOCUMENT_MANAGER_ROLE)` for `DocumentEngine`, `_checkOwner()` for `DocumentEngineOwnable`); +- the **bound-token path** delegates to `_authorizeBoundTokenDocumentManagement()`, which `TokenBindingModule` implements once for both deployments (it checks the shared binding allowlist). + +```solidity +// implemented per deployment (the only access-control hook they supply) +function _authorizeDocumentManagement() internal view virtual { + _checkRole(DOCUMENT_MANAGER_ROLE); // or _checkOwner() +} + +// implemented once in TokenBindingModule for both deployments +function _authorizeBoundTokenDocumentManagement() internal view virtual override { + _checkTokenBound(); // reverts NotBoundToken if msg.sender is not bound +} +``` + +This separates the document-management implementation from the authorization logic: a subclass changes *who* is authorized by overriding the hook, never by touching the management functions. + +## Why not reuse RuleEngine's ERC-3643 compliance module? + +CMTA's [RuleEngine](https://github.com/CMTA/RuleEngine) (v3) ships an `ERC3643ComplianceExtendedModule` that offers a ready-made token-binding registry (`bindToken` / `unbindToken` / `isTokenBound` / `getTokenBounds`). It is tempting to reuse it for the bound-token path, but we deliberately do **not**, because that module is an **`IERC3643Compliance`** — a *transfer-compliance* contract. + +Inheriting it would force the DocumentEngine to also implement the ERC-3643 transfer-compliance callbacks that come with that interface: + +```solidity +function canTransfer(address, address, uint256) external view returns (bool); +function transferred(address, address, uint256) external; +function created(address, uint256) external; +function destroyed(address, uint256) external; +``` + +A document engine has **nothing to do with token transfers**, so these would have to be stubbed as no-ops (`canTransfer` always returning `true`). That is misleading: the contract would advertise a transfer-compliance surface it does not honor, enlarging the ABI and inviting integrators to wire it where a real compliance contract is expected. + +The binding concept we actually need is tiny — "is this caller a token allowed to manage its own documents?" — so we implement just that: a **single allowlist** in `TokenBindingModule`, shared by both deployments and gated by each one's document-management hook. It is deliberately **not** a role: there is no `TOKEN_CONTRACT_ROLE`, and `DocumentEngineOwnable` uses the same allowlist rather than a separate owner-managed one. This keeps the engine's surface honest and minimal while still mirroring the RuleEngine binding pattern; the RuleEngine submodule is kept as a reference for that pattern. + +## Events + +This engine is a **shared, multi-token** document manager, so — per the ERC-1643 ["Emission Responsibility"](./ERCSpecification/erc-1643.md) rules — it emits **only** the address-carrying extension events `DocumentUpdatedForSubject(address indexed subject, …)` / `DocumentRemovedForSubject(…)`, and **not** the base `DocumentUpdated` / `DocumentRemoved` events. The base events carry no address and so cannot identify which token contract a change belongs to; they are the responsibility of the token contract that exposes ERC-1643 to consumers (it re-emits them when delegating). See the [Multi-Subject Document Management draft](./ERCSpecification/erc-draft_multi_document_management.md) and the `IERC1643MultiDocument` extension. + +## Integration with CMTAT + +Since CMTAT v3, the shipped standalone tokens store documents on-chain (`DocumentERC1643Module`) and do not consume an external engine through their constructor. To use this engine, a CMTAT token relies on the `DocumentEngineModule` and is wired at runtime with `setDocumentEngine(engine)`; reads/writes are then forwarded to the engine keyed by the token address. + +### Topology + +One engine serves a whole fleet of tokens. Each token keeps its own document namespace, keyed by its address, and can never reach another token's: + +![Topology: one engine, many subjects](./img/cmtat-integration-architecture.png) + +_Diagram source: `doc/img/cmtat-integration-architecture.puml`._ + +### Writing a document + +![Writing a document through a CMTAT token](./img/cmtat-write-simple.png) + +_Diagram source: `doc/img/cmtat-write-simple.puml`._ + +### Reading a document + +![Reading a document from a CMTAT token or the engine](./img/cmtat-read-simple.png) + +_Diagram source: `doc/img/cmtat-read-simple.puml`._ + +### Wiring and the full call flow + +Two independent steps wire a token to the engine, and they are easy to get half right: `bindToken(token)` on the **engine** authorises the token to use the single-argument ERC-1643 functions, while `setDocumentEngine(engine)` on the **token** tells it where to forward. Bind without wiring and the token has nowhere to send; wire without binding and the forwarded call reverts `NotBoundToken`. + +The diagram below expands the two above with the wiring steps, every revert branch, and the admin path — the one case where the emission split does not hold: + +![DocumentEngine and CMTAT call sequence](./img/cmtat-integration-sequence.png) + +_Diagram source: `doc/img/cmtat-integration-sequence.puml`._ + +A minimal integration: + +```solidity +// 1. authorise the token on the engine (engine's document manager) +documentEngine.bindToken(address(token)); + +// 2. point the token at the engine (token's document manager) +token.setDocumentEngine(documentEngine); + +// 3. the token now manages its own documents through the standard ERC-1643 calls, +// and reads are forwarded to the engine keyed by the token address +token.setDocument(bytes32("prospectus"), "ipfs://...", keccak256(bytes(content))); +``` + +Both halves are covered by the test suite against real CMTAT code: `testCanReturnCMTATDocument` wires `CMTATDocumentEngineMock` (built on CMTAT's `DocumentEngineModule`) with `setDocumentEngine` and reads through it, and `testBoundTokenCanManageOwnDocument` exercises the bound-token write and the namespace isolation that goes with it. + + + +## Architecture + +The engine is split into two contracts (CMTAT module/deployment pattern): + +![Contract structure: two deployments over one shared base](./img/documentengine-contract-structure.png) + +_Diagram source: `doc/img/documentengine-contract-structure.puml`._ + +- **`DocumentEngineBase`** (abstract) — holds the document storage and all the ERC-1643 document-management functions, plus the `onlyDocumentManager` / `onlyBoundToken` modifiers and the **abstract** `_authorize*` hooks. It is agnostic to the access-control implementation. +- **`DocumentEngine`** (deployment) — the concrete, deployable contract. It defines the **access control** (`AccessControlEnumerable`, the `_authorize*` hook implementations and the `hasRole` override) and wires the ERC-2771 (gasless) support. `AccessControlEnumerable` additionally allows enumerating the members of each role on-chain. +- **`DocumentEngineOwnable`** (alternative deployment) — same base logic, but access control is a single **owner** via `Ownable2Step` (two-step ownership transfer) instead of roles. Both document management and token binding are `owner`-only. +- **`TokenBindingModule`** (`src/modules/TokenBindingModule.sol`) — the shared token-binding registry (an allowlist) implementing `ITokenBinding` (`bindToken` / `unbindToken` / `isTokenBound` + `TokenBindingSet`). Both deployments inherit it, so binding is identical (same functions, event, and `NotBoundToken` revert) and ERC-165-discoverable regardless of the access-control model; binding is authorized by each deployment's document-management hook. + +`DocumentEngineInvariant` provides the errors shared by every deployment. Access-control specifics are **not** defined there: the `DOCUMENT_MANAGER_ROLE` constant lives in the role-based `DocumentEngine`, and the owner logic in `DocumentEngineOwnable`. + +`VersionModule` (`src/modules/VersionModule.sol`) isolates the version concern and implements [ERC-8303](https://ethereum-magicians.org/t/erc-8303-contract-version/28795) (see below). + +## Version (ERC-8303) + +The contract version is exposed through the `VersionModule`, which implements the [ERC-8303](https://ethereum-magicians.org/t/erc-8303-contract-version/28795) `IERC8303` interface: + +```solidity +interface IERC8303 { + function version() external view returns (string memory); +} +``` + +- `version()` returns the current version string (e.g. `"0.4.0"`), following Semantic Versioning 2.0.0. +- The public `VERSION` constant is kept for backward compatibility and returns the same value. +- ERC-165 discovery is supported: `supportsInterface(0x54fd4d50)` (the ERC-8303 interface id) returns `true`. + +### ERC-165: what the engine advertises + +Both deployments advertise: + +| Interface | Id | | +| --- | --- | --- | +| `IERC1643` | `0xecfecec8` | base single-argument functions, for a **bound subject** | +| `IERC1643MultiDocument` | `0xa2b1179b` | address-scoped document management | +| `ITokenBinding` | — | `bindToken` / `unbindToken` / `isTokenBound` | +| `IERC8303` | `0x54fd4d50` | `version()` | +| `IERC165` | `0x01ffc9a7` | | +| `IAccessControlEnumerable` | — | `DocumentEngine` only | + +`type(IERC1643).interfaceId` is advertised because the engine really does implement the base single-argument functions. Its audience is a **token wiring itself to the engine**: before calling `setDocumentEngine(engine)`, or before forwarding `setDocument(name, uri, hash)`, a token can confirm through ERC-165 that those endpoints exist here rather than discovering it from a failed call. `ITokenBinding` answers the complementary question — does this engine have a binding surface — and `isTokenBound(address(this))` whether that particular token may use it. + +> **It is not an invitation to read documents from this address.** The base functions are `_msgSender()`-scoped, so a third party calling `getDocument(name)` on the engine reads *its own*, empty namespace — no revert, no error, just nothing — and the engine emits only the address-carrying `*ForSubject` events. Point document consumers at the **subject**, or use the address-scoped `getDocument(subject, name)`. Asserted by `testBaseERC1643IsAdvertisedButReadsAreCallerScoped`. + +## Schema + +Generated with Surya — regenerate with the three scripts in [`doc/script`](./script). Diagrams for **every** file in `src/`, interfaces included, live under [`doc/surya`](./surya); the ones below are the two deployments and the base they share. + +### Inheritance + +Both deployments sit on the same two modules — `DocumentEngineBase` (document logic) and `TokenBindingModule` (the binding allowlist) — and differ only in the access-control layer. + +#### `DocumentEngine` — role-based (`AccessControlEnumerable`) + +![surya_inheritance_DocumentEngine.sol](./surya/surya_inheritance/surya_inheritance_DocumentEngine.sol.png) + +#### `DocumentEngineOwnable` — single owner (`Ownable2Step`) + +![surya_inheritance_DocumentEngineOwnable.sol](./surya/surya_inheritance/surya_inheritance_DocumentEngineOwnable.sol.png) + +### Graph + +#### `DocumentEngineBase` — the shared document logic + +![surya_graph_DocumentEngineBase.sol](./surya/surya_graph/surya_graph_DocumentEngineBase.sol.png) + +#### `DocumentEngine` + +![surya_graph_DocumentEngine.sol](./surya/surya_graph/surya_graph_DocumentEngine.sol.png) + +#### `DocumentEngineOwnable` + +![surya_graph_DocumentEngineOwnable.sol](./surya/surya_graph/surya_graph_DocumentEngineOwnable.sol.png) + +## Surya Description Report + +### Contracts Description Table + +Per-file reports live in [`doc/surya/surya_report`](./surya/surya_report); the tables below merge them. Note that the document functions belong to **`DocumentEngineBase`**, not to either deployment — each deployment contributes only its access-control layer and its ERC-2771 context overrides. + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **DocumentEngineBase** | Implementation | IERC1643, IERC1643MultiDocument, DocumentEngineInvariant, Context ||| +| └ | removeDocument | External ❗️ | 🛑 | onlyDocumentManager | +| └ | setDocument | External ❗️ | 🛑 | onlyBoundToken | +| └ | removeDocument | External ❗️ | 🛑 | onlyBoundToken | +| └ | batchSetDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | batchSetDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | getDocument | External ❗️ | |NO❗️ | +| └ | getDocument | External ❗️ | |NO❗️ | +| └ | getAllDocuments | External ❗️ | |NO❗️ | +| └ | getAllDocuments | External ❗️ | |NO❗️ | +| └ | setDocument | Public ❗️ | 🛑 | onlyDocumentManager | +| └ | _removeDocumentName | Internal 🔒 | 🛑 | | +| └ | _removeDocument | Internal 🔒 | 🛑 | | +| └ | _setDocument | Internal 🔒 | 🛑 | | +| └ | _authorizeDocumentManagement | Internal 🔒 | | | +| └ | _authorizeBoundTokenDocumentManagement | Internal 🔒 | | | +| └ | _getDocument | Internal 🔒 | | | +|||||| +| **DocumentEngine** | Implementation | TokenBindingModule, VersionModule, AccessControlEnumerable, ERC2771Context ||| +| └ | | Public ❗️ | 🛑 | ERC2771Context | +| └ | hasRole | Public ❗️ | |NO❗️ | +| └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | _authorizeDocumentManagement | Internal 🔒 | | | +| └ | _msgSender | Internal 🔒 | | | +| └ | _msgData | Internal 🔒 | | | +| └ | _contextSuffixLength | Internal 🔒 | | | +|||||| +| **DocumentEngineOwnable** | Implementation | TokenBindingModule, VersionModule, Ownable2Step, ERC2771Context ||| +| └ | | Public ❗️ | 🛑 | Ownable ERC2771Context | +| └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | _authorizeDocumentManagement | Internal 🔒 | | | +| └ | _msgSender | Internal 🔒 | | | +| └ | _msgData | Internal 🔒 | | | +| └ | _contextSuffixLength | Internal 🔒 | | | +|||||| +| **TokenBindingModule** | Implementation | DocumentEngineBase, ITokenBinding ||| +| └ | bindToken | External ❗️ | 🛑 |NO❗️ | +| └ | unbindToken | External ❗️ | 🛑 |NO❗️ | +| └ | isTokenBound | Public ❗️ | |NO❗️ | +| └ | _setTokenBinding | Internal 🔒 | 🛑 | | +| └ | _authorizeBoundTokenDocumentManagement | Internal 🔒 | | | +| └ | _checkTokenBound | Internal 🔒 | | | +|||||| +| **VersionModule** | Implementation | IERC8303, ERC165 ||| +| └ | version | Public ❗️ | |NO❗️ | +| └ | supportsInterface | Public ❗️ | |NO❗️ | +|||||| +| **DocumentEngineInvariant** | Implementation | ||| + +### Interfaces + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **IERC1643MultiDocument** | Interface | ||| +| └ | setDocument | External ❗️ | 🛑 |NO❗️ | +| └ | removeDocument | External ❗️ | 🛑 |NO❗️ | +| └ | getDocument | External ❗️ | |NO❗️ | +| └ | getAllDocuments | External ❗️ | |NO❗️ | +|||||| +| **ITokenBinding** | Interface | ||| +| └ | bindToken | External ❗️ | 🛑 |NO❗️ | +| └ | unbindToken | External ❗️ | 🛑 |NO❗️ | +| └ | isTokenBound | External ❗️ | |NO❗️ | +|||||| +| **IERC8303** | Interface | ||| +| └ | version | External ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +| :----: | ------------------------- | +| 🛑 | Function can modify state | +| 💵 | Function is payable | + + + +## Gasless support (ERC-2771) + +The DocumentEngine supports client-side gasless transactions using the [Gas Station Network](https://docs.opengsn.org/#the-problem) (GSN) pattern, the main open standard for transfering fee payment to another account than that of the transaction issuer. The contract uses the OpenZeppelin contract `ERC2771ContextUpgradeable`, which allows a contract to get the original client with `_msgSender()` instead of the fee payer given by `msg.sender` while allowing upgrades on the main contract (see *Deployment via a proxy* above). + +At deployment, the parameter `forwarder` inside the constructor has to be set with the defined address of the forwarder. Please note that the forwarder can not be changed after deployment. + +Please see the OpenGSN [documentation](https://docs.opengsn.org/contracts/#receiving-a-relayed-call) for more details on what is done to support GSN in the contract. + + + +## Dependencies + +The toolchain includes the following components, where the versions are the latest ones that we tested: + +- Foundry +- Solidity 0.8.34 (via solc-js), `evm_version = prague` +- OpenZeppelin Contracts (submodule) [v5.7.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.7.0) +- Tests + - [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) (binding-pattern reference only — its compliance module is [not reused](#why-not-reuse-ruleengines-erc-3643-compliance-module)) + - OpenZeppelin Contracts Upgradeable (submodule) [v5.7.0](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.7.0) + +### Version compatibility + +Each release of this engine is tested against one CMTAT release and is supported across a **verified, bounded range** of them. The range is deliberately closed at both ends rather than written as `≥ minimum`: CMTAT's `IERC1643` has changed shape *within* a single minor line — `getDocument` returned a `Document` struct in `v3.0.0`, `v3.1.0`, `v3.2.0` and `v3.3.0-rc1`, and only became the three flat values at `v3.3.0-rc2` — so a future CMTAT release cannot be assumed compatible until it has been built and tested against. + +| DocumentEngine | Compatible CMTAT | Tested against | Solidity / `evm_version` | OpenZeppelin | `getDocument` returns | +| -------------- | ---------------- | -------------- | ------------------------ | ------------ | --------------------- | +| **v0.4.0** (current) | `v3.3.0-rc2` – [`v3.3.0-rc3`](https://github.com/CMTA/CMTAT/releases/tag/v3.3.0-rc3) | v3.3.0-rc3 | `0.8.34` / `prague` | v5.7.0 | `(string, bytes32, uint256)` | +| v0.3.0 | [v2.5.0-rc0](https://github.com/CMTA/CMTAT/releases/tag/v2.5.0-rc0) (range not established) | v2.5.0-rc0 | `0.8.26` / `cancun` | v5.0.2 | `(string, bytes32, uint256)` | +| v0.2.0 | [v2.5.0-rc0](https://github.com/CMTA/CMTAT/releases/tag/v2.5.0-rc0) (range not established) | v2.5.0-rc0 | `0.8.26` / `cancun` | v5.0.2 | `(string, bytes32, uint256)` | +| v0.1.0 | [v2.5.0-rc0](https://github.com/CMTA/CMTAT/releases/tag/v2.5.0-rc0) (range not established) | v2.5.0-rc0 | `0.8.26` / `cancun` | v5.0.2 | `(string, bytes32, uint256)` | + +Notes on the CMTAT v2 → v3 jump at `v0.4.0`: + +- **No CMTAT below `v3.3.0-rc2` is supported, including the v3.0–v3.2 finals.** `v3.0.0`, `v3.1.0` and `v3.2.0` return a `Document` struct from `IERC1643.getDocument` and declare neither `ERC1643InvalidName` nor `ERC1643MissingDocument` on the interface, so this engine does not compile against them; they also predate CMTAT's token-side `DocumentEngineModule`, which first appears at `v3.3.0-rc1`, so a token on those versions has no supported way to consume an external engine at all. +- **CMTAT `v3.3.0-rc1` is not supported either.** It is the one release in which `IERC1643.getDocument` returns a `Document` struct rather than the three flat values; `v3.3.0-rc2` reverted that and `v3.3.0-rc3` keeps the flat return. rc1 also does not declare `ERC1643InvalidName` / `ERC1643MissingDocument` on the interface. Building this engine against rc1 fails to compile. +- **`v3.3.0-rc2` → `v3.3.0-rc3` is a no-op for this engine**, which is why the supported range spans both. The only change to the document surface (`draft-IERC1643.sol`, `IDocumentEngine.sol`, `DocumentEngineModule.sol`, `DocumentERC1643Module.sol`) is a pragma bump from `^0.8.20` to `^0.8.24`; the interface, the errors and the `getDocument` return shape are unchanged. Verified by building and running the full suite against rc2 as well as rc3 — 74/74 in both. +- The `IERC1643` import path moved in CMTAT v3, from `CMTAT/interfaces/engine/draft-IERC1643.sol` to `CMTAT/interfaces/tokenization/draft-IERC1643.sol`. +- Document names became `bytes32` in CMTAT v3 (they were `string` up to v2.5.0-rc0). +- Two different Solidity floors apply from `v0.4.0` on, and the sources declare the lower of them: + - **`src/` requires `≥ 0.8.24`** — the pragma every file declares. OpenZeppelin's `AccessControlEnumerable.sol` / `EnumerableSet.sol` and, since CMTAT `v3.3.0-rc3`, `draft-IERC1643.sol` are all `^0.8.24`, so no contract here compiles below it. + - **Building the full project, tests included, requires `≥ 0.8.27`**, because CMTAT v3 uses `require(cond, CustomError())`, which is restricted to the via-ir pipeline before `0.8.27`. + + This is why the declared pragma is `^0.8.24` while `foundry.toml` pins `0.8.34`. + +Exact submodule revisions are pinned in [`foundry.lock`](../foundry.lock). + +## Tools + +### Formatting (forge fmt) + +`forge fmt` is the canonical formatter for this project (configured under `[fmt]` in `foundry.toml`): + +```bash +forge fmt # format src/, test/, script/ +forge fmt --check # verify formatting (CI) +``` + +### Static analysis + +Reports are versioned under [`doc/audits/tools/`](./audits/tools), one directory per release, each with the raw tool output (prefixed by a summary table) and a feedback file triaging every finding against the source. The security overview is [`doc/audits/AUDIT_OVERVIEW.md`](./audits/AUDIT_OVERVIEW.md). + +| Release | Tool | Result | Report | Triage | +| ------- | ---- | ------ | ------ | ------ | +| v0.4.0 | Aderyn `0.6.5` | 0 High · 6 Low — **nothing to fix** | [report](./audits/tools/v0.4.0/aderyn/aderyn-report.md) | [feedback](./audits/tools/v0.4.0/aderyn/aderyn-report-feedback.md) | +| v0.4.0 | Slither `0.11.5` | 0 High · 0 Medium · 0 Low · 2 Info — **nothing to fix** | [report](./audits/tools/v0.4.0/slither/slither-report.md) | [feedback](./audits/tools/v0.4.0/slither/slither-report-feedback.md) | +| v0.4.0 | Claude Code (code quality) | 14 findings, **no vulnerability** — 6 implemented, 8 deliberately left | [report](./audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md) | (triage is in the report) | + +```bash +# Aderyn — mocks excluded (this project's mocks live in test/, which Aderyn does not scan) +aderyn -x mocks --output doc/audits/tools/v0.4.0/aderyn/aderyn-report.md + +# Slither — mocks excluded (they live in test/, removed by the `test` filter) +slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks" \ + > doc/audits/tools/v0.4.0/slither/slither-report.md +``` + +> **Filter on `lib`, not on individual submodule names.** This is a Foundry project, so every dependency lives under `lib/`. `--filter-paths` fails *open* — an entry matching nothing silently widens scope instead of erroring — so naming submodules one by one risks pulling a whole vendored tree into the report. Verify with `grep -c 'lib/\|node_modules/' `, which must return `0`. Slither also writes its checklist to **stdout** and its detector log to **stderr**, and exits non-zero when it finds anything: `exit=255` with a populated report is the normal outcome. + +> **Static-analysis output is leads, not findings.** Every dismissal in the feedback files was verified against the cited `file:line`, and neither tool can see the specification-level issues that matter most here — those are tracked under *Known open items* in [`AUDIT_OVERVIEW.md`](./audits/AUDIT_OVERVIEW.md). + +### Surya + +Three scripts in [`doc/script`](./script) regenerate the diagrams and reports for every `.sol` under `src/`, writing into a scratch `docOut/` at the repo root. **Run them from `doc/script/` and in this order** — the graph script creates `docOut/`, and the report script's `mkdir` has no `-p`: + +```bash +(cd doc/script && bash script_surya_graph.sh) +(cd doc/script && bash script_surya_inheritance.sh) +(cd doc/script && bash script_surya_report.sh) +``` + +Then replace the three directories under [`doc/surya`](./surya) with the fresh output. Requires Graphviz (`dot`) — the graph and inheritance scripts pipe through it. + +> **Known Surya bug — check for 0-byte PNGs.** `surya graph` parses only the file it is given, so a `super.()` call into a base declared elsewhere throws `TypeError: Cannot read properties of undefined (reading 'includes')`. Piped into `dot`, that surfaces as a silent **empty PNG**, not an error. Four files here call `super.()` (`DocumentEngine`, `DocumentEngineOwnable`, `VersionModule`, `TokenBindingModule`), so the guard in `surya/lib/graph.js` — `functionsPerContract[contract] && functionsPerContract[contract].includes(name)` — must be applied before regenerating. It lives in `node_modules` (or the `npx` cache) and is reverted by any reinstall. + +### Foundry + +Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +#### Documentation + +https://book.getfoundry.sh/ + +#### Usage + +##### Coverage + +```bash +$ forge coverage --report lcov && genhtml lcov.info --branch-coverage --output-dir coverage +``` + +##### Gas report + +```bash +$ forge test --gas-report +``` + +##### Build + +```shell +$ forge build +``` + +##### Test + +```shell +$ forge test +``` + +##### Format + +```shell +$ forge fmt +``` + +##### Gas Snapshots + +```shell +$ forge snapshot +``` + +##### Anvil + +```shell +$ anvil +``` + +##### Deploy + +Two deployment scripts are provided in [`script/`](../script), one per access-control variant. Both read their configuration from environment variables: + +| Variable | Used by | Default | Meaning | +| --- | --- | --- | --- | +| `DOCUMENT_ENGINE_ADMIN` | `DeployDocumentEngine` | `msg.sender` | account granted `DEFAULT_ADMIN_ROLE` | +| `DOCUMENT_ENGINE_OWNER` | `DeployDocumentEngineOwnable` | `msg.sender` | initial owner | +| `DOCUMENT_ENGINE_FORWARDER` | both | `address(0)` | ERC-2771 trusted forwarder (`address(0)` disables gasless) | + +> **Warning** These environment variables, and passing a raw key with `--private-key`, are intended for **local testing only — do not use them in production**. A private key supplied on the command line or through an environment variable is exposed in your shell history and process environment. For production deployments, use a secure signing method (encrypted keystore, hardware wallet, ...) as described in the Foundry Key Management documentation (getfoundry.sh) for securely broadcasting transactions through a script. + +```shell +# Role-based DocumentEngine (AccessControlEnumerable) +$ DOCUMENT_ENGINE_ADMIN=0xYourAdmin \ + forge script script/DeployDocumentEngine.s.sol \ + --rpc-url --private-key --broadcast + +# Owner-based DocumentEngineOwnable (Ownable2Step) +$ DOCUMENT_ENGINE_OWNER=0xYourOwner \ + forge script script/DeployDocumentEngineOwnable.s.sol \ + --rpc-url --private-key --broadcast +``` + +Drop `--broadcast` (and `--rpc-url`) for a local dry-run. The scripts are covered by [`test/Deploy.t.sol`](../test/Deploy.t.sol). + +##### Cast + +```shell +$ cast +``` + +##### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` + +## Intellectual property + +The code is copyright (c) Capital Market and Technology Association, 2018-2026, and is released under [Mozilla Public License 2.0](https://github.com/CMTA/CMTAT/blob/master/LICENSE.md). diff --git a/doc/audits/AUDIT_OVERVIEW.md b/doc/audits/AUDIT_OVERVIEW.md new file mode 100644 index 0000000..f47381c --- /dev/null +++ b/doc/audits/AUDIT_OVERVIEW.md @@ -0,0 +1,85 @@ +# Security & audit overview — DocumentEngine + +> **This project has not been audited.** No formal external security audit has been performed on any +> release. What follows is the record of the automated and AI-assisted analyses that *have* been run. +> These are **not** a substitute for an audit. Anyone deploying this engine in production must +> commission their own independent security assessment. + +## In scope + +The `src/` tree only — 9 files, 307 nSLOC as of `v0.4.0`: + +``` +src/DocumentEngine.sol src/interfaces/IERC1643MultiDocument.sol +src/DocumentEngineBase.sol src/interfaces/IERC8303.sol +src/DocumentEngineInvariant.sol src/interfaces/ITokenBinding.sol +src/DocumentEngineOwnable.sol src/modules/TokenBindingModule.sol + src/modules/VersionModule.sol +``` + +Out of scope: `lib/` (CMTAT, RuleEngine, OpenZeppelin — audited, or not, upstream), `test/`, +`script/`. + +## Analyses + +| Analysis | Version | Report | Triage | +| --- | --- | --- | --- | +| Aderyn `0.6.5` | `v0.4.0` | [report](./tools/v0.4.0/aderyn/aderyn-report.md) | [feedback](./tools/v0.4.0/aderyn/aderyn-report-feedback.md) | +| Slither `0.11.5` | `v0.4.0` | [report](./tools/v0.4.0/slither/slither-report.md) | [feedback](./tools/v0.4.0/slither/slither-report-feedback.md) | +| ERC conformance analysis (AI-assisted) | `v0.4.0` | open items: [below](#known-open-items) | — | +| Code-quality review (AI-assisted) | `v0.4.0` | [`CLAUDE_ANALYSIS.md`](./tools/v0.4.0/claude/CLAUDE_ANALYSIS.md) — 14 findings, **no vulnerability**; 6 implemented, 8 deliberately left, nothing outstanding | — | + +Both tool runs are against CMTAT `v3.3.0-rc3` and OpenZeppelin `v5.7.0`, with mocks and tests +excluded. + +## Static-analysis results + +| Tool | High | Medium | Low | Info | Anything to fix? | +| --- | --- | --- | --- | --- | --- | +| Aderyn `0.6.5` | 0 | — | 6 | 0 | **No.** 4 by design, 1 environment, 1 false positive; 1 of the "by design" instances overlaps a known scalability item (§4.7) | +| Slither `0.11.5` | 0 | 0 | 0 | 2 | **No.** Both are false positives — required `_msgData()` overrides read as dead code. (Was 4 results; the `incorrect-equality`/`timestamp` pair stopped firing when B-2 switched a memory copy to a storage pointer. Nothing was fixed — see the Slither triage.) | + +Aderyn reports no Medium or Info categories; it classifies only High and Low. + +**Neither tool found anything to fix in `v0.4.0`.** The two agree on the absence of the classic +classes — no reentrancy, no access-control gap, no uninitialised state, no unchecked external call — +which is the expected result for a contract that holds no funds and makes no external calls. They +disagree only on what is worth reporting: Slither's highest result (`incorrect-equality`, Medium) is +one Aderyn ignores, and Aderyn's loop advisories draw nothing from Slither. Each dismissal was +verified against the cited line; the `_msgData()` "dead code" was verified by deleting it and +confirming the compile fails (`Error (6480): Derived contract must override function "_msgData"`). +Slither's highest result at the time was `incorrect-equality` (Medium); it no longer fires, but as a +detector artefact rather than a fix — the triage explains why. + +Note the standing limitation: neither tool can see the specification-level issues that matter most +for this engine — those are tracked as open items below. + +## Substantive findings fixed in `v0.4.0` + +From the ERC conformance analysis (open items: [below](#known-open-items)) rather than from the +static analyzers — neither tool can see these, since both are ABI- and specification-level: + +| Finding | Severity | Status | +| --- | --- | --- | +| `getDocument` returned a `Document` struct where ERC-1643 mandates three flat values. Same selector and same `type(IERC1643).interfaceId` either way, so ERC-165 detection could not distinguish them and a spec-conformant consumer silently decoded corrupt values | High | **Fixed** — flat return on both overloads, pinned by `testGetDocumentReturnsFlatErc1643Abi`, which inspects the returndata directly | +| `ERC1643InvalidName` / `ERC1643MissingDocument` declared both locally and by `IERC1643`, which the multi-subject draft forbids and the compiler rejects | Blocker | **Fixed** — local declarations removed | +| Null-subject error named `ERC1643InvalidSubject`, after a standard in which the condition cannot occur, and declared on an abstract contract rather than an interface | Low | **Fixed** — renamed `MultiDocumentInvalidSubject` and moved to `IERC1643MultiDocument`; every specification error now sits on the interface defining its condition | +| `bindToken(address(0))` accepted, and bind/unbind emitted `TokenBindingSet` even when the binding did not change | Low | **Fixed** — null address rejected with `TokenBindingInvalidToken()`; both are now idempotent and emit only on a real transition | +| ERC-165 advertises `type(IERC1643).interfaceId`, which a token uses to check the base endpoints exist, but which a third party could misread as "read documents here" — the base functions are caller-scoped, so such a read silently returns an empty namespace | Low | **Documented** — the id is kept for the token's capability check; the caveat is stated in the README and both `supportsInterface` NatSpecs, and asserted by a test | + +## Known open items + +Not defects in the sense of being exploitable, but tracked deviations from the two specifications +this engine implements. **This table is the canonical record**; the items keep the `OPEN-n` numbering +they were first given, which is the numbering the audit reports cite. + +| ID | Item | Severity | +| --- | --- | --- | +| **OPEN-1** | `_authorizeDocumentManagement()` takes no `subject`, so a deployment cannot make authorization per-subject by overriding the hook. Conformant for the single-issuer fleet the engine targets — `DOCUMENT_MANAGER_ROLE` is permitted to manage every subject — but **one engine instance therefore serves one trust domain**: unrelated issuers should each deploy their own rather than share one. | Low (Medium if shared across unrelated issuers) | +| **OPEN-2** | Admin write path has no execution point in the subject, so an ERC-1643 subject emits nothing for a write sent straight to the engine. Point document consumers at the subject only when writes go through the bound-token path. | Medium | +| **OPEN-4** | `_removeDocumentName` is O(n); no paginated enumeration. Also surfaced by Aderyn L-5; the constant was reduced by CLAUDE_ANALYSIS B-1/B-2 but the complexity is unchanged. | Low | +| **OPEN-5** | The ERC-2771 trusted forwarder can act as any bound subject, and is immutable after construction. | Info | + +## Reporting a vulnerability + +See [`SECURITY.md`](../../SECURITY.md), or contact [admin@cmta.ch](mailto:admin@cmta.ch). diff --git a/doc/audits/tools/v0.4.0/aderyn/aderyn-report-feedback.md b/doc/audits/tools/v0.4.0/aderyn/aderyn-report-feedback.md new file mode 100644 index 0000000..35f84f7 --- /dev/null +++ b/doc/audits/tools/v0.4.0/aderyn/aderyn-report-feedback.md @@ -0,0 +1,86 @@ +# Aderyn report — triage (DocumentEngine `v0.4.0`) + +| | | +| --- | --- | +| Report | [`aderyn-report.md`](./aderyn-report.md) | +| Command | `aderyn -x mocks --output doc/audits/tools/v0.4.0/aderyn/aderyn-report.md` | +| Tool version | `aderyn 0.6.5` | +| Scope | `src/` only — 9 files, 307 nSLOC. **Mocks and tests excluded.** This project keeps its mocks (`CMTATDocumentEngineMock`, `OpenDocumentEngine`) inside `test/DocumentEngine.t.sol`, which Aderyn does not scan, so `-x mocks` matched nothing and changed nothing. | +| Dependencies | CMTAT `v3.3.0-rc3` (`658672f1`), OpenZeppelin `v5.7.0` (`cab19933`) | +| Result | **0 High · 6 Low** | +| Companion | [`../slither/slither-report.md`](../slither/slither-report.md) — Slither `0.11.5`, 2 results, also nothing to fix | + +## Executive triage + +**Nothing to fix.** No finding is exploitable, and none blocks the `v0.4.0` release. + +Five of the six are the analyzer's standing advisories about deliberate design choices (a privileged +operator, a caret pragma, PUSH0, revert-in-loop, storage-writes-in-loop) and one is a false positive. + +The one result worth keeping in view is **L-5 at `DocumentEngineBase.sol:265`**, which is not a batch +loop but the linear scan in `_removeDocumentName`. Aderyn reached it from the "costly operation in a +loop" heuristic; it happens to land on the same code as `OPEN-4` in `AUDIT_OVERVIEW.md`, which flags the O(n) +removal against the multi-subject draft's expectation of "index tracking to support O(1) removals". +That is a scalability item, not a vulnerability — a subject with a large document set makes +`removeDocument` progressively more expensive, and `batchRemoveDocuments` compounds it to O(n·m). +Two independent routes arriving at the same line is a reasonable argument for doing the index-mapping +fix in a later release. It is deliberately **not** being done in `v0.4.0`, which is scoped to the +CMTAT upgrade. + +## Findings + +| ID | Detector | Sev | Instances | Disposition | Reason (verified against the cited lines) | +| --- | --- | --- | --- | --- | --- | +| L-1 | Centralization Risk | Low | 2 | **By design** | `DocumentEngine.sol:26`, `DocumentEngineOwnable.sol:25`. The whole premise of the contract is that a trusted operator manages documents for a fleet of subjects; `DOCUMENT_MANAGER_ROLE` (and `owner`) are that operator. Documented in the README and analysed as `OPEN-1` in `AUDIT_OVERVIEW.md`, which concludes the global role is the correct model for the single-issuer fleet this engine targets. Aderyn cannot express that distinction. | +| L-2 | Unspecific Solidity Pragma | Low | 9 | **By design** | Every file uses a caret pragma, intentionally, so the sources stay consumable as a library by projects on a different `0.8.x`; the compiler actually used for the deployed bytecode is pinned to `0.8.34` in `foundry.toml`, and `foundry.lock` pins every dependency. Verified: no file uses a construct that behaves differently across the allowed range. The floor is now **`^0.8.24`**, raised from `^0.8.20` after the previous run: `^0.8.20` over-promised, because `AccessControlEnumerable.sol` and `EnumerableSet.sol` were already `^0.8.24` and CMTAT `v3.3.0-rc3` moved `draft-IERC1643.sol` there too — no compiler in `0.8.20`–`0.8.23` could build the tree. `0.8.24` is the true `src/` floor; the full project including the CMTAT-importing tests needs `0.8.27`, because `require(cond, CustomError())` is legacy-pipeline-only from that version on. | +| L-3 | PUSH0 Opcode | Low | 9 | **Environment** | Consequence of the caret pragma plus `evm_version = prague`: the compiler emits `PUSH0`, which is unavailable on chains that have not adopted Shanghai. Not a source defect. A deployer targeting such a chain must lower `evm_version` in `foundry.toml` — but CMTAT v3 itself requires `prague`, so that configuration is out of scope for this engine. | +| L-4 | Loop Contains `require`/`revert` | Low | 4 | **By design** | `DocumentEngineBase.sol:118, 141, 158, 175` — the four batch loops. The reverts are raised inside `_setDocument` / `_removeDocument` (`ERC1643InvalidName`, `MultiDocumentInvalidSubject`, `ERC1643MissingDocument`). Batch operations are deliberately **all-or-nothing**: a batch containing one bad entry must not half-apply, since partial application would leave the operator unable to tell which documents were written without re-reading every entry. Skipping bad entries instead would silently drop them. | +| L-5 | Costly operations inside loop | Low | 5 | **By design** ×4, **known item** ×1 | Four instances (`:118, 141, 158, 175`) are storage writes in the batch loops — unavoidable, and the reason the batch functions exist is to amortise the 21 000-gas transaction overhead across those writes. The fifth (`:265`) is `_removeDocumentName`'s linear scan with swap-and-pop; see the triage note above and `OPEN-4` in `AUDIT_OVERVIEW.md`. | +| L-6 | Unchecked Return | Low | 1 | **False positive** | `DocumentEngine.sol:43`, `_grantRole(DEFAULT_ADMIN_ROLE, admin);`. OpenZeppelin's `_grantRole` returns `false` only when the account already holds the role. This call is in the constructor of a freshly deployed contract, where no role has been granted yet, so it always returns `true`; `admin == address(0)` is already rejected on the preceding lines. There is no state to check and no recovery path to take. | + +## Delta + +This report was regenerated after the error-naming and token-binding fixes (error renaming and +relocation; `bindToken`/`unbindToken` null-address rejection and idempotence). **Nothing moved**: +the same six detectors fire with the same instance counts, and every cited line is unchanged. nSLOC +rose 298 → 307 for the added guard and its NatSpec. + +Worth noting explicitly, since it is a null result that is easy to misread as "not analysed": the new +`TokenBindingModule._setTokenBinding` — which adds a revert and an early return — triggered **no** +new finding, including no addition to L-4 (`revert` in a loop), because it contains no loop. + +## Delta from the previous run (dependency upgrade) + +Re-run after the `v0.4.0` dependency bump — CMTAT `v3.3.0-rc2` → `v3.3.0-rc3`, OpenZeppelin +`v5.6.1` → `v5.7.0`, and the source pragma `^0.8.20` → `^0.8.24`. + +**Nothing moved.** The same six detectors fire with the same instance counts (2 / 9 / 9 / 4 / 5 / 1), +on the same code, and nSLOC is unchanged at 307 across the same 9 files. The only textual difference +in the raw report is the pragma quoted under L-2 and L-3, which now reads `^0.8.24`. + +Both tools were then re-run a second time after the behaviour-preserving **style pass** (functions +reordered by visibility group, global imports replaced by named ones, NatSpec completed). Findings +were again identical in kind and count; only the cited line numbers shifted, and the citations in +this file and in the Slither triage were remapped to match. That the finding set survived a +wholesale reordering unchanged is itself a useful check that the reordering changed no behaviour. + +Two null results worth recording, because they are easy to misread as "not analysed": + +- **The pragma bump did not clear L-2 or L-3.** Aderyn flags the *caret*, not the floor, so raising + `^0.8.20` to `^0.8.24` leaves both counts at 9. L-3's own description still names `0.8.20` — that + is boilerplate detector text, not a reading of the current source. +- **The OpenZeppelin `EnumerableSet.at()` → `pos()` deprecation produced no finding.** This engine + has no call site of either, and its only exposure is the inherited + `AccessControlEnumerable.getRoleMember`, whose behaviour is unchanged. + +## Delta from the previous version + +None — `v0.4.0` is the **first** release with static analysis recorded. `doc/audits/` did not exist +before it. Future runs should diff against this one. + +**Slither has now been run** (`0.11.5`, 2 results, nothing to fix) — see +[`../slither/slither-report-feedback.md`](../slither/slither-report-feedback.md). This closes the +gap flagged here previously, so the next release can diff both tools. The two disagree on what is +worth reporting: Slither raised an existence-check equality and two required `_msgData()` overrides +that Aderyn ignored, while Aderyn's loop advisories (L-4, L-5) and `_grantRole` return (L-6) drew +nothing from Slither. No finding from either tool is real. diff --git a/doc/audits/tools/v0.4.0/aderyn/aderyn-report.md b/doc/audits/tools/v0.4.0/aderyn/aderyn-report.md new file mode 100644 index 0000000..767b782 --- /dev/null +++ b/doc/audits/tools/v0.4.0/aderyn/aderyn-report.md @@ -0,0 +1,330 @@ +> **Summary — generated for DocumentEngine `v0.4.0` (CMTAT `v3.3.0-rc3`, OpenZeppelin `v5.7.0`).** +> +> | | | +> | --- | --- | +> | Command | `aderyn -x mocks --output doc/audits/tools/v0.4.0/aderyn/aderyn-report.md` | +> | Tool version | `aderyn 0.6.5` | +> | Scope | `src/` only — 9 files, 307 nSLOC. **Mocks/tests excluded** (this project has no `src/mocks`; its mocks live in `test/`, which Aderyn does not scan). | +> | Result | **0 High · 6 Low · 0 Info** | +> | Verdict | **Nothing to fix.** No finding is exploitable. One (L-5 at `DocumentEngineBase.sol:265`) independently corroborates a known gas/scalability item already tracked as `OPEN-4` in `AUDIT_OVERVIEW.md`. | +> +> | ID | Detector | Sev | Instances | Assessment | +> | --- | --- | --- | --- | --- | +> | L-1 | Centralization Risk | Low | 2 | **By design** — a document manager is a privileged operator by definition | +> | L-2 | Unspecific Solidity Pragma | Low | 9 | **By design** — the caret is deliberate; the deployed compiler is pinned in `foundry.toml`. Now `^0.8.24`, the true `src/` floor | +> | L-3 | PUSH0 Opcode | Low | 9 | **Environment** — `evm_version = prague`; only relevant on chains without PUSH0 | +> | L-4 | Loop Contains `require`/`revert` | Low | 4 | **By design** — batch operations are deliberately all-or-nothing | +> | L-5 | Costly operations inside loop | Low | 5 | **By design** (4 batch loops) + **1 known item** — `_removeDocumentName` is O(n), see `OPEN-4` in `AUDIT_OVERVIEW.md` | +> | L-6 | Unchecked Return | Low | 1 | **False positive** — `_grantRole` in a constructor on a fresh contract cannot return `false` | +> +> Re-run after the code-quality review (`../claude/CLAUDE_ANALYSIS.md`). **Unchanged**: same six +> detectors, same instance counts (2/9/9/4/5/1). One line moved — L-5's fifth instance is now +> `DocumentEngineBase.sol:265`, because finding B-1 inserted a storage-pointer line above the loop. +> Notably the `virtual` keywords added by E-1 produced no new finding. +> +> Full triage, with the reasoning verified against each cited line: +> [`aderyn-report-feedback.md`](./aderyn-report-feedback.md). +> Companion Slither run: [`../slither/slither-report.md`](../slither/slither-report.md). +> Code-quality review: [`../claude/CLAUDE_ANALYSIS.md`](../claude/CLAUDE_ANALYSIS.md). +> Security overview: [`doc/audits/AUDIT_OVERVIEW.md`](../../../AUDIT_OVERVIEW.md). + +# Aderyn Analysis Report + +This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a static analysis tool built by [Cyfrin](https://cyfrin.io), a blockchain security company. This report is not a substitute for manual audit or security review. It should not be relied upon for any purpose other than to assist in the identification of potential security vulnerabilities. +# Table of Contents + +- [Summary](#summary) + - [Files Summary](#files-summary) + - [Files Details](#files-details) + - [Issue Summary](#issue-summary) +- [Low Issues](#low-issues) + - [L-1: Centralization Risk](#l-1-centralization-risk) + - [L-2: Unspecific Solidity Pragma](#l-2-unspecific-solidity-pragma) + - [L-3: PUSH0 Opcode](#l-3-push0-opcode) + - [L-4: Loop Contains `require`/`revert`](#l-4-loop-contains-requirerevert) + - [L-5: Costly operations inside loop](#l-5-costly-operations-inside-loop) + - [L-6: Unchecked Return](#l-6-unchecked-return) + + +# Summary + +## Files Summary + +| Key | Value | +| --- | --- | +| .sol Files | 9 | +| Total nSLOC | 312 | + + +## Files Details + +| Filepath | nSLOC | +| --- | --- | +| src/DocumentEngine.sol | 54 | +| src/DocumentEngineBase.sol | 151 | +| src/DocumentEngineInvariant.sol | 5 | +| src/DocumentEngineOwnable.sol | 29 | +| src/interfaces/IERC1643MultiDocument.sol | 13 | +| src/interfaces/IERC8303.sol | 4 | +| src/interfaces/ITokenBinding.sol | 8 | +| src/modules/TokenBindingModule.sol | 36 | +| src/modules/VersionModule.sol | 12 | +| **Total** | **312** | + + +## Issue Summary + +| Category | No. of Issues | +| --- | --- | +| High | 0 | +| Low | 6 | + + +# 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. + +
2 Found Instances + + +- Found in src/DocumentEngine.sol [Line: 26](../../../../../src/DocumentEngine.sol#L26) + + ```solidity + contract DocumentEngine is TokenBindingModule, VersionModule, AccessControlEnumerable, ERC2771Context { + ``` + +- Found in src/DocumentEngineOwnable.sol [Line: 25](../../../../../src/DocumentEngineOwnable.sol#L25) + + ```solidity + contract DocumentEngineOwnable is TokenBindingModule, VersionModule, Ownable2Step, ERC2771Context { + ``` + +
+ + + +## 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;` + +
9 Found Instances + + +- Found in src/DocumentEngine.sol [Line: 2](../../../../../src/DocumentEngine.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/DocumentEngineBase.sol [Line: 2](../../../../../src/DocumentEngineBase.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/DocumentEngineInvariant.sol [Line: 2](../../../../../src/DocumentEngineInvariant.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/DocumentEngineOwnable.sol [Line: 2](../../../../../src/DocumentEngineOwnable.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/interfaces/IERC1643MultiDocument.sol [Line: 2](../../../../../src/interfaces/IERC1643MultiDocument.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/interfaces/IERC8303.sol [Line: 2](../../../../../src/interfaces/IERC8303.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/interfaces/ITokenBinding.sol [Line: 2](../../../../../src/interfaces/ITokenBinding.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/modules/TokenBindingModule.sol [Line: 2](../../../../../src/modules/TokenBindingModule.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/modules/VersionModule.sol [Line: 2](../../../../../src/modules/VersionModule.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +
+ + + +## L-3: PUSH0 Opcode + +Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail. + +
9 Found Instances + + +- Found in src/DocumentEngine.sol [Line: 2](../../../../../src/DocumentEngine.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/DocumentEngineBase.sol [Line: 2](../../../../../src/DocumentEngineBase.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/DocumentEngineInvariant.sol [Line: 2](../../../../../src/DocumentEngineInvariant.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/DocumentEngineOwnable.sol [Line: 2](../../../../../src/DocumentEngineOwnable.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/interfaces/IERC1643MultiDocument.sol [Line: 2](../../../../../src/interfaces/IERC1643MultiDocument.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/interfaces/IERC8303.sol [Line: 2](../../../../../src/interfaces/IERC8303.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/interfaces/ITokenBinding.sol [Line: 2](../../../../../src/interfaces/ITokenBinding.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/modules/TokenBindingModule.sol [Line: 2](../../../../../src/modules/TokenBindingModule.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +- Found in src/modules/VersionModule.sol [Line: 2](../../../../../src/modules/VersionModule.sol#L2) + + ```solidity + pragma solidity ^0.8.24; + ``` + +
+ + + +## L-4: Loop Contains `require`/`revert` + +Avoid `require` / `revert` statements in a loop because a single bad item can cause the whole transaction to fail. It's better to forgive on fail and return failed elements post processing of the loop + +
4 Found Instances + + +- Found in src/DocumentEngineBase.sol [Line: 118](../../../../../src/DocumentEngineBase.sol#L118) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 141](../../../../../src/DocumentEngineBase.sol#L141) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 158](../../../../../src/DocumentEngineBase.sol#L158) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 175](../../../../../src/DocumentEngineBase.sol#L175) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +
+ + + +## L-5: Costly operations inside loop + +Invoking `SSTORE` operations in loops may waste gas. Use a local variable to hold the loop computation result. + +
5 Found Instances + + +- Found in src/DocumentEngineBase.sol [Line: 118](../../../../../src/DocumentEngineBase.sol#L118) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 141](../../../../../src/DocumentEngineBase.sol#L141) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 158](../../../../../src/DocumentEngineBase.sol#L158) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 175](../../../../../src/DocumentEngineBase.sol#L175) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +- Found in src/DocumentEngineBase.sol [Line: 265](../../../../../src/DocumentEngineBase.sol#L265) + + ```solidity + for (uint256 i = 0; i < length; ++i) { + ``` + +
+ + + +## L-6: Unchecked Return + +Function returns a value but it is ignored. Consider checking the return value. + +
1 Found Instances + + +- Found in src/DocumentEngine.sol [Line: 43](../../../../../src/DocumentEngine.sol#L43) + + ```solidity + _grantRole(DEFAULT_ADMIN_ROLE, admin); + ``` + +
+ + + diff --git a/doc/audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md b/doc/audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md new file mode 100644 index 0000000..6aa8cd8 --- /dev/null +++ b/doc/audits/tools/v0.4.0/claude/CLAUDE_ANALYSIS.md @@ -0,0 +1,534 @@ +# DocumentEngine — Code Quality Review + +| | | +| --- | --- | +| Scope | `src/` (9 files, 307 nSLOC) and `script/` (2 files). `lib/`, `test/` excluded except where cited as evidence. | +| Version | `v0.4.0` (unreleased) | +| Dependencies | CMTAT `v3.3.0-rc3`, OpenZeppelin `v5.7.0`, RuleEngine `v3.0.0-rc5` | +| Compiler | solc `0.8.34`, `evm_version = prague`, optimizer on (200 runs) | +| Date | 2026-08-17 | +| Produced with | Claude Code | + +> **This is a code-quality review, not a security audit.** Nothing in this report is a +> vulnerability. No finding lets an unauthorized party move value, bypass a restriction, or brick a +> contract. The one finding that *looks* like an access-control problem on first reading — **H-1**, +> revoking a role from the default admin does not remove its access — is analysed below and is not +> exploitable: the default admin can re-grant itself any role in the same transaction, so the +> "revocation" could never have been a durable restriction. Its defect is misleading feedback, not +> lost containment. +> +> For the static-analysis passes see [`aderyn-report.md`](../aderyn/aderyn-report.md) and +> [`slither-report.md`](../slither/slither-report.md); both found nothing to fix, and this review +> covers what those tools structurally cannot see. +> Security overview: [`AUDIT_OVERVIEW.md`](../../../AUDIT_OVERVIEW.md). + +## Disposition summary + +| ID | Finding | Outcome | Where | +| --- | --- | --- | --- | +| A-1 | `unchecked { ++i }` would buy nothing on this compiler | ⬜ left as is (anti-recommendation) | `DocumentEngineBase.sol:118,141,158,175,264` | +| A-2 | `string memory` on the admin `setDocument` — `calldata` is **slower** here | ⬜ left as is (measured) | `DocumentEngineBase.sol:245` | +| B-1 | Mapping slot re-hashed every iteration in `_removeDocumentName` | ✅ fixed, **−2200 gas** | `DocumentEngineBase.sol:263` | +| B-2 | `_removeDocument` copied the whole `Document` (incl. the URI) to memory | ✅ fixed, **−645 gas** | `DocumentEngineBase.sol:281` | +| C-1 | Every event has exactly one emit site | ⬜ nothing to do — verified good | — | +| C-2 | Trusted forwarder set at construction without an event | ⬜ left as is | `DocumentEngine.sol:39` | +| D-1 | ERC-2771 trio duplicated byte-for-byte across both deployments | ⬜ left as is — **extraction proven impossible** | `DocumentEngine.sol:146`, `DocumentEngineOwnable.sol:73` | +| E-1 | `virtual` coverage inconsistent between the two modules | ✅ fixed — all 12 internal functions now `virtual` | `DocumentEngineBase.sol`, `TokenBindingModule.sol`, both deployments | +| F-1 | ERC-165 interface IDs — no inherited-selector trap | ⬜ nothing to do — verified correct | `DocumentEngine.sol:114` | +| G-1 | `DocumentEngineInvariant` comment misattributes `NotBoundToken` | ✅ fixed | `DocumentEngineInvariant.sol` | +| G-2 | Contracts point at documentation paths that have already moved once | ✅ fixed — all 3 pointers removed, comments got *shorter* | `DocumentEngineBase.sol`, `IERC1643MultiDocument.sol` | +| G-3 | NatSpec block-length distribution is healthy | ⬜ nothing to do — measured | — | +| H-1 | A role cannot be revoked from the default admin, but the call succeeds | ✅ documented + regression test | `DocumentEngine.sol:72` | +| H-2 | Caller-scoped reads return an empty namespace instead of reverting | ⬜ left as is — already documented and tested | `DocumentEngineBase.sol:190` | + +Rows: 14. Fixed: 6. Left deliberately: 8. Open decisions: none. + +## Outstanding + +Nothing. Every finding is either implemented or carries a recorded decision to leave it alone. + +--- + +## A. Loops and iteration + +### A-1. `unchecked { ++i }` would buy exactly nothing — do not add it + +`DocumentEngineBase.sol:118, 141, 158, 175, 264`. All five loops already use `++i` with a bound +read once into a local: + +```solidity +uint256 length = subjects.length; +for (uint256 i = 0; i < length; ++i) { +``` + +This project compiles with solc `0.8.34`. Since **0.8.22** the compiler elides the overflow check on +a loop counter it can prove bounded, so the `unchecked` block that reviewers habitually recommend is +dead weight. Measured rather than asserted — two contracts, each with a single function of the same +name, so selector-dispatch depth cannot skew the comparison: + +| variant | gas, 100 iterations | +| --- | --- | +| `++i` | 33 005 | +| `unchecked { ++i }` | 33 005 | +| **delta** | **0** | + +**Verdict: leave.** Recorded here specifically so the next review does not re-raise it. Adding the +`unchecked` block would cost three lines of noise and buy zero gas. + +### A-2. `string memory` on the admin `setDocument` — `calldata` is measurably *worse* + +`DocumentEngineBase.sol:245`: + +```solidity +function setDocument(address subject, bytes32 name_, string memory uri_, bytes32 documentHash_) + public override onlyDocumentManager +``` + +The standing advice is that an external-only entrypoint should take `calldata`. This function +qualifies — nothing calls it internally — and the interface it overrides already declares +`string calldata`. I toggled it to `external` + `string calldata` in place and re-ran the same +harness: + +| path | `string memory` | `string calldata` | delta | +| --- | --- | --- | --- | +| new document, short URI | 107 895 | 107 944 | **+49** | +| new document, long URI (105 chars) | 194 876 | 194 925 | **+49** | +| update, long URI | 105 500 | 105 549 | **+49** | + +Consistently **49 gas worse**, and flat in URI length — so it is not the copy. The reason the +expected saving does not materialise is that `_setDocument` takes `string memory`, so the +calldata→memory copy happens either way; `calldata` only moves it and adds offset handling. + +**Verdict: leave.** This is the case where the textbook optimization is a pessimisation. Changing +`_setDocument` to take `calldata` too is not possible — the bound-token path already passes +`calldata` there and the batch paths pass array elements, so the parameter must stay `memory` for +one of its callers regardless. + +## B. Storage reads + +### B-1. The mapping slot was re-hashed on every loop iteration — **fixed, −2200 gas** + +`DocumentEngineBase.sol:262`. Before: + +```solidity +uint256 length = _documentNames[subject].length; +for (uint256 i = 0; i < length; ++i) { + if (_documentNames[subject][i] == name_) { + _documentNames[subject][i] = _documentNames[subject][length - 1]; + _documentNames[subject].pop(); +``` + +Every `_documentNames[subject]` recomputes `keccak256(subject . slot)` — a hash per access, twice per +iteration on the comparison path. Caching the array as a storage pointer computes it once: + +```solidity +bytes32[] storage names = _documentNames[subject]; +uint256 length = names.length; +for (uint256 i = 0; i < length; ++i) { + if (names[i] == name_) { +``` + +Measured on a subject holding 20 documents, toggled in place, same harness both times: + +| case | before | after | delta | +| --- | --- | --- | --- | +| target at index 19 (full scan) | 87 250 | 85 050 | **−2200** | +| target at index 0 (early exit) | 28 397 | 28 002 | −395 | + +≈116 gas per iteration. This is the function `OPEN-4` in `AUDIT_OVERVIEW.md` already flags as the O(n) +scalability hotspot, and `batchRemoveDocuments` compounds it to O(n·m), so the saving multiplies. + +**Verdict: implemented.** Note this does not change the complexity — it lowers the constant. The +index-mapping fix that would make removal O(1) remains the open item it was. + +### B-2. `_removeDocument` copied the entire struct, URI included — **fixed, −645 gas** + +`DocumentEngineBase.sol:281`. `Document memory doc = _documents[subject][name_];` copies all three +fields into memory, including the dynamic `uri` string, before the existence check. Both the check +and the event read the fields, but they can read them through a storage pointer instead: + +```solidity +Document storage doc = _documents[subject][name_]; +``` + +| case | before B-1 | after B-1 | after B-1+B-2 | total | +| --- | --- | --- | --- | --- | +| full scan | 87 250 | 85 050 | **84 405** | **−2845 (−3.3 %)** | +| early exit | 28 397 | 28 002 | **27 357** | **−1040 (−3.7 %)** | + +**The hazard, and how it was checked.** With a storage pointer the emit *must* stay before the +`delete`; move it after and the event silently logs an empty URI and a zero hash rather than +reverting. Rather than assume the suite catches that, I introduced the mutation deliberately: + +``` +[FAIL: DocumentRemovedForSubject != expected DocumentRemovedForSubject] + testRemoveDocumentEmitsForSubjectEvent() +``` + +The guard is real. Ordering restored, 73/73 passing. + +**Verdict: implemented.** Storage layout re-checked from the compiled artifacts afterwards +(`--extra-output storageLayout`, 5 non-empty entries per deployment, unchanged). + +**Side effect worth flagging, because it is easy to misread as a win.** Re-running Slither after this +change, its two highest findings — `incorrect-equality` (Medium) and `timestamp` (Low), both on this +exact line — **stopped firing**, taking its total from 4 results to 2. Nothing was fixed: the +comparison is character-for-character identical, and both were already triaged as false positives on +their merits. Slither classifies `lastModified` as timestamp-derived when it arrives via a memory +copy of the struct and loses that classification through a storage pointer. The Slither triage +records this as a detector artefact rather than a remediation, so nobody later reads the drop as a +Medium having been closed. + +## C. Events + +### C-1. Single emit site per event — verified, nothing to do + +The usual failure here is an event emitted from several places, so "every write emits" holds by +convention rather than structurally. Counted: + +| event | emit sites | +| --- | --- | +| `DocumentUpdatedForSubject` | 1 (`_setDocument`) | +| `DocumentRemovedForSubject` | 1 (`_removeDocument`) | +| `TokenBindingSet` | 1 (`_setTokenBinding`) | + +All three already funnel through a single internal writer that owns validation + write + event, and +every public entrypoint delegates to it — including the batch variants, which call `_setDocument` / +`_removeDocument` rather than re-implementing. This is the shape the check exists to recommend; it is +already in place. + +**Verdict: nothing to do.** Recorded because it is the strongest structural property in the codebase +and a future refactor should preserve it. + +### C-2. The trusted forwarder is not evented at construction + +`DocumentEngine.sol:39` / `DocumentEngineOwnable.sol:31` pass `forwarderIrrevocable` to +`ERC2771Context` and emit nothing, so a log-only indexer never sees the value. That matters more than +usual here because the forwarder can act as any bound subject (`OPEN-5` in `AUDIT_OVERVIEW.md`). + +Against that: the value is `immutable`, so it can never change and there is no sequence to +reconstruct; it is publicly readable — `trustedForwarder()` (`0x7da0a877`) and +`isTrustedForwarder(address)` are both in the ABI, confirmed with `forge inspect`; and OpenZeppelin +itself emits nothing here, so adding an event departs from upstream for a one-off value anyone can +read. + +**Verdict: leave.** A single `SLOAD`-free public getter of an immutable is adequate observability. + +## D. Duplication + +### D-1. The ERC-2771 trio is byte-identical across both deployments — and cannot be shared + +`DocumentEngine.sol:146-165` and `DocumentEngineOwnable.sol:73-92` contain `_msgSender`, `_msgData` +and `_contextSuffixLength`, **10 code lines each** (NatSpec excluded), byte-for-byte identical +(`diff` confirms). + +Both projects this codebase cites as its pattern reference do extract this: RuleEngine has +`ERC2771ModuleStandalone`, CMTAT has `ERC2771Module` plus `6_CMTATBaseERC2771.sol`, which holds the +trio and marks it `virtual`. On that evidence the obvious recommendation is "extract a shared +`ERC2771Module`". + +**I built it, and it does not compile.** First attempt — module inherits `ERC2771Context` and +declares the three overrides: + +``` +Error (2353): Invalid contract specified in override list: "Context". +Error (6480): Derived contract must override function "_msgData". + Two or more base classes define function with same name and parameter types. + --> src/DocumentEngine.sol +``` + +Second attempt — module inherits `Context, ERC2771Context`, which fixes the first error: + +``` +Error (6480): Derived contract must override function "_msgSender". +Error (6480): Derived contract must override function "_msgData". +Error (6480): Derived contract must override function "_contextSuffixLength". + --> src/DocumentEngine.sol +``` + +The reason is C3 linearization, and it is why the reference projects can do what this one cannot. +CMTAT's inheritance is a single linear chain (`1_…` → `7_`), so one base can resolve `Context` for +everything below it. Here the two deployments **diverge at the access-control base** — +`AccessControlEnumerable` in one, `Ownable2Step` in the other — and each of those brings its own +`Context` branch. Solidity requires the most-derived contract to resolve the ambiguity, so the +override must be re-stated in each deployment no matter what a shared parent does. + +**Verdict: leave.** The duplication is forced by the language, not an oversight. Ten lines is the +price of supporting two access-control models, and this entry exists so the next reviewer does not +spend the same hour discovering it. (RuleEngine's `ERC2771ModuleStandalone`, worth noting, contains +only a constructor — *not* the trio — which is consistent with this conclusion.) + +## E. `virtual` / override convention + +### E-1. The two modules disagree about what is overridable + +`CLAUDE.md` states the convention as *"restricted functions use the `onlyDocumentManager` / +`onlyBoundToken` modifiers, which delegate to overridable `internal virtual` hooks"*. Both hooks are +`virtual`, so the documented convention is met. The inconsistency is one level out: + +| contract | `virtual` | not `virtual` | +| --- | --- | --- | +| `TokenBindingModule` | `bindToken`, `unbindToken`, `isTokenBound`, `_authorizeBoundTokenDocumentManagement` | `_setTokenBinding`, `_checkTokenBound` | +| `DocumentEngineBase` | `_authorizeDocumentManagement`, `_authorizeBoundTokenDocumentManagement` (both abstract) | all 13 others — `setDocument` ×2, `removeDocument` ×2, `batchSetDocuments` ×2, `batchRemoveDocuments` ×2, `getDocument` ×2, `getAllDocuments` ×2, `_setDocument`, `_removeDocument`, `_removeDocumentName`, `_getDocument` | + +So `TokenBindingModule` exposes its whole public surface for override while hiding its internals, and +`DocumentEngineBase` does the reverse — nothing overridable but the two abstract hooks. Two modules +in one codebase, opposite conventions. That inconsistency is the finding, independent of which is +right. + +For reference, CMTAT's `DocumentERC1643Module` — the module this engine mirrors — is **5 of 5** +public/external/internal functions `virtual`. + +The consequence is concrete: a deployment cannot override `getAllDocuments` to paginate, or +`setDocument` to add a per-subject policy, even though the architecture is explicitly built around +subclassing (the suite's own `OpenDocumentEngine` demonstrates the pattern). + +Cost: **zero**, measured rather than asserted — `virtual` on an internal function is resolved +statically unless actually overridden: + +| variant | gas | +| --- | --- | +| `internal` | 885 | +| `internal virtual` | 885 | + +**Verdict: implemented — all internal functions are now `virtual`.** The maintainer chose to widen +the internal surface rather than narrow the public one, which resolves the inconsistency in the +direction CMTAT takes while leaving the external API commitment unchanged. Twelve functions gained +the keyword: + +| contract | now `virtual` | +| --- | --- | +| `DocumentEngineBase` | `_removeDocumentName`, `_removeDocument`, `_setDocument`, `_getDocument` | +| `TokenBindingModule` | `_setTokenBinding`, `_checkTokenBound` | +| `DocumentEngine` | `_msgSender`, `_msgData`, `_contextSuffixLength` | +| `DocumentEngineOwnable` | `_msgSender`, `_msgData`, `_contextSuffixLength` | + +`virtual` sits in the style-guide keyword position (visibility → mutability → `virtual` → `override`), +so the style checker still reports 0 `[modifier-order]` violations. + +**Runtime cost: zero, and this time proven on the real contracts rather than a synthetic pair.** +Runtime bytecode before and after, with the CBOR metadata trailer stripped: + +| contract | executable code before | after | | +| --- | --- | --- | --- | +| `DocumentEngine` | 7457 bytes | 7457 bytes | **byte-identical** | +| `DocumentEngineOwnable` | 6111 bytes | 6111 bytes | **byte-identical** | + +Only the metadata hash moved, because the source text changed. Solidity resolves an unoverridden +`virtual` internal call statically, so nothing reaches the runtime — consistent with the earlier +885-vs-885 synthetic measurement, now confirmed against production code. + +**The guard.** A convention-only change needs a harness that *compiles*, so +`OverridingDocumentEngine` (in `DocumentEngine.t.sol`) overrides three of the twelve — one from the +write path (`_setDocument`), one from the read path (`_getDocument`), one from the authorization path +(`_checkTokenBound`). Removing `virtual` from any of them breaks the build, verified by doing it: + +``` +Error (4334): Trying to override non-virtual function. Did you forget to add "virtual"? + --> src/modules/TokenBindingModule.sol:88:5 +``` + +A compile-only check would not catch a silently shadowed override, so +`testInternalHooksAreVirtualAndOverridesAreReached` additionally asserts each override is on the real +call path: the counter increments, the read comes back URI-tagged, and an **unbound** caller passes +the bound-token gate that would otherwise revert `NotBoundToken`. + +## F. ERC / specification conformance + +### F-1. No ERC-165 inherited-selector trap — verified correct + +The classic bug is `type(IFoo).interfaceId` covering only the selectors declared *directly* on `IFoo` +while the contract advertises it as covering inherited ones too. Checked all four interfaces: + +| interface | inherits | functions declared directly | id covers all | +| --- | --- | --- | --- | +| `IERC1643` (CMTAT) | nothing | 4 | yes | +| `IERC1643MultiDocument` | nothing (deliberately not `IERC1643`) | 4 | yes | +| `ITokenBinding` | nothing | 3 | yes | +| `IERC8303` | nothing | 1 | yes | + +Every interface is flat, so each `interfaceId` is the complete XOR of its surface and the trap cannot +arise. `IERC1643MultiDocument`'s deliberate non-inheritance of `IERC1643` — documented in its own +NatSpec — is what makes this safe, and is worth preserving for that reason as well as the one already +given. + +Sentinel handling checked too: `_setDocument` rejects `subject == address(0)` +(`MultiDocumentInvalidSubject`) and `_setTokenBinding` rejects `token == address(0)` +(`TokenBindingInvalidToken`), so `address(0)` can never become a document-holding subject. The read +paths do not re-check it, but a read against a namespace that cannot be populated returns empty and +is harmless. + +**Verdict: nothing to do.** + +## G. Code / documentation mismatch + +### G-1. `DocumentEngineInvariant` misattributes an error — **fixed** + +`DocumentEngineInvariant.sol` carried a comment mapping each specification error to the interface +that declares it: + +``` +// - `NotBoundToken(address)` → `ITokenBinding` +``` + +`NotBoundToken` is **not** declared by `ITokenBinding`. It is declared in `TokenBindingModule` +(`error NotBoundToken(address caller);`); `ITokenBinding` declares only `TokenBindingInvalidToken`. +The comment exists precisely to tell a reader where each error lives, so an incorrect entry defeats +its own purpose — and this one would send an integrator building an ABI from `ITokenBinding` looking +for a selector that is not there. + +**Verdict: implemented** — the line now names `TokenBindingModule`, with a note on why that one +differs (it is the module's own operational error, not a specification error, so no interface +declares it). + +### G-2. Three contract comments point at documentation paths — one has already broken once + +``` +src/DocumentEngineBase.sol:289 // responsibility). See doc/ERCSpecification. +src/DocumentEngineBase.sol:325 // event (see {_removeDocument} note and doc/ERCSpecification). +src/interfaces/IERC1643MultiDocument.sol:13 + * on-chain product). See `doc/ERCSpecification/erc-draft_multi_document_management.md`. +``` + +Documentation moves; deployed source does not. Someone reading verified source on a block explorer +has the comment and not the file. This is normally a theoretical risk — here it is a demonstrated +one, from this repo's own history: + +``` +1233b42 A doc/ERCSpecification/ERC-1643-proposition.md +113a348 D doc/ERCSpecification/ERC-1643-proposition.md +5d13ee0 A doc/ERCSpecification/erc-draft_multi_document_management.md +``` + +The file was added, deleted, and replaced under a different name inside four commits. A `README` +pointer to the old name survived that rename as a dangling link until it was fixed in this session. +The pointer now baked into `IERC1643MultiDocument.sol` names the *replacement*, which is one rename +away from the same fate — except that this one would be frozen in verified bytecode. + +**Verdict: implemented — all three pointers removed, and every comment came out shorter.** + +The prediction above was that the third site would need a *replacement clause written in*. That +turned out to be the wrong instinct, and the maintainer's steer — "don't put too many information in +the code" — is the correction: the sentence the pointer propped up was itself the padding. + +| site | before | after | +| --- | --- | --- | +| `DocumentEngineBase._removeDocument` | `…is the token contract's responsibility). See doc/ERCSpecification.` | pointer deleted; the preceding sentence already states the emission rule in full | +| `DocumentEngineBase._setDocument` | `(see {_removeDocument} note and doc/ERCSpecification)` | `(see the {_removeDocument} note)` — the NatSpec link resolves inside the source, so it stays | +| `IERC1643MultiDocument` header | `…(typically a token contract, but the reasoning applies to any ERC-721/ERC-1155 token, vault, or other on-chain product). See doc/ERCSpecification/erc-draft_multi_document_management.md.` | `…the address of the contract the documents belong to — any contract, not only a token.` | + +The third row is the instructive one. The clause enumerating "ERC-721/ERC-1155 token, vault, or other +on-chain product" was gesturing at a rationale that lived in the draft; the *operative* fact for +anyone implementing the interface is simply that `subject` need not be a token. Stating that in one +clause removed the pointer and the enumeration at once — the interface header went from 10 lines +to 9. Nothing was moved into the docs, because nothing needed to be: the draft +already carries the derivation, and it is now reachable only by looking for it, which is correct for +a document that may be renamed again. + +Verified: `grep -rn '\.md\|doc/\|docs/' src/` returns nothing. The two remaining hits repo-wide are in +`test/DocumentEngine.t.sol` and cite this report by bare filename plus a finding ID — the exemption +argued below, deliberately kept. + +Two exemptions deliberately **not** flagged: mocks and tests (never deployed), and citations of audit +records by bare filename (`CLAUDE_ANALYSIS.md` plus a finding ID) — those are immutable historical +records, the bare filename survives a move, and the ID carries context a comment cannot restate. The +regression test added under H-1 cites this report exactly that way, on purpose. + +### G-3. NatSpec block lengths are healthy — measured, nothing to do + +The usual finding here is a handful of 30–40 line contract headers a reader must wade through before +reaching any code. Measured across `src/`: + +| metric | value | +| --- | --- | +| NatSpec blocks | 65 | +| median | 6 lines | +| 90th percentile | 11 lines | +| max | 24 lines | +| blocks ≥ 20 lines | **1** | + +One outlier: the 24-line block on `DocumentEngine.supportsInterface`. Its content is a genuine +footgun warning — that advertising `type(IERC1643).interfaceId` is *not* an invitation to read +documents from the engine's address, because the base functions are caller-scoped and a third-party +read silently returns an empty namespace. That is a safety precondition with a non-obvious failure +mode, which is exactly what earns space in a comment. + +**Verdict: nothing to do.** Reported with the distribution attached, because "24 lines" only means +something next to a median of 6 — and here the ratio is defensible. + +## H. Weird behaviour + +### H-1. Revoking a role from the default admin succeeds but removes nothing — **documented** + +`DocumentEngine.sol:72` overrides `hasRole` so that `DEFAULT_ADMIN_ROLE` implicitly holds every role. +The existing NatSpec documented one consequence (the enumeration mismatch). It did not document this +one, which I verified by running it: + +``` +after revokeRole, hasRole(admin): TRUE +getRoleMemberCount: 0 +admin STILL wrote a document after its role was revoked +``` + +`revokeRole(DOCUMENT_MANAGER_ROLE, admin)` **succeeds**, emits `RoleRevoked`, and genuinely removes +the explicit grant — `getRoleMemberCount` drops to 0. Yet `hasRole` still answers `true`, so the +admin sails through `_checkRole` and writes a document. An operator watching events, or a dashboard +reading `getRoleMemberCount`, sees a successful revocation that did not happen. The contrast case +behaves correctly: an ordinary grantee is properly blocked after revocation. + +**Why this is a quality finding and not a vulnerability.** No privilege is gained. The default admin +is the most privileged account by construction and can call `grantRole` to restore any role in the +same transaction, so "revoking a role from the admin" could never have been a durable restriction — +only revoking `DEFAULT_ADMIN_ROLE` itself withdraws anything. The defect is that the call reports +success for something it cannot do. + +**Verdict: documented, not changed.** Making `revokeRole` revert here would deviate from +`IAccessControl` semantics and break the "admin has all roles" model the contract deliberately +adopts. Instead: +- a `WARNING:` paragraph was added to the `hasRole` NatSpec stating that a role is unrevokable from + the default admin and that only `DEFAULT_ADMIN_ROLE` itself can be withdrawn; +- `testRevokingRoleFromDefaultAdminDoesNotRemoveAccess` pins the behaviour, asserting all three + facts — `hasRole` still true, `getRoleMemberCount` zero, write still succeeds — so the surprise is + a tested property rather than a latent one. + +### H-2. Caller-scoped reads return empty rather than reverting + +`getDocument(bytes32)` and `getAllDocuments()` resolve against `_msgSender()`. A third party calling +them on the engine reads *its own* namespace: no revert, no error, just empty values. This is the +"hardcoded everything-is-fine answer" shape, and it travels — an integrator who wires a UI to the +engine address sees a document set that is silently empty rather than an error telling them they +asked the wrong contract. + +**Verdict: leave.** This is inherent to ERC-1643's single-argument signature, which has no subject +parameter; the engine cannot know which namespace a reader meant. It is already handled about as well +as it can be: stated in the README, in both `supportsInterface` NatSpec blocks, and asserted by +`testBaseERC1643IsAdvertisedButReadsAreCallerScoped` and +`testMsgSenderScopedReadReturnsEmptyForOther`. The address-scoped `getDocument(subject, name)` is the +correct entrypoint for third parties and is advertised through `IERC1643MultiDocument`. + +Recorded here so it is visible as a deliberate trade-off rather than rediscovered as a defect. + +--- + +## Verification performed + +- `forge build` — clean; `forge test` — **73/73 passing** (72 before, +1 regression test from H-1). +- `forge fmt --check` — clean. Style checker (`check_order.py`) — 0 violations across `src/` + `script/`. +- Storage layout re-read from compiled artifacts after B-1/B-2 + (`forge build --force --extra-output storageLayout`): 5 non-empty entries per deployment, unchanged. + No signature, visibility or ABI change in any finding implemented. +- All four temporary benchmark harnesses deleted; test count returned to its expected value. +- Gas figures come from `gasleft()` deltas after identical warm-ups, each variant either in its own + single-function contract (A-1, E-1) or toggled in place and re-run against the same harness + (A-2, B-1, B-2). + +## What was assumed rather than executed + +- The claim in A-1 that solc elides the bounded-counter overflow check *from 0.8.22 onwards* is the + documented compiler behaviour; what I measured is that on **0.8.34** the delta is zero. I did not + bisect the compiler versions. +- D-1's conclusion is that a shared module cannot resolve the override under C3 linearization. I + proved the two natural formulations fail to compile; I did not exhaustively enumerate every + possible inheritance arrangement. +- H-2's reach ("an integrator who wires a UI to the engine address") is reasoning about consumer + behaviour, not something observed. diff --git a/doc/audits/tools/v0.4.0/slither/slither-report-feedback.md b/doc/audits/tools/v0.4.0/slither/slither-report-feedback.md new file mode 100644 index 0000000..6f511e5 --- /dev/null +++ b/doc/audits/tools/v0.4.0/slither/slither-report-feedback.md @@ -0,0 +1,90 @@ +# Slither report — triage (DocumentEngine `v0.4.0`) + +| | | +| --- | --- | +| Report | [`slither-report.md`](./slither-report.md) | +| Command | `slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks"` | +| Tool version | `slither 0.11.5` | +| Scope | `src/` only — 28 contracts analysed with 101 detectors. **Mocks and tests excluded.** This project keeps its mocks (`CMTATDocumentEngineMock`, `OpenDocumentEngine`) inside `test/DocumentEngine.t.sol`, which the `test` filter removes; there is no `src/mocks`, so the `mocks` filter entry matched nothing. | +| Dependencies | CMTAT `v3.3.0-rc3` (`658672f1`), OpenZeppelin `v5.7.0` (`cab19933`) | +| Result | **0 High · 0 Medium · 0 Low · 2 Informational** (2 results) — was 4; see the correction below | + +## Executive triage + +**Nothing to fix.** No finding is exploitable, and none blocks the `v0.4.0` release. + +Both remaining results are the same thing: **`dead-code` ×2** flags the `_msgData()` overrides. +These are not dead — Solidity **requires** them. Verified by deleting one and compiling: +`Error (6480): Derived contract must override function "_msgData". Two or more base classes define +function with same name and parameter types.` + +### Correction — two findings disappeared, and not because anything was fixed + +The previous run of this report carried two further results, both on +`_removeDocument`'s `doc.lastModified == 0`: + +| ID | Detector | Sev | Status now | +| --- | --- | --- | --- | +| (was ID-0) | `incorrect-equality` | Medium | **No longer reported** | +| (was ID-1) | `timestamp` | Low | **No longer reported** | + +They stopped firing when the code-quality review's finding B-2 changed +`Document memory doc = _documents[subject][name_]` to `Document storage doc = …` — a gas +optimisation that left the comparison character-for-character identical. Slither's taint tracking +classifies `lastModified` as timestamp-derived when it arrives via a memory copy of the struct, and +apparently loses that classification when the field is read through a storage pointer. + +**Nothing was fixed.** The original triage (retained below) established both as false positives on +their merits; their disappearance is a detector artefact, not an improvement, and the same reasoning +would apply verbatim if a future Slither release started reporting them again. Recorded here rather +than deleted, because a reader comparing "4 results" against "2 results" across the two runs would +otherwise conclude a Medium had been remediated. + +**The original triage, still the operative reasoning if these ever return.** Slither's +`incorrect-equality` detector targets strict equality against a *quantity that can step past the +compared value* — a balance that can be donated to, or a timestamp compared with `==` where a block +can skip the exact second. Neither shape applies: `0` is not a point on a timeline the value passes +through, it is the default of an unwritten struct. `lastModified` is only ever assigned +`block.timestamp`, which is non-zero on every live chain, so a *stored* document can never read back +as `0`; the comparison is a total existence test, and the ERC-1643 spec requires the +revert-on-missing behaviour it implements. Covered by `testCannotRemoveMissingDocument`. The +`timestamp` detector's concern — a validator nudging `block.timestamp` to flip a branch — needs an +ordering comparison; there is none here, and no achievable manipulation sets `block.timestamp` to +`0`. + +## Findings + +| ID | Detector | Sev | Conf | Instances | Disposition | Reason (verified against the cited lines) | +| --- | --- | --- | --- | --- | --- | --- | +| ID-0 | `dead-code` | Info | Medium | 1 | **False positive — required override** | `DocumentEngine.sol:155-157`, `_msgData()`. `DocumentEngine` inherits `Context` through two paths (`AccessControlEnumerable` → `AccessControl` → `Context`, and `ERC2771Context` → `Context`), and `ERC2771Context` overrides `_msgData()`. Solidity therefore demands an explicit `override(ERC2771Context, Context)` in the derived contract. **Verified empirically:** removing the function fails to compile with `Error (6480): Derived contract must override function "_msgData"`. Slither reports it "never used" because nothing in this project calls `_msgData()` directly — but it is what makes ERC-2771 calldata handling correct for any inherited code that does. | +| ID-1 | `dead-code` | Info | Medium | 1 | **False positive — required override** | `DocumentEngineOwnable.sol:82-84`. Identical to ID-0, via `Ownable2Step` → `Ownable` → `Context` and `ERC2771Context` → `Context`. | + +## What Slither did *not* flag + +Worth recording, since absences are easy to misread as "not analysed". With 101 detectors over the +full `src/` tree, Slither reported **no** reentrancy, no access-control gap, no uninitialised state, +no unchecked external call, no shadowing, and no arbitrary-`from` issue. That is the expected result +for this contract — the engine holds no funds, makes no external calls, and every state-changing +entry point is behind `onlyDocumentManager` or `onlyBoundToken`. + +Note also that Slither did **not** reproduce Aderyn's L-6 (`_grantRole` return value ignored) or its +loop advisories (L-4, L-5). The two tools disagree on what is worth reporting rather than on the +facts; every one of those is triaged in the Aderyn feedback file. + +## Delta from the previous version + +None — this is the **first** Slither run recorded for this repository. `v0.4.0`'s earlier audit pass +ran Aderyn only, and the `doc/audits/tools/v0.4.0/slither/` directory did not exist. There is no +baseline to diff against; future runs should diff against this one. + +Two notes for whoever runs it next: + +1. **Use `lib` as the dependency filter, not individual submodule names.** This is a Foundry project, + so every dependency lives under `lib/`. The command previously documented in the README — + `--filter-paths "node_modules,test,forge-std,CMTAT,openzeppelin-contracts"` — names submodules + individually and omits `lib/RuleEngine` entirely. Slither's `--filter-paths` fails *open*: an + entry that matches nothing silently widens scope rather than erroring. The README has been + updated to the `lib` form used here. +2. **Slither writes the checklist to stdout and its detector log to stderr, and exits non-zero when + it finds anything.** `exit=255` with a populated report is the normal, successful outcome — do not + read it as a failed run. diff --git a/doc/audits/tools/v0.4.0/slither/slither-report.md b/doc/audits/tools/v0.4.0/slither/slither-report.md new file mode 100644 index 0000000..7278cdc --- /dev/null +++ b/doc/audits/tools/v0.4.0/slither/slither-report.md @@ -0,0 +1,52 @@ +> **Summary — generated for DocumentEngine `v0.4.0` (CMTAT `v3.3.0-rc3`, OpenZeppelin `v5.7.0`).** +> +> | | | +> | --- | --- | +> | Command | `slither . --checklist --filter-paths "node_modules,lib,test,forge-std,mocks"` | +> | Tool version | `slither 0.11.5` | +> | Scope | `src/` only — 28 contracts analysed with 101 detectors (the count includes inherited OpenZeppelin/CMTAT contracts pulled in by the compiler; findings are filtered to project sources). **Mocks/tests excluded** — this project's mocks (`CMTATDocumentEngineMock`, `OpenDocumentEngine`, `OverridingDocumentEngine`) live in `test/DocumentEngine.t.sol`, which the `test` filter removes. | +> | Result | **0 High · 0 Medium · 0 Low · 2 Informational** (2 results) | +> | Verdict | **Nothing to fix.** Both results are required Solidity overrides misread as dead code. | +> +> | Detector | Severity | Confidence | Instances | Assessment | +> | --- | --- | --- | --- | --- | +> | `dead-code` | Informational | Medium | 2 | **False positive** — `_msgData()` is a *mandatory* override; removing it fails to compile (verified) | +> +> **Changed since the previous run — read this before comparing counts.** This report has 2 results +> where the previous run had 4. The `incorrect-equality` (Medium) and `timestamp` (Low) findings on +> `_removeDocument`'s `doc.lastModified == 0` no longer fire, because the code-quality review's +> finding B-2 changed `Document memory doc` to `Document storage doc`. **Nothing was fixed by that** — +> the comparison is character-for-character the same and was a false positive to begin with (see the +> feedback file). Slither's taint tracking simply stops classifying the value as timestamp-derived +> when it is read through a storage pointer instead of a memory copy. Do not read the drop from 4 to +> 2 as a security improvement; it is a detector artefact. +> +> **Scope check:** `grep -c 'lib/\|node_modules/'` over the tool output below returns **0** — no +> dependency code is in scope. This is a Foundry project, so the dependency filter entry is `lib`; +> note this differs from the command previously documented in the README, which listed individual +> submodule names and would have left `lib/RuleEngine` unfiltered. +> +> Full triage, with the reasoning verified against each cited line: +> [`slither-report-feedback.md`](./slither-report-feedback.md). +> Companion Aderyn run: [`../aderyn/aderyn-report.md`](../aderyn/aderyn-report.md). +> Code-quality review: [`../claude/CLAUDE_ANALYSIS.md`](../claude/CLAUDE_ANALYSIS.md). +> Security overview: [`doc/audits/AUDIT_OVERVIEW.md`](../../../AUDIT_OVERVIEW.md). + +**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results. +Summary + - [dead-code](#dead-code) (2 results) (Informational) +## dead-code +Impact: Informational +Confidence: Medium + - [ ] ID-0 +[DocumentEngine._msgData()](src/DocumentEngine.sol#L155-L157) is never used and should be removed + +src/DocumentEngine.sol#L155-L157 + + + - [ ] ID-1 +[DocumentEngineOwnable._msgData()](src/DocumentEngineOwnable.sol#L82-L84) is never used and should be removed + +src/DocumentEngineOwnable.sol#L82-L84 + + diff --git a/doc/coverage/coverage/index-sort-f.html b/doc/coverage/coverage/index-sort-f.html index 7e88c84..08e80aa 100644 --- a/doc/coverage/coverage/index-sort-f.html +++ b/doc/coverage/coverage/index-sort-f.html @@ -31,27 +31,18 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 125 + 131 + 95.4 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 - 85.7 % - - - - - - Branches: - 13 - 14 - 92.9 % + 36 + 40 + 90.0 % @@ -65,33 +56,38 @@ - - - - - - - - + + + + + + - + + + + + + + + - - - - - - + + + +


Directory Sort by name Line Coverage Sort by line coverage Functions Sort by function coverageBranches Sort by branch coverage
src -
98.1%98.1%
+
94.4%94.4%
+
94.4 %101 / 10787.5 %28 / 32
src/modules +
100.0%
98.1 %51 / 5285.7 %12 / 1492.9 %13 / 14100.0 %24 / 24100.0 %8 / 8
diff --git a/doc/coverage/coverage/index-sort-l.html b/doc/coverage/coverage/index-sort-l.html index 00b9318..8a6d349 100644 --- a/doc/coverage/coverage/index-sort-l.html +++ b/doc/coverage/coverage/index-sort-l.html @@ -31,27 +31,18 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 125 + 131 + 95.4 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 - 85.7 % - - - - - - Branches: - 13 - 14 - 92.9 % + 36 + 40 + 90.0 % @@ -65,33 +56,38 @@ - - - - - - - - + + + + + + - + + + + + + + + - - - - - - + + + +


Directory Sort by name Line Coverage Sort by line coverage Functions Sort by function coverageBranches Sort by branch coverage
src -
98.1%98.1%
+
94.4%94.4%
+
94.4 %101 / 10787.5 %28 / 32
src/modules +
100.0%
98.1 %51 / 5285.7 %12 / 1492.9 %13 / 14100.0 %24 / 24100.0 %8 / 8
diff --git a/doc/coverage/coverage/index.html b/doc/coverage/coverage/index.html index 138d820..0278b86 100644 --- a/doc/coverage/coverage/index.html +++ b/doc/coverage/coverage/index.html @@ -31,27 +31,18 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 125 + 131 + 95.4 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 - 85.7 % - - - - - - Branches: - 13 - 14 - 92.9 % + 36 + 40 + 90.0 % @@ -65,33 +56,38 @@ - - - - - - - - + + + + + + - + + + + + + + + - - - - - - + + + +


Directory Sort by name Line Coverage Sort by line coverage Functions Sort by function coverageBranches Sort by branch coverage
src -
98.1%98.1%
+
94.4%94.4%
+
94.4 %101 / 10787.5 %28 / 32
src/modules +
100.0%
98.1 %51 / 5285.7 %12 / 1492.9 %13 / 14100.0 %24 / 24100.0 %8 / 8
diff --git a/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html b/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html index 935fc26..d0559b5 100644 --- a/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/DocumentEngine.sol.func-sort-c.html @@ -31,28 +31,19 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 17 + 19 + 89.5 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 + 6 + 7 85.7 % - - - - - Branches: - 13 - 14 - 92.9 % - @@ -69,60 +60,32 @@ Hit count Sort by hit count - DocumentEngine._msgData + DocumentEngine._msgData 0 - DocumentEngine.hasRole - 0 - - - DocumentEngine.removeDocument - 2 - - - DocumentEngine._removeDocument - 5 - - - DocumentEngine._removeDocumentName - 5 - - - DocumentEngine.batchRemoveDocuments - 7 - - - DocumentEngine.getAllDocuments + DocumentEngine.supportsInterface 8 - DocumentEngine.batchSetDocuments - 13 - - - DocumentEngine._getDocument - 20 - - - DocumentEngine.getDocument - 20 + DocumentEngine.constructor + 64 - DocumentEngine.setDocument - 28 + DocumentEngine.hasRole + 68 - DocumentEngine._setDocument - 39 + DocumentEngine._authorizeDocumentManagement + 882 - DocumentEngine._contextSuffixLength - 50 + DocumentEngine._contextSuffixLength + 966 - DocumentEngine._msgSender - 50 + DocumentEngine._msgSender + 966
diff --git a/doc/coverage/coverage/src/DocumentEngine.sol.func.html b/doc/coverage/coverage/src/DocumentEngine.sol.func.html index 4139274..5b6f1a0 100644 --- a/doc/coverage/coverage/src/DocumentEngine.sol.func.html +++ b/doc/coverage/coverage/src/DocumentEngine.sol.func.html @@ -31,28 +31,19 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 17 + 19 + 89.5 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 + 6 + 7 85.7 % - - - - - Branches: - 13 - 14 - 92.9 % - @@ -69,61 +60,33 @@ Hit count Sort by hit count - DocumentEngine._contextSuffixLength - 50 + DocumentEngine._authorizeDocumentManagement + 882 - DocumentEngine._getDocument - 20 + DocumentEngine._contextSuffixLength + 966 - DocumentEngine._msgData + DocumentEngine._msgData 0 - DocumentEngine._msgSender - 50 - - - DocumentEngine._removeDocument - 5 - - - DocumentEngine._removeDocumentName - 5 - - - DocumentEngine._setDocument - 39 + DocumentEngine._msgSender + 966 - DocumentEngine.batchRemoveDocuments - 7 + DocumentEngine.constructor + 64 - DocumentEngine.batchSetDocuments - 13 + DocumentEngine.hasRole + 68 - DocumentEngine.getAllDocuments + DocumentEngine.supportsInterface 8 - - DocumentEngine.getDocument - 20 - - - DocumentEngine.hasRole - 0 - - - DocumentEngine.removeDocument - 2 - - - DocumentEngine.setDocument - 28 -
diff --git a/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html b/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html index f77a5a3..464d555 100644 --- a/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html +++ b/doc/coverage/coverage/src/DocumentEngine.sol.gcov.html @@ -31,28 +31,19 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 17 + 19 + 89.5 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 + 6 + 7 85.7 % - - - - - Branches: - 13 - 14 - 92.9 % - @@ -67,304 +58,174 @@ -
           Branch data     Line data    Source code
+
          Line data    Source code
-       1                 :            : //SPDX-License-Identifier: MPL-2.0
-       2                 :            : pragma solidity ^0.8.20;
-       3                 :            : 
-       4                 :            : import "OZ/access/AccessControl.sol";
-       5                 :            : import "OZ/metatx/ERC2771Context.sol";
-       6                 :            : import "CMTAT/interfaces/engine/draft-IERC1643.sol";
-       7                 :            : import "./DocumentEngineInvariant.sol";
-       8                 :            : 
-       9                 :            : /**
-      10                 :            :  * @title DocumentEngine
-      11                 :            :  * @notice contract to manage documents on-chain through ERC-1643
-      12                 :            :  */
-      13                 :            : contract DocumentEngine is
-      14                 :            :     IERC1643,
-      15                 :            :     DocumentEngineInvariant,
-      16                 :            :     AccessControl,
-      17                 :            :     ERC2771Context
-      18                 :            : {
-      19                 :            :     /**
-      20                 :            :      * @notice
-      21                 :            :      * Get the current version of the smart contract
-      22                 :            :      */
-      23                 :            :     string public constant VERSION = "0.3.0";
-      24                 :            :     // Mapping from contract addresses to document names to their corresponding Document structs
-      25                 :            :     mapping(address => mapping(bytes32 => Document)) private _documents;
-      26                 :            :     mapping(address => bytes32[]) private _documentNames;
-      27                 :            : 
-      28                 :            :     // Constructor to initialize the admin role
-      29                 :            :     constructor(
-      30                 :            :         address admin,
-      31                 :            :         address forwarderIrrevocable
-      32                 :            :     ) ERC2771Context(forwarderIrrevocable) {
-      33                 :            :         if (admin == address(0)) {
-      34                 :            :             revert AdminWithAddressZeroNotAllowed();
-      35                 :            :         }
-      36                 :            :         _grantRole(DEFAULT_ADMIN_ROLE, admin);
-      37                 :            :     }
-      38                 :            : 
-      39                 :            :     /*//////////////////////////////////////////////////////////////
-      40                 :            :                             PUBLIC/EXTERNAL FUNCTIONS
-      41                 :            :     //////////////////////////////////////////////////////////////*/
-      42                 :            : 
-      43                 :            :     /**
-      44                 :            :      * @notice Restricted function to set or update a document
-      45                 :            :      */
-      46                 :            :     function setDocument(
-      47                 :            :         address smartContract,
-      48                 :            :         bytes32 name_,
-      49                 :            :         string memory uri_,
-      50                 :            :         bytes32 documentHash_
-      51                 :            :     ) public onlyRole(DOCUMENT_MANAGER_ROLE) {
-      52                 :         54 :         _setDocument(smartContract, name_, uri_, documentHash_);
-      53                 :            :     }
-      54                 :            : 
-      55                 :            :     /**
-      56                 :            :      * @notice Restricted function to remove a document for a given smart contract and name
-      57                 :            :      */
-      58                 :            :     function removeDocument(
-      59                 :            :         address smartContract,
-      60                 :            :         bytes32 name_
-      61                 :            :     ) external onlyRole(DOCUMENT_MANAGER_ROLE) {
-      62                 :          2 :         _removeDocument(smartContract, name_);
-      63                 :            :     }
-      64                 :            : 
-      65                 :            :     /**
-      66                 :            :      * @notice Batch version of setDocument to handle multiple documents at once
-      67                 :            :      */
-      68                 :            :     function batchSetDocuments(
-      69                 :            :         address[] calldata smartContracts,
-      70                 :            :         bytes32[] calldata names,
-      71                 :            :         string[] calldata uris,
-      72                 :            :         bytes32[] calldata hashes
-      73                 :            :     ) external onlyRole(DOCUMENT_MANAGER_ROLE) {
-      74         [ +  + ]:            :         if (
-      75                 :         35 :             smartContracts.length == 0 ||
-      76                 :         12 :             smartContracts.length != names.length ||
-      77                 :         10 :             names.length != uris.length ||
-      78                 :          8 :             uris.length != hashes.length
-      79                 :            :         ) {
-      80                 :          6 :             revert InvalidInputLength();
-      81                 :            :         }
-      82                 :         28 :         for (uint256 i = 0; i < smartContracts.length; i++) {
-      83                 :         16 :             _setDocument(smartContracts[i], names[i], uris[i], hashes[i]);
-      84                 :            :         }
-      85                 :            :     }
-      86                 :            : 
-      87                 :            :     /**
-      88                 :            :      * @notice Batch version of setDocument to handle multiple documents at once
-      89                 :            :      */
-      90                 :            :     function batchSetDocuments(
-      91                 :            :         address smartContract,
-      92                 :            :         bytes32[] calldata names,
-      93                 :            :         string[] calldata uris,
-      94                 :            :         bytes32[] calldata hashes
-      95                 :            :     ) external onlyRole(DOCUMENT_MANAGER_ROLE) {
-      96         [ +  + ]:            :         if (
-      97                 :         16 :             names.length == 0 ||
-      98                 :          6 :             names.length != uris.length ||
-      99                 :          4 :             uris.length != hashes.length
-     100                 :            :         ) {
-     101                 :          4 :             revert InvalidInputLength();
-     102                 :            :         }
-     103                 :         14 :         for (uint256 i = 0; i < names.length; ++i) {
-     104                 :          8 :             _setDocument(smartContract, names[i], uris[i], hashes[i]);
-     105                 :            :         }
-     106                 :            :     }
-     107                 :            : 
-     108                 :            :     /**
-     109                 :            :      * @notice Batch version of removeDocument to handle multiple documents at once
-     110                 :            :      */
-     111                 :            :     function batchRemoveDocuments(
-     112                 :            :         address[] calldata smartContracts,
-     113                 :            :         bytes32[] calldata names
-     114                 :            :     ) external onlyRole(DOCUMENT_MANAGER_ROLE) {
-     115         [ +  + ]:            :         if (
-     116                 :          9 :             smartContracts.length == 0 ||
-     117                 :            :             (smartContracts.length != names.length)
-     118                 :            :         ) {
-     119                 :          4 :             revert InvalidInputLength();
-     120                 :            :         }
-     121                 :            : 
-     122                 :          7 :         for (uint256 i = 0; i < smartContracts.length; ++i) {
-     123                 :          4 :             _removeDocument(smartContracts[i], names[i]);
-     124                 :            :         }
-     125                 :            :     }
-     126                 :            : 
-     127                 :            :     /**
-     128                 :            :      * @notice Batch version of removeDocument to handle multiple documents at once
-     129                 :            :      */
-     130                 :            :     function batchRemoveDocuments(
-     131                 :            :         address smartContract,
-     132                 :            :         bytes32[] calldata names
-     133                 :            :     ) external onlyRole(DOCUMENT_MANAGER_ROLE) {
-     134         [ +  + ]:          4 :         if (names.length == 0) {
-     135                 :          2 :             revert InvalidInputLength();
-     136                 :            :         }
-     137                 :            : 
-     138                 :          7 :         for (uint256 i = 0; i < names.length; ++i) {
-     139                 :          4 :             _removeDocument(smartContract, names[i]);
-     140                 :            :         }
-     141                 :            :     }
-     142                 :            : 
-     143                 :            :     /**
-     144                 :            :      * @notice Public function to get a document from msg.sender
-     145                 :            :      */
-     146                 :            :     function getDocument(
-     147                 :            :         bytes32 name_
-     148                 :            :     ) external view override returns (string memory, bytes32, uint256) {
-     149                 :          3 :         return _getDocument(msg.sender, name_);
-     150                 :            :     }
-     151                 :            : 
-     152                 :            :     /**
-     153                 :            :      * @notice Public function to get a document for a specific contract address
-     154                 :            :      */
-     155                 :            :     function getDocument(
-     156                 :            :         address smartContract,
-     157                 :            :         bytes32 name_
-     158                 :            :     ) external view returns (string memory, bytes32, uint256) {
-     159                 :         57 :         return _getDocument(smartContract, name_);
-     160                 :            :     }
-     161                 :            : 
-     162                 :            :     /**
-     163                 :            :      * @notice Get all document names for msg.sender
-     164                 :            :      */
-     165                 :            :     function getAllDocuments()
-     166                 :            :         external
-     167                 :            :         view
-     168                 :            :         override
-     169                 :            :         returns (bytes32[] memory)
-     170                 :            :     {
-     171                 :          2 :         return _documentNames[msg.sender];
-     172                 :            :     }
-     173                 :            : 
-     174                 :            :     /**
-     175                 :            :      * @notice Get all document names for a specific smart contract
-     176                 :            :      */
-     177                 :            :     function getAllDocuments(
-     178                 :            :         address smartContract
-     179                 :            :     ) external view returns (bytes32[] memory) {
-     180                 :         14 :         return _documentNames[smartContract];
-     181                 :            :     }
-     182                 :            : 
-     183                 :            :     /* ============ ACCESS CONTROL ============ */
-     184                 :            :     /*
-     185                 :            :      * @dev Returns `true` if `account` has been granted `role`.
-     186                 :            :      */
-     187                 :            :     function hasRole(
-     188                 :            :         bytes32 role,
-     189                 :            :         address account
-     190                 :            :     ) public view virtual override returns (bool) {
-     191                 :            :         // The Default Admin has all roles
-     192         [ +  + ]:        100 :         if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) {
-     193                 :         88 :             return true;
-     194                 :            :         }
-     195                 :         18 :         return AccessControl.hasRole(role, account);
-     196                 :            :     }
-     197                 :            : 
-     198                 :            :     /*//////////////////////////////////////////////////////////////
-     199                 :            :                             INTERNAL FUNCTIONS
-     200                 :            :     //////////////////////////////////////////////////////////////*/
-     201                 :            : 
-     202                 :            :     /**
-     203                 :            :      * @dev Internal function to fetch a document
-     204                 :            :      */
-     205                 :            :     function _getDocument(
-     206                 :            :         address smartContract,
-     207                 :            :         bytes32 name_
-     208                 :            :     ) internal view returns (string memory, bytes32, uint256) {
-     209                 :         40 :         Document memory doc = _documents[smartContract][name_];
-     210                 :         40 :         return (doc.uri, doc.documentHash, doc.lastModified);
-     211                 :            :     }
-     212                 :            : 
-     213                 :            :     /**
-     214                 :            :      * @dev Internal helper to remove the document name from the list of document names
-     215                 :            :      */
-     216                 :            :     function _removeDocumentName(
-     217                 :            :         address smartContract,
-     218                 :            :         bytes32 name_
-     219                 :            :     ) internal {
-     220                 :         10 :         uint256 length = _documentNames[smartContract].length;
-     221                 :         15 :         for (uint256 i = 0; i < length; ++i) {
-     222         [ #  + ]:         10 :             if (_documentNames[smartContract][i] == name_) {
-     223                 :         10 :                 _documentNames[smartContract][i] = _documentNames[
-     224                 :            :                     smartContract
-     225                 :            :                 ][length - 1];
-     226                 :         10 :                 _documentNames[smartContract].pop();
-     227                 :         10 :                 break;
-     228                 :            :             }
-     229                 :            :         }
-     230                 :            :     }
-     231                 :            : 
-     232                 :            :     function _removeDocument(address smartContract, bytes32 name_) internal {
-     233                 :         10 :         Document memory doc = _documents[smartContract][name_];
-     234                 :         10 :         emit DocumentRemoved(smartContract, name_, doc.uri, doc.documentHash);
-     235                 :            : 
-     236                 :         10 :         delete _documents[smartContract][name_];
-     237                 :         10 :         _removeDocumentName(smartContract, name_);
-     238                 :            :     }
-     239                 :            : 
-     240                 :            :     function _setDocument(
-     241                 :            :         address smartContract,
-     242                 :            :         bytes32 name_,
-     243                 :            :         string memory uri_,
-     244                 :            :         bytes32 documentHash_
-     245                 :            :     ) internal {
-     246                 :         78 :         Document storage doc = _documents[smartContract][name_];
-     247         [ +  + ]:         78 :         if (doc.lastModified == 0) {
-     248                 :            :             // new document
-     249                 :         60 :             _documentNames[smartContract].push(name_);
-     250                 :            :         }
-     251                 :         78 :         doc.uri = uri_;
-     252                 :         78 :         doc.documentHash = documentHash_;
-     253                 :         78 :         doc.lastModified = block.timestamp;
-     254                 :         78 :         emit DocumentUpdated(smartContract, name_, uri_, documentHash_);
-     255                 :            :     }
-     256                 :            : 
-     257                 :            :     /*//////////////////////////////////////////////////////////////
-     258                 :            :                            ERC2771
-     259                 :            :     //////////////////////////////////////////////////////////////*/
-     260                 :            : 
-     261                 :            :     /**
-     262                 :            :      * @dev This surcharge is not necessary if you do not use ERC2771
-     263                 :            :      */
-     264                 :            :     function _msgSender()
-     265                 :            :         internal
-     266                 :            :         view
-     267                 :            :         override(ERC2771Context, Context)
-     268                 :            :         returns (address sender)
-     269                 :            :     {
-     270                 :        150 :         return ERC2771Context._msgSender();
-     271                 :            :     }
-     272                 :            : 
-     273                 :            :     /**
-     274                 :            :      * @dev This surcharge is not necessary if you do not use ERC2771
-     275                 :            :      */
-     276                 :            :     function _msgData()
-     277                 :            :         internal
-     278                 :            :         view
-     279                 :            :         override(ERC2771Context, Context)
-     280                 :            :         returns (bytes calldata)
-     281                 :            :     {
-     282                 :          0 :         return ERC2771Context._msgData();
-     283                 :            :     }
-     284                 :            : 
-     285                 :            :     /**
-     286                 :            :      * @dev This surcharge is not necessary if you do not use the MetaTxModule
-     287                 :            :      */
-     288                 :            :     function _contextSuffixLength()
-     289                 :            :         internal
-     290                 :            :         view
-     291                 :            :         override(ERC2771Context, Context)
-     292                 :            :         returns (uint256)
-     293                 :            :     {
-     294                 :        150 :         return ERC2771Context._contextSuffixLength();
-     295                 :            :     }
-     296                 :            : }
+       1             : //SPDX-License-Identifier: MPL-2.0
+       2             : pragma solidity ^0.8.24;
+       3             : 
+       4             : import {AccessControl} from "OZ/access/AccessControl.sol";
+       5             : import {AccessControlEnumerable} from "OZ/access/extensions/AccessControlEnumerable.sol";
+       6             : import {IAccessControl} from "OZ/access/IAccessControl.sol";
+       7             : import {Context} from "OZ/utils/Context.sol";
+       8             : import {ERC2771Context} from "OZ/metatx/ERC2771Context.sol";
+       9             : import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol";
+      10             : import {IERC1643MultiDocument} from "./interfaces/IERC1643MultiDocument.sol";
+      11             : import {ITokenBinding} from "./interfaces/ITokenBinding.sol";
+      12             : import {TokenBindingModule} from "./modules/TokenBindingModule.sol";
+      13             : import {VersionModule} from "./modules/VersionModule.sol";
+      14             : 
+      15             : /**
+      16             :  * @title DocumentEngine
+      17             :  * @notice Deployment contract to manage documents on-chain through ERC-1643.
+      18             :  * @dev Wires the document-management logic ({DocumentEngineBase}) with a
+      19             :  * concrete access-control implementation. The authorization hooks are defined
+      20             :  * here (role-based `AccessControlEnumerable`, which additionally allows
+      21             :  * enumerating role members), keeping the access control separate from the
+      22             :  * document-management logic (CMTAT / CMTA-RuleEngine pattern). The contract
+      23             :  * version is exposed through the {VersionModule} (ERC-8303), and it also wires
+      24             :  * the ERC-2771 (gasless) meta-transaction support.
+      25             :  */
+      26             : contract DocumentEngine is TokenBindingModule, VersionModule, AccessControlEnumerable, ERC2771Context {
+      27             :     /**
+      28             :      * @notice Role allowed to manage documents on behalf of any smart contract, and to
+      29             :      * bind/unbind tokens (admin path).
+      30             :      * @dev Token binding uses the shared allowlist in {TokenBindingModule}, not a dedicated role.
+      31             :      */
+      32             :     bytes32 public constant DOCUMENT_MANAGER_ROLE = keccak256("DOCUMENT_MANAGER_ROLE");
+      33             : 
+      34             :     /**
+      35             :      * @notice Deploys the engine and grants `admin` the default admin role.
+      36             :      * @param admin address granted `DEFAULT_ADMIN_ROLE`; must not be the null address
+      37             :      * @param forwarderIrrevocable address of the ERC-2771 forwarder (gasless support)
+      38             :      */
+      39          64 :     constructor(address admin, address forwarderIrrevocable) ERC2771Context(forwarderIrrevocable) {
+      40          64 :         if (admin == address(0)) {
+      41           1 :             revert AdminWithAddressZeroNotAllowed();
+      42             :         }
+      43          63 :         _grantRole(DEFAULT_ADMIN_ROLE, admin);
+      44             :     }
+      45             : 
+      46             :     /*//////////////////////////////////////////////////////////////
+      47             :                         ACCESS CONTROL (public surface)
+      48             :     //////////////////////////////////////////////////////////////*/
+      49             : 
+      50             :     /**
+      51             :      * @notice Returns whether `account` holds `role`.
+      52             :      * @dev Returns `true` if `account` has been granted `role`. The default admin
+      53             :      * (`DEFAULT_ADMIN_ROLE`) is treated as holding **every** role.
+      54             :      *
+      55             :      * Note: this virtual "admin has all roles" behavior is NOT reflected by
+      56             :      * {AccessControlEnumerable} enumeration. `getRoleMember` / `getRoleMemberCount`
+      57             :      * report only explicit grants, so a `DEFAULT_ADMIN_ROLE` holder satisfies
+      58             :      * `hasRole(anyRole, admin)` yet does not appear in `getRoleMember(anyRole, ...)`.
+      59             :      *
+      60             :      * WARNING: the same short-circuit makes a role **unrevokable from the default admin**.
+      61             :      * `revokeRole(someRole, admin)` succeeds and emits `RoleRevoked` — the explicit grant is
+      62             :      * genuinely removed, and `getRoleMemberCount` drops — but this function still answers
+      63             :      * `true`, so the admin keeps the access the caller believed it had just removed. Only
+      64             :      * revoking `DEFAULT_ADMIN_ROLE` itself actually withdraws it. This is inherent to the
+      65             :      * "admin has all roles" model rather than a defect (an admin can always re-grant itself
+      66             :      * any role), but the success of the call is misleading. Pinned by
+      67             :      * `testRevokingRoleFromDefaultAdminDoesNotRemoveAccess`.
+      68             :      * @param role The role identifier to check.
+      69             :      * @param account The account to check.
+      70             :      * @return True when `account` holds `role`, or holds `DEFAULT_ADMIN_ROLE`.
+      71             :      */
+      72          68 :     function hasRole(bytes32 role, address account)
+      73             :         public
+      74             :         view
+      75             :         virtual
+      76             :         override(AccessControl, IAccessControl)
+      77             :         returns (bool)
+      78             :     {
+      79             :         // The Default Admin has all roles
+      80         952 :         if (super.hasRole(DEFAULT_ADMIN_ROLE, account)) {
+      81         882 :             return true;
+      82             :         }
+      83          70 :         return super.hasRole(role, account);
+      84             :     }
+      85             : 
+      86             :     /*//////////////////////////////////////////////////////////////
+      87             :                            ERC165
+      88             :     //////////////////////////////////////////////////////////////*/
+      89             : 
+      90             :     /**
+      91             :      * @notice Returns whether this contract implements `interfaceId`.
+      92             :      * @dev ERC-165 discovery: advertises ERC-1643 and its multi-subject extension, the token-binding
+      93             :      * surface, the version module (ERC-8303) and `AccessControlEnumerable`.
+      94             :      *
+      95             :      * `type(IERC1643).interfaceId` is advertised because the engine does implement the base
+      96             :      * single-argument functions, which is exactly what the draft conditions the id on. Its audience
+      97             :      * is a **token wiring itself to this engine**: before calling `setDocumentEngine(engine)`, or
+      98             :      * before forwarding `setDocument(name, uri, hash)` to it, a token can confirm through ERC-165
+      99             :      * that the single-argument ERC-1643 endpoints exist here, rather than finding out from a failed
+     100             :      * call. `type(ITokenBinding).interfaceId` answers the complementary question — whether this
+     101             :      * engine has a binding surface at all — and `isTokenBound(address(this))` whether that
+     102             :      * particular token may use it.
+     103             :      *
+     104             :      * It is **not** an invitation to read documents from this address. The base functions are
+     105             :      * `_msgSender()`-scoped, so a consumer calling `getDocument(name)` here reads its own, empty
+     106             :      * namespace, and this engine emits only the address-carrying `*ForSubject` events. Point
+     107             :      * document consumers at the **subject**, or use the address-scoped `getDocument(subject, name)`.
+     108             :      *
+     109             :      * See {IERC165-supportsInterface}.
+     110             :      * @param interfaceId The ERC-165 interface identifier to query.
+     111             :      * @return True when `interfaceId` is one of the advertised interfaces or is supported by a base
+     112             :      * contract.
+     113             :      */
+     114           8 :     function supportsInterface(bytes4 interfaceId)
+     115             :         public
+     116             :         view
+     117             :         virtual
+     118             :         override(VersionModule, AccessControlEnumerable)
+     119             :         returns (bool)
+     120             :     {
+     121           8 :         return interfaceId == type(IERC1643).interfaceId || interfaceId == type(IERC1643MultiDocument).interfaceId
+     122           5 :             || interfaceId == type(ITokenBinding).interfaceId || super.supportsInterface(interfaceId);
+     123             :     }
+     124             : 
+     125             :     /*//////////////////////////////////////////////////////////////
+     126             :                         ACCESS CONTROL (implementation)
+     127             :     //////////////////////////////////////////////////////////////*/
+     128             : 
+     129             :     /**
+     130             :      * @dev Authorization for the admin document-management path.
+     131             :      * The caller must hold `DOCUMENT_MANAGER_ROLE`. Override to customize.
+     132             :      */
+     133         882 :     function _authorizeDocumentManagement() internal view virtual override {
+     134         882 :         _checkRole(DOCUMENT_MANAGER_ROLE);
+     135             :     }
+     136             : 
+     137             :     /*//////////////////////////////////////////////////////////////
+     138             :                            ERC2771
+     139             :     //////////////////////////////////////////////////////////////*/
+     140             : 
+     141             :     /**
+     142             :      * @dev This surcharge is not necessary if you do not use ERC2771
+     143             :      * @return sender The transaction sender, unwrapped from the ERC-2771 calldata suffix when the
+     144             :      * call came through the trusted forwarder.
+     145             :      */
+     146         966 :     function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) {
+     147         966 :         return ERC2771Context._msgSender();
+     148             :     }
+     149             : 
+     150             :     /**
+     151             :      * @dev This surcharge is not necessary if you do not use ERC2771
+     152             :      * @return The calldata, stripped of the ERC-2771 sender suffix when the call came through the
+     153             :      * trusted forwarder.
+     154             :      */
+     155           0 :     function _msgData() internal view virtual override(ERC2771Context, Context) returns (bytes calldata) {
+     156           0 :         return ERC2771Context._msgData();
+     157             :     }
+     158             : 
+     159             :     /**
+     160             :      * @dev This surcharge is not necessary if you do not use the MetaTxModule
+     161             :      * @return The length of the ERC-2771 calldata suffix holding the sender address.
+     162             :      */
+     163         966 :     function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
+     164         966 :         return ERC2771Context._contextSuffixLength();
+     165             :     }
+     166             : }
 
diff --git a/doc/coverage/coverage/src/DocumentEngineBase.sol.func-sort-c.html b/doc/coverage/coverage/src/DocumentEngineBase.sol.func-sort-c.html new file mode 100644 index 0000000..b661e5a --- /dev/null +++ b/doc/coverage/coverage/src/DocumentEngineBase.sol.func-sort-c.html @@ -0,0 +1,152 @@ + + + + + + + LCOV - lcov.info - src/DocumentEngineBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - DocumentEngineBase.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:757797.4 %
Date:2026-08-17 11:57:58Functions:182090.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
DocumentEngineBase._authorizeBoundTokenDocumentManagement0
DocumentEngineBase._authorizeDocumentManagement0
DocumentEngineBase.batchRemoveDocuments.13
DocumentEngineBase.getAllDocuments.03
DocumentEngineBase.onlyDocumentManager3
DocumentEngineBase.removeDocument.13
DocumentEngineBase.batchRemoveDocuments.05
DocumentEngineBase.batchSetDocuments.15
DocumentEngineBase.getDocument.05
DocumentEngineBase.onlyBoundToken7
DocumentEngineBase.setDocument.07
DocumentEngineBase.batchSetDocuments.010
DocumentEngineBase.removeDocument.0263
DocumentEngineBase.getAllDocuments.1266
DocumentEngineBase._removeDocumentName267
DocumentEngineBase._removeDocument269
DocumentEngineBase.setDocument.1587
DocumentEngineBase._setDocument603
DocumentEngineBase.getDocument.1798
DocumentEngineBase._getDocument803
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/DocumentEngineBase.sol.func.html b/doc/coverage/coverage/src/DocumentEngineBase.sol.func.html new file mode 100644 index 0000000..d81a090 --- /dev/null +++ b/doc/coverage/coverage/src/DocumentEngineBase.sol.func.html @@ -0,0 +1,152 @@ + + + + + + + LCOV - lcov.info - src/DocumentEngineBase.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - DocumentEngineBase.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:757797.4 %
Date:2026-08-17 11:57:58Functions:182090.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
DocumentEngineBase._authorizeBoundTokenDocumentManagement0
DocumentEngineBase._authorizeDocumentManagement0
DocumentEngineBase._getDocument803
DocumentEngineBase._removeDocument269
DocumentEngineBase._removeDocumentName267
DocumentEngineBase._setDocument603
DocumentEngineBase.batchRemoveDocuments.05
DocumentEngineBase.batchRemoveDocuments.13
DocumentEngineBase.batchSetDocuments.010
DocumentEngineBase.batchSetDocuments.15
DocumentEngineBase.getAllDocuments.03
DocumentEngineBase.getAllDocuments.1266
DocumentEngineBase.getDocument.05
DocumentEngineBase.getDocument.1798
DocumentEngineBase.onlyBoundToken7
DocumentEngineBase.onlyDocumentManager3
DocumentEngineBase.removeDocument.0263
DocumentEngineBase.removeDocument.13
DocumentEngineBase.setDocument.07
DocumentEngineBase.setDocument.1587
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/DocumentEngineBase.sol.gcov.html b/doc/coverage/coverage/src/DocumentEngineBase.sol.gcov.html new file mode 100644 index 0000000..99391b7 --- /dev/null +++ b/doc/coverage/coverage/src/DocumentEngineBase.sol.gcov.html @@ -0,0 +1,439 @@ + + + + + + + LCOV - lcov.info - src/DocumentEngineBase.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - DocumentEngineBase.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:757797.4 %
Date:2026-08-17 11:57:58Functions:182090.0 %
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : //SPDX-License-Identifier: MPL-2.0
+       2             : pragma solidity ^0.8.24;
+       3             : 
+       4             : import {Context} from "OZ/utils/Context.sol";
+       5             : import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol";
+       6             : import {IERC1643MultiDocument} from "./interfaces/IERC1643MultiDocument.sol";
+       7             : import {DocumentEngineInvariant} from "./DocumentEngineInvariant.sol";
+       8             : 
+       9             : /**
+      10             :  * @title DocumentEngineBase
+      11             :  * @notice Document management logic (ERC-1643) for several smart contracts.
+      12             :  * @dev This abstract base holds the document storage and all the
+      13             :  * document-management functions, but it is **agnostic to the access-control
+      14             :  * implementation**. Authorization is delegated to the abstract hooks
+      15             :  * {_authorizeDocumentManagement} and {_authorizeBoundTokenDocumentManagement}
+      16             :  * (through the `onlyDocumentManager` / `onlyBoundToken` modifiers), which a
+      17             :  * deployment contract must implement (see {DocumentEngine}).
+      18             :  *
+      19             :  * This separation (base logic + deployment-defined access control) follows the
+      20             :  * CMTAT and CMTA/RuleEngine pattern.
+      21             :  */
+      22             : abstract contract DocumentEngineBase is IERC1643, IERC1643MultiDocument, DocumentEngineInvariant, Context {
+      23             :     /**
+      24             :      * @notice Documents held for each subject, keyed by subject address then document name.
+      25             :      */
+      26             :     mapping(address => mapping(bytes32 => Document)) private _documents;
+      27             : 
+      28             :     /**
+      29             :      * @notice The names of every document currently tracked for each subject.
+      30             :      */
+      31             :     mapping(address => bytes32[]) private _documentNames;
+      32             : 
+      33             :     /*//////////////////////////////////////////////////////////////
+      34             :                         ACCESS CONTROL (modifiers)
+      35             :     //////////////////////////////////////////////////////////////*/
+      36             : 
+      37             :     /**
+      38             :      * @dev Restricts a function to accounts allowed to manage documents on
+      39             :      * behalf of any smart contract (admin path). Delegates the authorization
+      40             :      * to {_authorizeDocumentManagement} so that the document-management
+      41             :      * implementation stays separate from the access-control logic.
+      42             :      */
+      43           3 :     modifier onlyDocumentManager() {
+      44           3 :         _authorizeDocumentManagement();
+      45             :         _;
+      46             :     }
+      47             : 
+      48             :     /**
+      49             :      * @dev Restricts a function to tokens bound to this engine, letting them
+      50             :      * manage their own documents (bound-token path). Delegates to
+      51             :      * {_authorizeBoundTokenDocumentManagement}.
+      52             :      */
+      53           7 :     modifier onlyBoundToken() {
+      54           7 :         _authorizeBoundTokenDocumentManagement();
+      55             :         _;
+      56             :     }
+      57             : 
+      58             :     /*//////////////////////////////////////////////////////////////
+      59             :                             EXTERNAL FUNCTIONS
+      60             :     //////////////////////////////////////////////////////////////*/
+      61             : 
+      62             :     /**
+      63             :      * @notice Restricted function to remove a document for a given smart contract and name
+      64             :      * @param subject The contract the document belongs to.
+      65             :      * @param name_ The document name.
+      66             :      */
+      67         263 :     function removeDocument(address subject, bytes32 name_) external override onlyDocumentManager {
+      68         262 :         _removeDocument(subject, name_);
+      69             :     }
+      70             : 
+      71             :     /* ============ ERC-1643 (bound token) ============ */
+      72             : 
+      73             :     /**
+      74             :      * @notice ERC-1643 function to set or update a document for the caller.
+      75             :      * @dev The document is stored under the caller (`_msgSender()`) namespace.
+      76             :      * Restricted by the `onlyBoundToken` hook: the caller must be a token bound to
+      77             :      * this engine. How a token is bound is deployment-specific (see the
+      78             :      * {_authorizeBoundTokenDocumentManagement} implementations). A bound token can
+      79             :      * only manage its own documents; it can never affect another contract's documents.
+      80             :      * @param name_ The document name.
+      81             :      * @param uri_ The document location.
+      82             :      * @param documentHash_ The hash of the document contents.
+      83             :      */
+      84           7 :     function setDocument(bytes32 name_, string calldata uri_, bytes32 documentHash_) external override onlyBoundToken {
+      85           4 :         _setDocument(_msgSender(), name_, uri_, documentHash_);
+      86             :     }
+      87             : 
+      88             :     /**
+      89             :      * @notice ERC-1643 function to remove a document for the caller.
+      90             :      * @dev See {setDocument}. Scoped to the caller (`_msgSender()`) namespace.
+      91             :      * @param name_ The document name.
+      92             :      */
+      93           3 :     function removeDocument(bytes32 name_) external override onlyBoundToken {
+      94           2 :         _removeDocument(_msgSender(), name_);
+      95             :     }
+      96             : 
+      97             :     /**
+      98             :      * @notice Batch version of setDocument to handle multiple documents at once
+      99             :      * @dev All-or-nothing: a single invalid entry reverts the whole batch.
+     100             :      * @param subjects The contract each document belongs to, one per entry.
+     101             :      * @param names The document names, one per entry.
+     102             :      * @param uris The document locations, one per entry.
+     103             :      * @param hashes The document content hashes, one per entry.
+     104             :      */
+     105          10 :     function batchSetDocuments(
+     106             :         address[] calldata subjects,
+     107             :         bytes32[] calldata names,
+     108             :         string[] calldata uris,
+     109             :         bytes32[] calldata hashes
+     110             :     ) external onlyDocumentManager {
+     111             :         if (
+     112           9 :             subjects.length == 0 || subjects.length != names.length || names.length != uris.length
+     113           6 :                 || uris.length != hashes.length
+     114           3 :         ) {
+     115           3 :             revert InvalidInputLength();
+     116             :         }
+     117           6 :         uint256 length = subjects.length;
+     118           6 :         for (uint256 i = 0; i < length; ++i) {
+     119          10 :             _setDocument(subjects[i], names[i], uris[i], hashes[i]);
+     120             :         }
+     121             :     }
+     122             : 
+     123             :     /**
+     124             :      * @notice Batch version of setDocument to handle multiple documents at once
+     125             :      * @dev All-or-nothing: a single invalid entry reverts the whole batch.
+     126             :      * @param subject The contract every document in the batch belongs to.
+     127             :      * @param names The document names, one per entry.
+     128             :      * @param uris The document locations, one per entry.
+     129             :      * @param hashes The document content hashes, one per entry.
+     130             :      */
+     131           5 :     function batchSetDocuments(
+     132             :         address subject,
+     133             :         bytes32[] calldata names,
+     134             :         string[] calldata uris,
+     135             :         bytes32[] calldata hashes
+     136             :     ) external onlyDocumentManager {
+     137           4 :         if (names.length == 0 || names.length != uris.length || uris.length != hashes.length) {
+     138           2 :             revert InvalidInputLength();
+     139             :         }
+     140           2 :         uint256 length = names.length;
+     141           2 :         for (uint256 i = 0; i < length; ++i) {
+     142           4 :             _setDocument(subject, names[i], uris[i], hashes[i]);
+     143             :         }
+     144             :     }
+     145             : 
+     146             :     /**
+     147             :      * @notice Batch version of removeDocument to handle multiple documents at once
+     148             :      * @dev All-or-nothing: a single missing document reverts the whole batch.
+     149             :      * @param subjects The contract each document belongs to, one per entry.
+     150             :      * @param names The document names, one per entry.
+     151             :      */
+     152           5 :     function batchRemoveDocuments(address[] calldata subjects, bytes32[] calldata names) external onlyDocumentManager {
+     153           4 :         if (subjects.length == 0 || (subjects.length != names.length)) {
+     154           2 :             revert InvalidInputLength();
+     155             :         }
+     156             : 
+     157           2 :         uint256 length = subjects.length;
+     158           2 :         for (uint256 i = 0; i < length; ++i) {
+     159           3 :             _removeDocument(subjects[i], names[i]);
+     160             :         }
+     161             :     }
+     162             : 
+     163             :     /**
+     164             :      * @notice Batch version of removeDocument to handle multiple documents at once
+     165             :      * @dev All-or-nothing: a single missing document reverts the whole batch.
+     166             :      * @param subject The contract every document in the batch belongs to.
+     167             :      * @param names The document names, one per entry.
+     168             :      */
+     169           3 :     function batchRemoveDocuments(address subject, bytes32[] calldata names) external onlyDocumentManager {
+     170           2 :         if (names.length == 0) {
+     171           1 :             revert InvalidInputLength();
+     172             :         }
+     173             : 
+     174           1 :         uint256 length = names.length;
+     175           1 :         for (uint256 i = 0; i < length; ++i) {
+     176           2 :             _removeDocument(subject, names[i]);
+     177             :         }
+     178             :     }
+     179             : 
+     180             :     /**
+     181             :      * @notice ERC-1643 function to get a document for the caller (`_msgSender()`)
+     182             :      * @dev Returns the three fields as flat values, matching the ERC-1643 ABI. The `Document`
+     183             :      * struct is kept for storage only: returning it would prepend a struct offset word to the
+     184             :      * returndata, so a consumer decoding per the ERC-1643 signature would silently mis-decode.
+     185             :      * @param name_ The document name.
+     186             :      * @return uri Document location.
+     187             :      * @return documentHash Hash of the document contents.
+     188             :      * @return lastModified Last update timestamp.
+     189             :      */
+     190           5 :     function getDocument(bytes32 name_)
+     191             :         external
+     192             :         view
+     193             :         override
+     194             :         returns (string memory uri, bytes32 documentHash, uint256 lastModified)
+     195             :     {
+     196           5 :         return _getDocument(_msgSender(), name_);
+     197             :     }
+     198             : 
+     199             :     /**
+     200             :      * @notice Public function to get a document for a specific contract address
+     201             :      * @dev Flat return, see {getDocument(bytes32)}.
+     202             :      * @param subject The contract the document belongs to.
+     203             :      * @param name_ The document name.
+     204             :      * @return uri Document location.
+     205             :      * @return documentHash Hash of the document contents.
+     206             :      * @return lastModified Last update timestamp.
+     207             :      */
+     208         798 :     function getDocument(address subject, bytes32 name_)
+     209             :         external
+     210             :         view
+     211             :         override
+     212             :         returns (string memory uri, bytes32 documentHash, uint256 lastModified)
+     213             :     {
+     214         798 :         return _getDocument(subject, name_);
+     215             :     }
+     216             : 
+     217             :     /**
+     218             :      * @notice Get all document names for msg.sender
+     219             :      * @return The names of every document currently tracked for the caller.
+     220             :      */
+     221           3 :     function getAllDocuments() external view override returns (bytes32[] memory) {
+     222           3 :         return _documentNames[_msgSender()];
+     223             :     }
+     224             : 
+     225             :     /**
+     226             :      * @notice Get all document names for a specific smart contract
+     227             :      * @param subject The contract to enumerate documents for.
+     228             :      * @return The names of every document currently tracked for `subject`.
+     229             :      */
+     230         266 :     function getAllDocuments(address subject) external view override returns (bytes32[] memory) {
+     231         266 :         return _documentNames[subject];
+     232             :     }
+     233             : 
+     234             :     /*//////////////////////////////////////////////////////////////
+     235             :                             PUBLIC FUNCTIONS
+     236             :     //////////////////////////////////////////////////////////////*/
+     237             : 
+     238             :     /**
+     239             :      * @notice Restricted function to set or update a document
+     240             :      * @param subject The contract the document belongs to.
+     241             :      * @param name_ The document name.
+     242             :      * @param uri_ The document location.
+     243             :      * @param documentHash_ The hash of the document contents.
+     244             :      */
+     245         587 :     function setDocument(address subject, bytes32 name_, string memory uri_, bytes32 documentHash_)
+     246             :         public
+     247             :         override
+     248             :         onlyDocumentManager
+     249             :     {
+     250         585 :         _setDocument(subject, name_, uri_, documentHash_);
+     251             :     }
+     252             : 
+     253             :     /*//////////////////////////////////////////////////////////////
+     254             :                             INTERNAL FUNCTIONS
+     255             :     //////////////////////////////////////////////////////////////*/
+     256             : 
+     257             :     /**
+     258             :      * @dev Internal helper to remove the document name from the list of document names
+     259             :      * @param subject The contract the document belongs to.
+     260             :      * @param name_ The document name to remove from the list.
+     261             :      */
+     262         267 :     function _removeDocumentName(address subject, bytes32 name_) internal virtual {
+     263         267 :         bytes32[] storage names = _documentNames[subject];
+     264         267 :         uint256 length = names.length;
+     265         267 :         for (uint256 i = 0; i < length; ++i) {
+     266         269 :             if (names[i] == name_) {
+     267         267 :                 names[i] = names[length - 1];
+     268         267 :                 names.pop();
+     269         267 :                 break;
+     270             :             }
+     271             :         }
+     272             :     }
+     273             : 
+     274             :     /**
+     275             :      * @dev Shared removal implementation: reverts {ERC1643MissingDocument} when the document does
+     276             :      * not exist, then emits the address-carrying extension event and clears the entry.
+     277             :      * @param subject The contract the document belongs to.
+     278             :      * @param name_ The document name.
+     279             :      */
+     280         269 :     function _removeDocument(address subject, bytes32 name_) internal virtual {
+     281         269 :         Document storage doc = _documents[subject][name_];
+     282             :         // ERC-1643: reverts when the named document does not exist
+     283         269 :         if (doc.lastModified == 0) {
+     284           2 :             revert ERC1643MissingDocument();
+     285             :         }
+     286             : 
+     287             :         // This engine is a shared, multi-subject manager: per the ERC-1643
+     288             :         // "Emission Responsibility" rules it emits only the address-carrying
+     289             :         // extension event (the base `DocumentRemoved` is the token contract's
+     290             :         // responsibility).
+     291         267 :         emit DocumentRemovedForSubject(subject, name_, doc.uri, doc.documentHash);
+     292             : 
+     293             :         delete _documents[subject][name_];
+     294         267 :         _removeDocumentName(subject, name_);
+     295             :     }
+     296             : 
+     297             :     /**
+     298             :      * @dev Shared create/update implementation: rejects a null `subject` and a null `name_`, tracks
+     299             :      * the name on first write, then stores the document and emits the extension event.
+     300             :      * @param subject The contract the document belongs to.
+     301             :      * @param name_ The document name.
+     302             :      * @param uri_ The document location.
+     303             :      * @param documentHash_ The hash of the document contents.
+     304             :      */
+     305         603 :     function _setDocument(address subject, bytes32 name_, string memory uri_, bytes32 documentHash_) internal virtual {
+     306             :         // Multi-token guard: `subject` must be a real contract address, never the
+     307             :         // null namespace. (The bound-token path passes `_msgSender()`, never zero.)
+     308         603 :         if (subject == address(0)) {
+     309           2 :             revert MultiDocumentInvalidSubject();
+     310             :         }
+     311             :         // ERC-1643: reject the null name (ambiguous / default key)
+     312         601 :         if (name_ == bytes32(0)) {
+     313           3 :             revert ERC1643InvalidName();
+     314             :         }
+     315             : 
+     316         598 :         Document storage doc = _documents[subject][name_];
+     317         598 :         if (doc.lastModified == 0) {
+     318             :             // new document
+     319         587 :             _documentNames[subject].push(name_);
+     320             :         }
+     321         598 :         doc.uri = uri_;
+     322         598 :         doc.documentHash = documentHash_;
+     323         598 :         doc.lastModified = block.timestamp;
+     324             : 
+     325             :         // Shared, multi-subject manager: emit only the address-carrying extension
+     326             :         // event (see the {_removeDocument} note).
+     327         598 :         emit DocumentUpdatedForSubject(subject, name_, uri_, documentHash_);
+     328             :     }
+     329             : 
+     330             :     /*//////////////////////////////////////////////////////////////
+     331             :                         ACCESS CONTROL (hooks)
+     332             :     //////////////////////////////////////////////////////////////*/
+     333             : 
+     334             :     /**
+     335             :      * @dev Authorization hook for the admin document-management path.
+     336             :      * Implemented by the deployment contract (e.g. a role check).
+     337             :      */
+     338           0 :     function _authorizeDocumentManagement() internal view virtual;
+     339             : 
+     340             :     /**
+     341             :      * @dev Authorization hook for the bound-token document-management path.
+     342             :      * Implemented by the deployment contract (e.g. a role check).
+     343             :      */
+     344           0 :     function _authorizeBoundTokenDocumentManagement() internal view virtual;
+     345             : 
+     346             :     /**
+     347             :      * @dev Internal function to fetch a document, as flat values
+     348             :      * @param subject The contract the document belongs to.
+     349             :      * @param name_ The document name.
+     350             :      * @return uri Document location.
+     351             :      * @return documentHash Hash of the document contents.
+     352             :      * @return lastModified Last update timestamp.
+     353             :      */
+     354         803 :     function _getDocument(address subject, bytes32 name_)
+     355             :         internal
+     356             :         view
+     357             :         virtual
+     358             :         returns (string memory uri, bytes32 documentHash, uint256 lastModified)
+     359             :     {
+     360         803 :         Document storage doc = _documents[subject][name_];
+     361         803 :         return (doc.uri, doc.documentHash, doc.lastModified);
+     362             :     }
+     363             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/DocumentEngineOwnable.sol.func-sort-c.html b/doc/coverage/coverage/src/DocumentEngineOwnable.sol.func-sort-c.html new file mode 100644 index 0000000..e992640 --- /dev/null +++ b/doc/coverage/coverage/src/DocumentEngineOwnable.sol.func-sort-c.html @@ -0,0 +1,92 @@ + + + + + + + LCOV - lcov.info - src/DocumentEngineOwnable.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - DocumentEngineOwnable.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:91181.8 %
Date:2026-08-17 11:57:58Functions:4580.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
DocumentEngineOwnable._msgData0
DocumentEngineOwnable.supportsInterface7
DocumentEngineOwnable._authorizeDocumentManagement8
DocumentEngineOwnable._contextSuffixLength20
DocumentEngineOwnable._msgSender20
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/DocumentEngineOwnable.sol.func.html b/doc/coverage/coverage/src/DocumentEngineOwnable.sol.func.html new file mode 100644 index 0000000..0f79dbf --- /dev/null +++ b/doc/coverage/coverage/src/DocumentEngineOwnable.sol.func.html @@ -0,0 +1,92 @@ + + + + + + + LCOV - lcov.info - src/DocumentEngineOwnable.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - DocumentEngineOwnable.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:91181.8 %
Date:2026-08-17 11:57:58Functions:4580.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
DocumentEngineOwnable._authorizeDocumentManagement8
DocumentEngineOwnable._contextSuffixLength20
DocumentEngineOwnable._msgData0
DocumentEngineOwnable._msgSender20
DocumentEngineOwnable.supportsInterface7
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/DocumentEngineOwnable.sol.gcov.html b/doc/coverage/coverage/src/DocumentEngineOwnable.sol.gcov.html new file mode 100644 index 0000000..59ce8c3 --- /dev/null +++ b/doc/coverage/coverage/src/DocumentEngineOwnable.sol.gcov.html @@ -0,0 +1,169 @@ + + + + + + + LCOV - lcov.info - src/DocumentEngineOwnable.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src - DocumentEngineOwnable.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:91181.8 %
Date:2026-08-17 11:57:58Functions:4580.0 %
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : //SPDX-License-Identifier: MPL-2.0
+       2             : pragma solidity ^0.8.24;
+       3             : 
+       4             : import {Ownable} from "OZ/access/Ownable.sol";
+       5             : import {Ownable2Step} from "OZ/access/Ownable2Step.sol";
+       6             : import {Context} from "OZ/utils/Context.sol";
+       7             : import {ERC2771Context} from "OZ/metatx/ERC2771Context.sol";
+       8             : import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol";
+       9             : import {IERC1643MultiDocument} from "./interfaces/IERC1643MultiDocument.sol";
+      10             : import {ITokenBinding} from "./interfaces/ITokenBinding.sol";
+      11             : import {TokenBindingModule} from "./modules/TokenBindingModule.sol";
+      12             : import {VersionModule} from "./modules/VersionModule.sol";
+      13             : 
+      14             : /**
+      15             :  * @title DocumentEngineOwnable
+      16             :  * @notice Alternative deployment of the DocumentEngine that uses a single owner
+      17             :  * ({Ownable2Step}) instead of role-based access control.
+      18             :  * @dev Reuses the same document-management logic ({DocumentEngineBase}) and token
+      19             :  * binding ({TokenBindingModule}), swapping only the access-control implementation:
+      20             :  * document management and token binding are both restricted to the `owner`, and a
+      21             :  * bound token manages only its own documents. Ownership uses the two-step transfer
+      22             :  * flow for safety, and the contract also exposes its version through ERC-8303
+      23             :  * ({VersionModule}) and wires ERC-2771.
+      24             :  */
+      25             : contract DocumentEngineOwnable is TokenBindingModule, VersionModule, Ownable2Step, ERC2771Context {
+      26             :     /**
+      27             :      * @notice Deploys the engine with `owner_` as its single privileged account.
+      28             :      * @param owner_ initial owner of the contract
+      29             :      * @param forwarderIrrevocable address of the ERC-2771 forwarder (gasless support)
+      30             :      */
+      31             :     constructor(address owner_, address forwarderIrrevocable) Ownable(owner_) ERC2771Context(forwarderIrrevocable) {}
+      32             : 
+      33             :     /*//////////////////////////////////////////////////////////////
+      34             :                            ERC165
+      35             :     //////////////////////////////////////////////////////////////*/
+      36             : 
+      37             :     /**
+      38             :      * @notice Returns whether this contract implements `interfaceId`.
+      39             :      * @dev ERC-165 discovery: advertises ERC-1643 and its multi-subject extension, the token-binding
+      40             :      * surface and the version module (ERC-8303). See the rationale on
+      41             :      * {DocumentEngine-supportsInterface} for what `type(IERC1643).interfaceId` does and does not
+      42             :      * tell a caller here. See {IERC165-supportsInterface}.
+      43             :      * @param interfaceId The ERC-165 interface identifier to query.
+      44             :      * @return True when `interfaceId` is one of the advertised interfaces or is supported by a base
+      45             :      * contract.
+      46             :      */
+      47           7 :     function supportsInterface(bytes4 interfaceId) public view virtual override(VersionModule) returns (bool) {
+      48           7 :         return interfaceId == type(IERC1643).interfaceId || interfaceId == type(IERC1643MultiDocument).interfaceId
+      49           5 :             || interfaceId == type(ITokenBinding).interfaceId || super.supportsInterface(interfaceId);
+      50             :     }
+      51             : 
+      52             :     /*//////////////////////////////////////////////////////////////
+      53             :                         ACCESS CONTROL (implementation)
+      54             :     //////////////////////////////////////////////////////////////*/
+      55             : 
+      56             :     /**
+      57             :      * @dev Authorization for the admin document-management path (and, via
+      58             :      * {TokenBindingModule}, for token binding): only the owner.
+      59             :      */
+      60           8 :     function _authorizeDocumentManagement() internal view virtual override {
+      61           8 :         _checkOwner();
+      62             :     }
+      63             : 
+      64             :     /*//////////////////////////////////////////////////////////////
+      65             :                            ERC2771
+      66             :     //////////////////////////////////////////////////////////////*/
+      67             : 
+      68             :     /**
+      69             :      * @dev This surcharge is not necessary if you do not use ERC2771
+      70             :      * @return sender The transaction sender, unwrapped from the ERC-2771 calldata suffix when the
+      71             :      * call came through the trusted forwarder.
+      72             :      */
+      73          20 :     function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) {
+      74          20 :         return ERC2771Context._msgSender();
+      75             :     }
+      76             : 
+      77             :     /**
+      78             :      * @dev This surcharge is not necessary if you do not use ERC2771
+      79             :      * @return The calldata, stripped of the ERC-2771 sender suffix when the call came through the
+      80             :      * trusted forwarder.
+      81             :      */
+      82           0 :     function _msgData() internal view virtual override(ERC2771Context, Context) returns (bytes calldata) {
+      83           0 :         return ERC2771Context._msgData();
+      84             :     }
+      85             : 
+      86             :     /**
+      87             :      * @dev This surcharge is not necessary if you do not use the MetaTxModule
+      88             :      * @return The length of the ERC-2771 calldata suffix holding the sender address.
+      89             :      */
+      90          20 :     function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
+      91          20 :         return ERC2771Context._contextSuffixLength();
+      92             :     }
+      93             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/index-sort-f.html b/doc/coverage/coverage/src/index-sort-f.html index 6d71c92..757b24d 100644 --- a/doc/coverage/coverage/src/index-sort-f.html +++ b/doc/coverage/coverage/src/index-sort-f.html @@ -31,27 +31,18 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 101 + 107 + 94.4 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 - 85.7 % - - - - - - Branches: - 13 - 14 - 92.9 % + 28 + 32 + 87.5 % @@ -65,33 +56,48 @@ - - - - - - - - + + + + + + - + + + + + + + + - - + + - - - + + + + + + + + +


Filename Sort by name Line Coverage Sort by line coverage Functions Sort by function coverageBranches Sort by branch coverage
DocumentEngineOwnable.sol +
81.8%81.8%
+
81.8 %9 / 1180.0 %4 / 5
DocumentEngine.sol -
98.1%98.1%
+
89.5%89.5%
98.1 %51 / 5289.5 %17 / 19 85.7 %12 / 1492.9 %13 / 146 / 7
DocumentEngineBase.sol +
97.4%97.4%
+
97.4 %75 / 7790.0 %18 / 20
diff --git a/doc/coverage/coverage/src/index-sort-l.html b/doc/coverage/coverage/src/index-sort-l.html index 9d0c2f2..4ee8603 100644 --- a/doc/coverage/coverage/src/index-sort-l.html +++ b/doc/coverage/coverage/src/index-sort-l.html @@ -31,27 +31,18 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 101 + 107 + 94.4 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 - 85.7 % - - - - - - Branches: - 13 - 14 - 92.9 % + 28 + 32 + 87.5 % @@ -65,33 +56,48 @@ - - - - - - - - + + + + + + - + + + + + + + + - - + + - - - + + + + + + + + +


Filename Sort by name Line Coverage Sort by line coverage Functions Sort by function coverageBranches Sort by branch coverage
DocumentEngineOwnable.sol +
81.8%81.8%
+
81.8 %9 / 1180.0 %4 / 5
DocumentEngine.sol -
98.1%98.1%
+
89.5%89.5%
98.1 %51 / 5289.5 %17 / 19 85.7 %12 / 1492.9 %13 / 146 / 7
DocumentEngineBase.sol +
97.4%97.4%
+
97.4 %75 / 7790.0 %18 / 20
diff --git a/doc/coverage/coverage/src/index.html b/doc/coverage/coverage/src/index.html index 64ab2cb..f433b89 100644 --- a/doc/coverage/coverage/src/index.html +++ b/doc/coverage/coverage/src/index.html @@ -31,27 +31,18 @@ lcov.info Lines: - 51 - 52 - 98.1 % + 101 + 107 + 94.4 % Date: - 2024-09-09 15:02:57 + 2026-08-17 11:57:58 Functions: - 12 - 14 - 85.7 % - - - - - - Branches: - 13 - 14 - 92.9 % + 28 + 32 + 87.5 % @@ -65,33 +56,48 @@ - - - - - - - - + + + + + + - - - + + - - - + + + + + + + + + + + + + + + + +


Filename Sort by name Line Coverage Sort by line coverage Functions Sort by function coverageBranches Sort by branch coverage
DocumentEngine.sol -
98.1%98.1%
+
89.5%89.5%
98.1 %51 / 5289.5 %17 / 19 85.7 %12 / 1492.9 %13 / 146 / 7
DocumentEngineBase.sol +
97.4%97.4%
+
97.4 %75 / 7790.0 %18 / 20
DocumentEngineOwnable.sol +
81.8%81.8%
+
81.8 %9 / 1180.0 %4 / 5
diff --git a/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func-sort-c.html new file mode 100644 index 0000000..7859b5a --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func-sort-c.html @@ -0,0 +1,96 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:2020100.0 %
Date:2026-08-17 11:57:58Functions:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenBindingModule.unbindToken6
TokenBindingModule._checkTokenBound9
TokenBindingModule._authorizeBoundTokenDocumentManagement10
TokenBindingModule.isTokenBound10
TokenBindingModule.bindToken12
TokenBindingModule._setTokenBinding16
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func.html b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func.html new file mode 100644 index 0000000..3bb1820 --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func.html @@ -0,0 +1,96 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:2020100.0 %
Date:2026-08-17 11:57:58Functions:66100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenBindingModule._authorizeBoundTokenDocumentManagement10
TokenBindingModule._checkTokenBound9
TokenBindingModule._setTokenBinding16
TokenBindingModule.bindToken12
TokenBindingModule.isTokenBound10
TokenBindingModule.unbindToken6
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingModule.sol.gcov.html b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.gcov.html new file mode 100644 index 0000000..38cd0c1 --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.gcov.html @@ -0,0 +1,169 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingModule.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:2020100.0 %
Date:2026-08-17 11:57:58Functions:66100.0 %
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // SPDX-License-Identifier: MPL-2.0
+       2             : pragma solidity ^0.8.24;
+       3             : 
+       4             : import {DocumentEngineBase} from "../DocumentEngineBase.sol";
+       5             : import {ITokenBinding} from "../interfaces/ITokenBinding.sol";
+       6             : 
+       7             : /**
+       8             :  * @title TokenBindingModule
+       9             :  * @notice Shared token-binding registry (an allowlist) implementing {ITokenBinding},
+      10             :  * used by every DocumentEngine deployment so binding behaves identically — same
+      11             :  * functions, same event, same revert — regardless of the access-control model.
+      12             :  * @dev A *bound* token may manage its own documents through the standard
+      13             :  * single-argument ERC-1643 functions (`msg.sender` is the token). This module:
+      14             :  *  - stores the allowlist and implements `bindToken` / `unbindToken` / `isTokenBound`;
+      15             :  *  - wires the base bound-token hook ({_authorizeBoundTokenDocumentManagement}) to
+      16             :  *    the allowlist ({_checkTokenBound});
+      17             :  *  - gates binding management with the deployment's document-management
+      18             :  *    authorization ({_authorizeDocumentManagement}), so whoever may manage
+      19             :  *    documents may also decide bindings. It is therefore access-control agnostic:
+      20             :  *    the deployment only implements {_authorizeDocumentManagement}.
+      21             :  */
+      22             : abstract contract TokenBindingModule is DocumentEngineBase, ITokenBinding {
+      23             :     /// @dev Tokens bound to the engine, allowed to manage their own documents.
+      24             :     mapping(address => bool) private _boundTokens;
+      25             : 
+      26             :     /// @notice Thrown when a non-bound caller attempts a bound-token operation.
+      27             :     error NotBoundToken(address caller);
+      28             : 
+      29             :     /**
+      30             :      * @inheritdoc ITokenBinding
+      31             :      * @dev Authorized by the deployment's document-management check.
+      32             :      */
+      33          12 :     function bindToken(address token) external virtual override {
+      34          12 :         _authorizeDocumentManagement();
+      35          10 :         _setTokenBinding(token, true);
+      36             :     }
+      37             : 
+      38             :     /**
+      39             :      * @inheritdoc ITokenBinding
+      40             :      * @dev Authorized by the deployment's document-management check.
+      41             :      */
+      42           6 :     function unbindToken(address token) external virtual override {
+      43           6 :         _authorizeDocumentManagement();
+      44           6 :         _setTokenBinding(token, false);
+      45             :     }
+      46             : 
+      47             :     /**
+      48             :      * @inheritdoc ITokenBinding
+      49             :      */
+      50          10 :     function isTokenBound(address token) public view virtual override returns (bool) {
+      51          10 :         return _boundTokens[token];
+      52             :     }
+      53             : 
+      54             :     /**
+      55             :      * @dev Shared bind/unbind implementation.
+      56             :      *
+      57             :      * Rejects the null address: `address(0)` can never call the engine, so binding it grants
+      58             :      * nothing, but it would still emit a {TokenBindingSet} that off-chain indexers key on — the
+      59             :      * same data-integrity argument the multi-subject draft makes for rejecting a null `subject`.
+      60             :      *
+      61             :      * Writing and emitting only on an actual change makes both functions idempotent and keeps the
+      62             :      * event stream free of no-op entries, so an indexer can treat every {TokenBindingSet} as a real
+      63             :      * transition rather than having to de-duplicate. The repeated call still succeeds, since the
+      64             :      * caller's intent — "this token is (not) bound" — already holds.
+      65             :      * @param token The token whose binding is being set.
+      66             :      * @param bound The binding state to apply: `true` to bind, `false` to unbind.
+      67             :      */
+      68          16 :     function _setTokenBinding(address token, bool bound) internal virtual {
+      69          16 :         if (token == address(0)) {
+      70           2 :             revert TokenBindingInvalidToken();
+      71             :         }
+      72          14 :         if (_boundTokens[token] == bound) {
+      73          14 :             return;
+      74             :         }
+      75          11 :         _boundTokens[token] = bound;
+      76          11 :         emit TokenBindingSet(token, bound);
+      77             :     }
+      78             : 
+      79             :     /**
+      80             :      * @dev Bound-token document-management authorization: the caller
+      81             :      * (`_msgSender()`) must be a bound token.
+      82             :      */
+      83          10 :     function _authorizeBoundTokenDocumentManagement() internal view virtual override {
+      84          10 :         _checkTokenBound();
+      85             :     }
+      86             : 
+      87             :     /// @dev Reverts {NotBoundToken} if the caller (`_msgSender()`) is not bound.
+      88           9 :     function _checkTokenBound() internal view virtual {
+      89           9 :         if (!_boundTokens[_msgSender()]) {
+      90           4 :             revert NotBoundToken(_msgSender());
+      91             :         }
+      92             :     }
+      93             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html new file mode 100644 index 0000000..32f4c60 --- /dev/null +++ b/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html @@ -0,0 +1,80 @@ + + + + + + + LCOV - lcov.info - src/modules/VersionModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - VersionModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:44100.0 %
Date:2026-08-17 11:57:58Functions:22100.0 %
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
VersionModule.version6
VersionModule.supportsInterface7
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.func.html b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html new file mode 100644 index 0000000..7f9d579 --- /dev/null +++ b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html @@ -0,0 +1,80 @@ + + + + + + + LCOV - lcov.info - src/modules/VersionModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - VersionModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:44100.0 %
Date:2026-08-17 11:57:58Functions:22100.0 %
+
+ +
+ + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
VersionModule.supportsInterface7
VersionModule.version6
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html new file mode 100644 index 0000000..f6fa800 --- /dev/null +++ b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html @@ -0,0 +1,116 @@ + + + + + + + LCOV - lcov.info - src/modules/VersionModule.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - VersionModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:44100.0 %
Date:2026-08-17 11:57:58Functions:22100.0 %
+
+ + + + + + + + +

+
          Line data    Source code
+
+       1             : // SPDX-License-Identifier: MPL-2.0
+       2             : pragma solidity ^0.8.24;
+       3             : 
+       4             : import {ERC165} from "OZ/utils/introspection/ERC165.sol";
+       5             : import {IERC8303} from "../interfaces/IERC8303.sol";
+       6             : 
+       7             : /**
+       8             :  * @title VersionModule
+       9             :  * @notice Exposes the current contract version through ERC-8303 (`version()`),
+      10             :  * with optional ERC-165 interface discovery.
+      11             :  * @dev Implements ERC-8303 (Draft). The version string is defined here so the
+      12             :  * version concern is isolated in a dedicated module (CMTAT pattern). A deployment
+      13             :  * contract that also implements ERC-165 must combine this module's
+      14             :  * {supportsInterface} with the others it inherits.
+      15             :  */
+      16             : abstract contract VersionModule is IERC8303, ERC165 {
+      17             :     /**
+      18             :      * @notice Get the current version of the smart contract.
+      19             :      * @dev Follows Semantic Versioning 2.0.0 (`MAJOR.MINOR.PATCH`).
+      20             :      */
+      21             :     string public constant VERSION = "0.4.0";
+      22             : 
+      23             :     /**
+      24             :      * @inheritdoc IERC8303
+      25             :      */
+      26           6 :     function version() public view virtual override(IERC8303) returns (string memory version_) {
+      27           6 :         return VERSION;
+      28             :     }
+      29             : 
+      30             :     /**
+      31             :      * @notice Returns whether this contract implements `interfaceId`.
+      32             :      * @dev Advertises ERC-8303 support (interface id `0x54fd4d50`).
+      33             :      * See {IERC165-supportsInterface}.
+      34             :      * @param interfaceId The ERC-165 interface identifier to query.
+      35             :      * @return True when `interfaceId` is ERC-8303 or is supported by a base contract.
+      36             :      */
+      37           7 :     function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
+      38           7 :         return interfaceId == type(IERC8303).interfaceId || super.supportsInterface(interfaceId);
+      39             :     }
+      40             : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/index-sort-f.html b/doc/coverage/coverage/src/modules/index-sort-f.html new file mode 100644 index 0000000..4fd74b9 --- /dev/null +++ b/doc/coverage/coverage/src/modules/index-sort-f.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info - src/modules + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modulesHitTotalCoverage
Test:lcov.infoLines:2424100.0 %
Date:2026-08-17 11:57:58Functions:88100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
VersionModule.sol +
100.0%
+
100.0 %4 / 4100.0 %2 / 2
TokenBindingModule.sol +
100.0%
+
100.0 %20 / 20100.0 %6 / 6
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/index-sort-l.html b/doc/coverage/coverage/src/modules/index-sort-l.html new file mode 100644 index 0000000..3418270 --- /dev/null +++ b/doc/coverage/coverage/src/modules/index-sort-l.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info - src/modules + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modulesHitTotalCoverage
Test:lcov.infoLines:2424100.0 %
Date:2026-08-17 11:57:58Functions:88100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
VersionModule.sol +
100.0%
+
100.0 %4 / 4100.0 %2 / 2
TokenBindingModule.sol +
100.0%
+
100.0 %20 / 20100.0 %6 / 6
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/index.html b/doc/coverage/coverage/src/modules/index.html new file mode 100644 index 0000000..0964418 --- /dev/null +++ b/doc/coverage/coverage/src/modules/index.html @@ -0,0 +1,103 @@ + + + + + + + LCOV - lcov.info - src/modules + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modulesHitTotalCoverage
Test:lcov.infoLines:2424100.0 %
Date:2026-08-17 11:57:58Functions:88100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Filename Sort by nameLine Coverage Sort by line coverageFunctions Sort by function coverage
TokenBindingModule.sol +
100.0%
+
100.0 %20 / 20100.0 %6 / 6
VersionModule.sol +
100.0%
+
100.0 %4 / 4100.0 %2 / 2
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/lcov.info b/doc/coverage/lcov.info index ba3dafb..10c4cb1 100644 --- a/doc/coverage/lcov.info +++ b/doc/coverage/lcov.info @@ -1,185 +1,246 @@ TN: SF:src/DocumentEngine.sol -FN:46,DocumentEngine.setDocument -FNDA:28,DocumentEngine.setDocument -DA:52,27 -DA:52,27 -FN:58,DocumentEngine.removeDocument -FNDA:2,DocumentEngine.removeDocument -DA:62,1 -DA:62,1 -FN:68,DocumentEngine.batchSetDocuments -FNDA:8,DocumentEngine.batchSetDocuments -DA:75,7 -DA:75,7 -DA:75,7 -DA:75,7 -DA:75,7 -DA:76,6 -DA:76,6 -DA:77,5 -DA:77,5 -DA:78,4 -DA:78,4 -BRDA:74,0,0,3 -BRDA:74,0,1,4 -DA:80,3 -DA:80,3 -DA:82,4 -DA:82,4 -DA:82,12 -DA:82,8 -DA:83,8 -DA:83,8 -FN:90,DocumentEngine.batchSetDocuments -FNDA:5,DocumentEngine.batchSetDocuments -DA:97,4 -DA:97,4 -DA:97,4 -DA:97,4 -DA:98,3 -DA:98,3 -DA:99,2 -DA:99,2 -BRDA:96,1,0,2 -BRDA:96,1,1,2 -DA:101,2 -DA:101,2 -DA:103,2 -DA:103,2 -DA:103,6 -DA:103,4 -DA:104,4 -DA:104,4 -FN:111,DocumentEngine.batchRemoveDocuments -FNDA:4,DocumentEngine.batchRemoveDocuments -DA:116,3 -DA:116,3 -DA:116,3 -BRDA:115,2,0,2 -BRDA:115,2,1,1 -DA:119,2 -DA:119,2 -DA:122,1 -DA:122,1 -DA:122,3 -DA:122,2 -DA:123,2 -DA:123,2 -FN:130,DocumentEngine.batchRemoveDocuments -FNDA:3,DocumentEngine.batchRemoveDocuments -DA:134,2 -DA:134,2 -BRDA:134,3,0,1 -BRDA:134,3,1,1 -DA:135,1 -DA:135,1 -DA:138,1 -DA:138,1 -DA:138,3 +FN:39,DocumentEngine.constructor +FN:72,DocumentEngine.hasRole +FN:114,DocumentEngine.supportsInterface +FN:133,DocumentEngine._authorizeDocumentManagement +FN:146,DocumentEngine._msgSender +FN:155,DocumentEngine._msgData +FN:163,DocumentEngine._contextSuffixLength +FNDA:8,DocumentEngine.supportsInterface +FNDA:64,DocumentEngine.constructor +FNDA:0,DocumentEngine._msgData +FNDA:966,DocumentEngine._contextSuffixLength +FNDA:966,DocumentEngine._msgSender +FNDA:882,DocumentEngine._authorizeDocumentManagement +FNDA:68,DocumentEngine.hasRole +FNF:7 +FNH:6 +DA:39,64 +DA:40,64 +DA:41,1 +DA:43,63 +DA:72,68 +DA:80,952 +DA:81,882 +DA:83,70 +DA:114,8 +DA:121,8 +DA:122,5 +DA:133,882 +DA:134,882 +DA:146,966 +DA:147,966 +DA:155,0 +DA:156,0 +DA:163,966 +DA:164,966 +LF:19 +LH:17 +end_of_record +TN: +SF:src/DocumentEngineBase.sol +FN:43,DocumentEngineBase.onlyDocumentManager +FN:53,DocumentEngineBase.onlyBoundToken +FN:67,DocumentEngineBase.removeDocument.0 +FN:84,DocumentEngineBase.setDocument.0 +FN:93,DocumentEngineBase.removeDocument.1 +FN:105,DocumentEngineBase.batchSetDocuments.0 +FN:131,DocumentEngineBase.batchSetDocuments.1 +FN:152,DocumentEngineBase.batchRemoveDocuments.0 +FN:169,DocumentEngineBase.batchRemoveDocuments.1 +FN:190,DocumentEngineBase.getDocument.0 +FN:208,DocumentEngineBase.getDocument.1 +FN:221,DocumentEngineBase.getAllDocuments.0 +FN:230,DocumentEngineBase.getAllDocuments.1 +FN:245,DocumentEngineBase.setDocument.1 +FN:262,DocumentEngineBase._removeDocumentName +FN:280,DocumentEngineBase._removeDocument +FN:305,DocumentEngineBase._setDocument +FN:338,DocumentEngineBase._authorizeDocumentManagement +FN:344,DocumentEngineBase._authorizeBoundTokenDocumentManagement +FN:354,DocumentEngineBase._getDocument +FNDA:798,DocumentEngineBase.getDocument.1 +FNDA:7,DocumentEngineBase.setDocument.0 +FNDA:5,DocumentEngineBase.batchRemoveDocuments.0 +FNDA:266,DocumentEngineBase.getAllDocuments.1 +FNDA:7,DocumentEngineBase.onlyBoundToken +FNDA:3,DocumentEngineBase.getAllDocuments.0 +FNDA:263,DocumentEngineBase.removeDocument.0 +FNDA:10,DocumentEngineBase.batchSetDocuments.0 +FNDA:269,DocumentEngineBase._removeDocument +FNDA:5,DocumentEngineBase.batchSetDocuments.1 +FNDA:3,DocumentEngineBase.onlyDocumentManager +FNDA:3,DocumentEngineBase.removeDocument.1 +FNDA:587,DocumentEngineBase.setDocument.1 +FNDA:0,DocumentEngineBase._authorizeDocumentManagement +FNDA:803,DocumentEngineBase._getDocument +FNDA:3,DocumentEngineBase.batchRemoveDocuments.1 +FNDA:0,DocumentEngineBase._authorizeBoundTokenDocumentManagement +FNDA:267,DocumentEngineBase._removeDocumentName +FNDA:603,DocumentEngineBase._setDocument +FNDA:5,DocumentEngineBase.getDocument.0 +FNF:20 +FNH:18 +DA:43,3 +DA:44,3 +DA:53,7 +DA:54,7 +DA:67,263 +DA:68,262 +DA:84,7 +DA:85,4 +DA:93,3 +DA:94,2 +DA:105,10 +DA:112,9 +DA:113,6 +DA:114,3 +DA:115,3 +DA:117,6 +DA:118,6 +DA:119,10 +DA:131,5 +DA:137,4 DA:138,2 -DA:139,2 -DA:139,2 -FN:146,DocumentEngine.getDocument -FNDA:1,DocumentEngine.getDocument -DA:149,1 -DA:149,1 -DA:149,1 -FN:155,DocumentEngine.getDocument -FNDA:19,DocumentEngine.getDocument -DA:159,19 -DA:159,19 -DA:159,19 -FN:165,DocumentEngine.getAllDocuments -FNDA:1,DocumentEngine.getAllDocuments +DA:140,2 +DA:141,2 +DA:142,4 +DA:152,5 +DA:153,4 +DA:154,2 +DA:157,2 +DA:158,2 +DA:159,3 +DA:169,3 +DA:170,2 DA:171,1 -DA:171,1 -FN:177,DocumentEngine.getAllDocuments -FNDA:7,DocumentEngine.getAllDocuments -DA:180,7 -DA:180,7 -FN:187,DocumentEngine.hasRole -FNDA:0,DocumentEngine.hasRole -DA:192,50 -DA:192,50 -BRDA:192,4,0,44 -BRDA:192,4,1,6 -DA:193,44 -DA:193,44 -DA:195,6 -DA:195,6 -DA:195,6 -FN:205,DocumentEngine._getDocument -FNDA:20,DocumentEngine._getDocument -DA:209,20 -DA:209,20 -DA:210,20 -DA:210,20 -FN:216,DocumentEngine._removeDocumentName -FNDA:5,DocumentEngine._removeDocumentName -DA:220,5 -DA:220,5 -DA:221,5 -DA:221,5 -DA:221,5 -DA:221,0 -DA:222,5 -DA:222,5 -BRDA:222,5,0,- -BRDA:222,5,1,5 -DA:223,5 -DA:223,5 -DA:226,5 -DA:226,5 -DA:227,5 -DA:227,5 -FN:232,DocumentEngine._removeDocument -FNDA:5,DocumentEngine._removeDocument -DA:233,5 -DA:233,5 -DA:234,5 -DA:234,5 -DA:236,5 -DA:236,5 -DA:237,5 -DA:237,5 -FN:240,DocumentEngine._setDocument -FNDA:39,DocumentEngine._setDocument -DA:246,39 -DA:246,39 -DA:247,39 -DA:247,39 -BRDA:247,6,0,30 -BRDA:247,6,1,39 -DA:249,30 -DA:249,30 -DA:251,39 -DA:251,39 -DA:252,39 -DA:252,39 -DA:253,39 -DA:253,39 -DA:254,39 -DA:254,39 -FN:264,DocumentEngine._msgSender -FNDA:50,DocumentEngine._msgSender -DA:270,50 -DA:270,50 -DA:270,50 -FN:276,DocumentEngine._msgData -FNDA:0,DocumentEngine._msgData -DA:282,0 -DA:282,0 -DA:282,0 -FN:288,DocumentEngine._contextSuffixLength -FNDA:50,DocumentEngine._contextSuffixLength -DA:294,50 -DA:294,50 -DA:294,50 -FNF:18 -FNH:16 -LF:52 -LH:51 -BRF:14 -BRH:13 +DA:174,1 +DA:175,1 +DA:176,2 +DA:190,5 +DA:196,5 +DA:208,798 +DA:214,798 +DA:221,3 +DA:222,3 +DA:230,266 +DA:231,266 +DA:245,587 +DA:250,585 +DA:262,267 +DA:263,267 +DA:264,267 +DA:265,267 +DA:266,269 +DA:267,267 +DA:268,267 +DA:269,267 +DA:280,269 +DA:281,269 +DA:283,269 +DA:284,2 +DA:291,267 +DA:294,267 +DA:305,603 +DA:308,603 +DA:309,2 +DA:312,601 +DA:313,3 +DA:316,598 +DA:317,598 +DA:319,587 +DA:321,598 +DA:322,598 +DA:323,598 +DA:327,598 +DA:338,0 +DA:344,0 +DA:354,803 +DA:360,803 +DA:361,803 +LF:77 +LH:75 +end_of_record +TN: +SF:src/DocumentEngineOwnable.sol +FN:47,DocumentEngineOwnable.supportsInterface +FN:60,DocumentEngineOwnable._authorizeDocumentManagement +FN:73,DocumentEngineOwnable._msgSender +FN:82,DocumentEngineOwnable._msgData +FN:90,DocumentEngineOwnable._contextSuffixLength +FNDA:0,DocumentEngineOwnable._msgData +FNDA:20,DocumentEngineOwnable._contextSuffixLength +FNDA:20,DocumentEngineOwnable._msgSender +FNDA:7,DocumentEngineOwnable.supportsInterface +FNDA:8,DocumentEngineOwnable._authorizeDocumentManagement +FNF:5 +FNH:4 +DA:47,7 +DA:48,7 +DA:49,5 +DA:60,8 +DA:61,8 +DA:73,20 +DA:74,20 +DA:82,0 +DA:83,0 +DA:90,20 +DA:91,20 +LF:11 +LH:9 +end_of_record +TN: +SF:src/modules/TokenBindingModule.sol +FN:33,TokenBindingModule.bindToken +FN:42,TokenBindingModule.unbindToken +FN:50,TokenBindingModule.isTokenBound +FN:68,TokenBindingModule._setTokenBinding +FN:83,TokenBindingModule._authorizeBoundTokenDocumentManagement +FN:88,TokenBindingModule._checkTokenBound +FNDA:10,TokenBindingModule.isTokenBound +FNDA:9,TokenBindingModule._checkTokenBound +FNDA:16,TokenBindingModule._setTokenBinding +FNDA:6,TokenBindingModule.unbindToken +FNDA:10,TokenBindingModule._authorizeBoundTokenDocumentManagement +FNDA:12,TokenBindingModule.bindToken +FNF:6 +FNH:6 +DA:33,12 +DA:34,12 +DA:35,10 +DA:42,6 +DA:43,6 +DA:44,6 +DA:50,10 +DA:51,10 +DA:68,16 +DA:69,16 +DA:70,2 +DA:72,14 +DA:73,14 +DA:75,11 +DA:76,11 +DA:83,10 +DA:84,10 +DA:88,9 +DA:89,9 +DA:90,4 +LF:20 +LH:20 +end_of_record +TN: +SF:src/modules/VersionModule.sol +FN:26,VersionModule.version +FN:37,VersionModule.supportsInterface +FNDA:7,VersionModule.supportsInterface +FNDA:6,VersionModule.version +FNF:2 +FNH:2 +DA:26,6 +DA:27,6 +DA:37,7 +DA:38,7 +LF:4 +LH:4 end_of_record diff --git a/doc/img/cmtat-integration-architecture.png b/doc/img/cmtat-integration-architecture.png new file mode 100644 index 0000000..3e500e2 Binary files /dev/null and b/doc/img/cmtat-integration-architecture.png differ diff --git a/doc/img/cmtat-integration-architecture.puml b/doc/img/cmtat-integration-architecture.puml new file mode 100644 index 0000000..d98d712 --- /dev/null +++ b/doc/img/cmtat-integration-architecture.puml @@ -0,0 +1,42 @@ +@startuml +title Topology — one engine, many subjects + +skinparam componentStyle rectangle +skinparam shadowing false +skinparam defaultTextAlignment center +skinparam linetype ortho +skinparam component { + BackgroundColor #FDFDFD + BorderColor #666666 +} +skinparam database { + BackgroundColor #EEF5FF + BorderColor #666666 +} + +actor "Document manager" as Operator + +component "CMTAT token A" as TokenA +component "CMTAT token B" as TokenB +component "**DocumentEngine**" as Engine +database "documents,\nkeyed by subject" as Store + +TokenA --> Engine : setDocument(name, ...) +TokenB --> Engine : setDocument(name, ...) +Operator --> Engine : setDocument(**subject**, name, ...)\nbindToken(token) +Engine --> Store + +note right of TokenA + **Bound-token path.** The token calls the + single-argument ERC-1643 functions; the + engine uses msg.sender as the subject, so + a token can only touch its own documents. +end note + +note right of Operator + **Admin path.** Names the subject + explicitly, so one manager can write + for any contract in the fleet. +end note + +@enduml diff --git a/doc/img/cmtat-integration-sequence.png b/doc/img/cmtat-integration-sequence.png new file mode 100644 index 0000000..543df0c Binary files /dev/null and b/doc/img/cmtat-integration-sequence.png differ diff --git a/doc/img/cmtat-integration-sequence.puml b/doc/img/cmtat-integration-sequence.puml new file mode 100644 index 0000000..cbdc8b6 --- /dev/null +++ b/doc/img/cmtat-integration-sequence.puml @@ -0,0 +1,100 @@ +@startuml +title DocumentEngine + CMTAT — wiring, writes, and who emits what + +skinparam shadowing false +skinparam sequenceMessageAlign left +skinparam ParticipantPadding 12 +skinparam BoxPadding 12 + +actor "Engine\ndocument manager" as Operator +actor "Token\ndocument manager" as TokenAdmin +participant "CMTAT token\nDocumentEngineModule" as Token +participant "DocumentEngine\nDocumentEngineBase\n+ TokenBindingModule" as Engine + +== 1. Wiring (once) == + +Operator -> Engine : bindToken(token) +activate Engine +Engine -> Engine : _authorizeDocumentManagement() +Engine -> Engine : _setTokenBinding(token, true) +Engine --> Operator : emit **TokenBindingSet**(token, true)\non the engine — only on a real change +deactivate Engine + +TokenAdmin -> Token : setDocumentEngine(engine) +activate Token +Token --> TokenAdmin : emit **DocumentEngine**(engine)\non the token +deactivate Token + +note over Token, Engine + Both steps are required and are independent. + Binding without setDocumentEngine → the token has nowhere to forward to. + setDocumentEngine without binding → the forwarded call reverts **NotBoundToken**. +end note + +== 2. Bound-token write — the path integrators use == + +TokenAdmin -> Token : setDocument(name, uri, hash) +activate Token +Token -> Token : _authorizeDocumentManagement()\ntoken-side access control + +alt no engine configured + Token --> TokenAdmin : revert **CMTAT_DocumentEngineModule_NoDocumentEngine** +else engine configured + Token -> Engine : setDocument(name, uri, hash)\nmsg.sender = token + activate Engine + Engine -> Engine : _checkTokenBound() + + alt token not bound + Engine --> Token : revert **NotBoundToken**(token) + else name == 0 + Engine --> Token : revert **ERC1643InvalidName**() + else accepted + Engine -> Engine : _setDocument(subject = _msgSender(), ...)\nwrites _documents[token][name] + Engine --> Engine : emit **DocumentUpdatedForSubject**(token, name, uri, hash)\non the ENGINE — carries the subject address + end + deactivate Engine + + Token --> TokenAdmin : emit **DocumentUpdated**(name, uri, hash)\non the TOKEN — the address ERC-1643 consumers watch +end +deactivate Token + +note over Token, Engine #EEF5FF + **Emission responsibility.** The engine is a shared multi-subject manager, so it emits + only the address-carrying `*ForSubject` events — the base ERC-1643 events carry no + address and could not say which subject changed. The token re-emits the base event on + its own address. Together the pair is conformant. +end note + +== 3. Admin write — same storage, one event short == + +Operator -> Engine : setDocument(**subject**, name, uri, hash) +activate Engine +Engine -> Engine : _authorizeDocumentManagement() +Engine -> Engine : _setDocument(subject, ...) +Engine --> Operator : emit **DocumentUpdatedForSubject**(subject, ...) +deactivate Engine + +note over Token, Engine #FDEEEE + The subject is **never called**, so it emits nothing: anyone subscribed to the token's + address sees no change. Tracked as **OPEN-2** in AUDIT_OVERVIEW.md. Use the bound-token + path when consumers watch the token; use the admin path for bulk/backfill operations. +end note + +== 4. Read == + +participant "Consumer" as Consumer +Consumer -> Token : getDocument(name) +activate Token +Token -> Engine : getDocument(name)\nmsg.sender = token +Engine --> Token : (uri, hash, lastModified)\nfrom _documents[token][name] +Token --> Consumer : (uri, hash, lastModified) +deactivate Token + +note over Consumer, Engine + Read through the **token**, or use the engine's address-scoped + getDocument(subject, name). Calling the engine's single-argument + getDocument(name) directly reads the **caller's own** namespace — + empty, with no revert. +end note + +@enduml diff --git a/doc/img/cmtat-read-simple.png b/doc/img/cmtat-read-simple.png new file mode 100644 index 0000000..0470f5c Binary files /dev/null and b/doc/img/cmtat-read-simple.png differ diff --git a/doc/img/cmtat-read-simple.puml b/doc/img/cmtat-read-simple.puml new file mode 100644 index 0000000..4221621 --- /dev/null +++ b/doc/img/cmtat-read-simple.puml @@ -0,0 +1,43 @@ +@startuml +title Reading a document — two valid routes, one trap + +skinparam shadowing false +skinparam sequenceMessageAlign center +skinparam ParticipantPadding 20 + +actor "Consumer" as User +participant "CMTAT token" as Token +participant "DocumentEngine" as Engine + +group Route 1 — ask the token (standard ERC-1643) + User -> Token : getDocument(name) + activate Token + Token -> Engine : getDocument(name) + note right : msg.sender = token + activate Engine + Engine --> Token : uri, hash, lastModified + deactivate Engine + Token --> User : uri, hash, lastModified + deactivate Token +end + +group Route 2 — ask the engine, naming the subject + User -> Engine : getDocument(**token**, name) + activate Engine + Engine --> User : uri, hash, lastModified + deactivate Engine +end + +group #FDEEEE Do not do this + User -> Engine : getDocument(name) + activate Engine + Engine --> User : "", 0x0, 0 + deactivate Engine + note over User, Engine #FDEEEE + The single-argument read is scoped to **msg.sender**, so a + third party reads its **own** empty namespace. It returns + empty values instead of reverting — silent, not an error. + end note +end + +@enduml diff --git a/doc/img/cmtat-write-simple.png b/doc/img/cmtat-write-simple.png new file mode 100644 index 0000000..afc8978 Binary files /dev/null and b/doc/img/cmtat-write-simple.png differ diff --git a/doc/img/cmtat-write-simple.puml b/doc/img/cmtat-write-simple.puml new file mode 100644 index 0000000..bf015bc --- /dev/null +++ b/doc/img/cmtat-write-simple.puml @@ -0,0 +1,33 @@ +@startuml +title Writing a document through a CMTAT token + +skinparam shadowing false +skinparam sequenceMessageAlign center +skinparam ParticipantPadding 20 + +actor "Document\nmanager" as Admin +participant "CMTAT token" as Token +participant "DocumentEngine" as Engine +database "_documents\n[token][name]" as Store + +Admin -> Token : setDocument(name, uri, hash) +activate Token + +Token -> Engine : setDocument(name, uri, hash) +note right : msg.sender = token,\nso the token is the subject +activate Engine + +Engine -> Store : store the document +Engine --> Token : emit **DocumentUpdatedForSubject**(token, name, ...) +deactivate Engine + +Token --> Admin : emit **DocumentUpdated**(name, ...) +deactivate Token + +note over Token, Engine #EEF5FF + Two events, on purpose: the engine names the **subject** + (it serves many tokens), the token emits the plain + ERC-1643 event on **its own** address. +end note + +@enduml diff --git a/doc/img/documentengine-contract-structure.png b/doc/img/documentengine-contract-structure.png new file mode 100644 index 0000000..3886c8f Binary files /dev/null and b/doc/img/documentengine-contract-structure.png differ diff --git a/doc/img/documentengine-contract-structure.puml b/doc/img/documentengine-contract-structure.puml new file mode 100644 index 0000000..7b36a95 --- /dev/null +++ b/doc/img/documentengine-contract-structure.puml @@ -0,0 +1,66 @@ +@startuml +title Contract structure — two deployments over one shared base + +skinparam shadowing false +skinparam linetype ortho +skinparam class { + BackgroundColor #FDFDFD + BorderColor #666666 +} +hide empty members +hide circle + +abstract class DocumentEngineBase <> #EEF5FF { + document storage + all ERC-1643 logic + -- + {abstract} _authorizeDocumentManagement() + {abstract} _authorizeBoundTokenDocumentManagement() +} + +abstract class TokenBindingModule <> #EEF5FF { + binding allowlist (ITokenBinding) + -- + implements _authorizeBoundTokenDocumentManagement() +} + +abstract class VersionModule <> #EEF5FF { + ERC-8303 version() +} + +class DocumentEngine #FFF7E6 { + DOCUMENT_MANAGER_ROLE + -- + implements _authorizeDocumentManagement() +} + +class DocumentEngineOwnable #FFF7E6 { + owner + -- + implements _authorizeDocumentManagement() +} + +together { + class AccessControlEnumerable + class Ownable2Step + class ERC2771Context +} + +DocumentEngineBase <|-- TokenBindingModule + +TokenBindingModule <|-- DocumentEngine +VersionModule <|-- DocumentEngine +AccessControlEnumerable <|-- DocumentEngine +ERC2771Context <|-- DocumentEngine + +TokenBindingModule <|-- DocumentEngineOwnable +VersionModule <|-- DocumentEngineOwnable +Ownable2Step <|-- DocumentEngineOwnable +ERC2771Context <|-- DocumentEngineOwnable + +legend bottom + blue = shared by both deployments · orange = the deployment itself · white = OpenZeppelin + A deployment supplies **only** the access-control hook (_authorizeDocumentManagement), + which is the single point where the two differ. +endlegend + +@enduml diff --git a/doc/slither-report.md b/doc/slither-report.md deleted file mode 100644 index cc8c1dd..0000000 --- a/doc/slither-report.md +++ /dev/null @@ -1,38 +0,0 @@ -**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results. -Summary - - [dead-code](#dead-code) (1 results) (Informational) - - [solc-version](#solc-version) (1 results) (Informational) -## dead-code - -> Acknowledge - -Impact: Informational -Confidence: Medium - - - [ ] ID-0 -[DocumentEngine._msgData()](src/DocumentEngine.sol#L265-L272) is never used and should be removed - -src/DocumentEngine.sol#L265-L272 - -## solc-version - -> Acknowledge - -Impact: Informational -Confidence: High - - [ ] ID-1 - Version constraint ^0.8.20 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - - VerbatimInvalidDeduplication - - FullInlinerNonExpressionSplitArgumentEvaluationOrder - - MissingSideEffectsOnSelectorAccess. - It is used by: - - lib/CMTAT/contracts/interfaces/engine/draft-IERC1643.sol#3 - - lib/openzeppelin-contracts/contracts/access/AccessControl.sol#4 - - lib/openzeppelin-contracts/contracts/access/IAccessControl.sol#4 - - lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol#4 - - lib/openzeppelin-contracts/contracts/utils/Context.sol#4 - - lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol#4 - - lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol#4 - - src/DocumentEngine.sol#2 - - src/DocumentEngineInvariant.sol#2 - diff --git a/doc/surya/surya_graph/surya_graph_DocumentEngine.sol.png b/doc/surya/surya_graph/surya_graph_DocumentEngine.sol.png index ed1905f..ee636b2 100644 Binary files a/doc/surya/surya_graph/surya_graph_DocumentEngine.sol.png and b/doc/surya/surya_graph/surya_graph_DocumentEngine.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_DocumentEngineBase.sol.png b/doc/surya/surya_graph/surya_graph_DocumentEngineBase.sol.png new file mode 100644 index 0000000..3d48907 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_DocumentEngineBase.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_DocumentEngineInvariant.sol.png b/doc/surya/surya_graph/surya_graph_DocumentEngineInvariant.sol.png index e166f8f..5b4fa6b 100644 Binary files a/doc/surya/surya_graph/surya_graph_DocumentEngineInvariant.sol.png and b/doc/surya/surya_graph/surya_graph_DocumentEngineInvariant.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_DocumentEngineOwnable.sol.png b/doc/surya/surya_graph/surya_graph_DocumentEngineOwnable.sol.png new file mode 100644 index 0000000..23a2185 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_DocumentEngineOwnable.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_IERC1643MultiDocument.sol.png b/doc/surya/surya_graph/surya_graph_IERC1643MultiDocument.sol.png new file mode 100644 index 0000000..2e7d7dc Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_IERC1643MultiDocument.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_IERC8303.sol.png b/doc/surya/surya_graph/surya_graph_IERC8303.sol.png new file mode 100644 index 0000000..8d0dbd3 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_IERC8303.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_ITokenBinding.sol.png b/doc/surya/surya_graph/surya_graph_ITokenBinding.sol.png new file mode 100644 index 0000000..00d1b1f Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_ITokenBinding.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_TokenBindingModule.sol.png b/doc/surya/surya_graph/surya_graph_TokenBindingModule.sol.png new file mode 100644 index 0000000..6a14dc8 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_TokenBindingModule.sol.png differ diff --git a/doc/surya/surya_graph/surya_graph_VersionModule.sol.png b/doc/surya/surya_graph/surya_graph_VersionModule.sol.png new file mode 100644 index 0000000..a8db0d5 Binary files /dev/null and b/doc/surya/surya_graph/surya_graph_VersionModule.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_DocumentEngine.sol.png b/doc/surya/surya_inheritance/surya_inheritance_DocumentEngine.sol.png index add3eb2..d0de70b 100644 Binary files a/doc/surya/surya_inheritance/surya_inheritance_DocumentEngine.sol.png and b/doc/surya/surya_inheritance/surya_inheritance_DocumentEngine.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_DocumentEngineBase.sol.png b/doc/surya/surya_inheritance/surya_inheritance_DocumentEngineBase.sol.png new file mode 100644 index 0000000..91f3846 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_DocumentEngineBase.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_DocumentEngineOwnable.sol.png b/doc/surya/surya_inheritance/surya_inheritance_DocumentEngineOwnable.sol.png new file mode 100644 index 0000000..4e2b439 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_DocumentEngineOwnable.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_IERC1643MultiDocument.sol.png b/doc/surya/surya_inheritance/surya_inheritance_IERC1643MultiDocument.sol.png new file mode 100644 index 0000000..c463074 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_IERC1643MultiDocument.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_IERC8303.sol.png b/doc/surya/surya_inheritance/surya_inheritance_IERC8303.sol.png new file mode 100644 index 0000000..8d83e91 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_IERC8303.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_ITokenBinding.sol.png b/doc/surya/surya_inheritance/surya_inheritance_ITokenBinding.sol.png new file mode 100644 index 0000000..f9d8287 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_ITokenBinding.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_TokenBindingModule.sol.png b/doc/surya/surya_inheritance/surya_inheritance_TokenBindingModule.sol.png new file mode 100644 index 0000000..0a96320 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_TokenBindingModule.sol.png differ diff --git a/doc/surya/surya_inheritance/surya_inheritance_VersionModule.sol.png b/doc/surya/surya_inheritance/surya_inheritance_VersionModule.sol.png new file mode 100644 index 0000000..b113a03 Binary files /dev/null and b/doc/surya/surya_inheritance/surya_inheritance_VersionModule.sol.png differ diff --git a/doc/surya/surya_report/surya_report_DocumentEngine.sol.md b/doc/surya/surya_report/surya_report_DocumentEngine.sol.md index 69a3c95..b2c75ec 100644 --- a/doc/surya/surya_report/surya_report_DocumentEngine.sol.md +++ b/doc/surya/surya_report/surya_report_DocumentEngine.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./DocumentEngine.sol | [object Promise] | +| ./DocumentEngine.sol | f94dc434fc6173eace632f1f3c70857c1b28dd69 | ### Contracts Description Table @@ -15,23 +15,11 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **DocumentEngine** | Implementation | IERC1643, DocumentEngineInvariant, AccessControl, ERC2771Context ||| +| **DocumentEngine** | Implementation | TokenBindingModule, VersionModule, AccessControlEnumerable, ERC2771Context ||| | └ | | Public ❗️ | 🛑 | ERC2771Context | -| └ | setDocument | Public ❗️ | 🛑 | onlyRole | -| └ | removeDocument | External ❗️ | 🛑 | onlyRole | -| └ | batchSetDocuments | External ❗️ | 🛑 | onlyRole | -| └ | batchSetDocuments | External ❗️ | 🛑 | onlyRole | -| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyRole | -| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyRole | -| └ | getDocument | External ❗️ | |NO❗️ | -| └ | getDocument | External ❗️ | |NO❗️ | -| └ | getAllDocuments | External ❗️ | |NO❗️ | -| └ | getAllDocuments | External ❗️ | |NO❗️ | | └ | hasRole | Public ❗️ | |NO❗️ | -| └ | _getDocument | Internal 🔒 | | | -| └ | _removeDocumentName | Internal 🔒 | 🛑 | | -| └ | _removeDocument | Internal 🔒 | 🛑 | | -| └ | _setDocument | Internal 🔒 | 🛑 | | +| └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | _authorizeDocumentManagement | Internal 🔒 | | | | └ | _msgSender | Internal 🔒 | | | | └ | _msgData | Internal 🔒 | | | | └ | _contextSuffixLength | Internal 🔒 | | | diff --git a/doc/surya/surya_report/surya_report_DocumentEngineBase.sol.md b/doc/surya/surya_report/surya_report_DocumentEngineBase.sol.md new file mode 100644 index 0000000..72af896 --- /dev/null +++ b/doc/surya/surya_report/surya_report_DocumentEngineBase.sol.md @@ -0,0 +1,44 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./DocumentEngineBase.sol | 2c5a7b3ace8bd7e83ea19b1d53dc833fc4ec5349 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **DocumentEngineBase** | Implementation | IERC1643, IERC1643MultiDocument, DocumentEngineInvariant, Context ||| +| └ | removeDocument | External ❗️ | 🛑 | onlyDocumentManager | +| └ | setDocument | External ❗️ | 🛑 | onlyBoundToken | +| └ | removeDocument | External ❗️ | 🛑 | onlyBoundToken | +| └ | batchSetDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | batchSetDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | batchRemoveDocuments | External ❗️ | 🛑 | onlyDocumentManager | +| └ | getDocument | External ❗️ | |NO❗️ | +| └ | getDocument | External ❗️ | |NO❗️ | +| └ | getAllDocuments | External ❗️ | |NO❗️ | +| └ | getAllDocuments | External ❗️ | |NO❗️ | +| └ | setDocument | Public ❗️ | 🛑 | onlyDocumentManager | +| └ | _removeDocumentName | Internal 🔒 | 🛑 | | +| └ | _removeDocument | Internal 🔒 | 🛑 | | +| └ | _setDocument | Internal 🔒 | 🛑 | | +| └ | _authorizeDocumentManagement | Internal 🔒 | | | +| └ | _authorizeBoundTokenDocumentManagement | Internal 🔒 | | | +| └ | _getDocument | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_DocumentEngineInvariant.sol.md b/doc/surya/surya_report/surya_report_DocumentEngineInvariant.sol.md index c310e6e..20c75e4 100644 --- a/doc/surya/surya_report/surya_report_DocumentEngineInvariant.sol.md +++ b/doc/surya/surya_report/surya_report_DocumentEngineInvariant.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./DocumentEngineInvariant.sol | [object Promise] | +| ./DocumentEngineInvariant.sol | c6d759bcf18c530f78db5ec5cebc7ddd7b715ff3 | ### Contracts Description Table diff --git a/doc/surya/surya_report/surya_report_DocumentEngineOwnable.sol.md b/doc/surya/surya_report/surya_report_DocumentEngineOwnable.sol.md new file mode 100644 index 0000000..76b793a --- /dev/null +++ b/doc/surya/surya_report/surya_report_DocumentEngineOwnable.sol.md @@ -0,0 +1,32 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./DocumentEngineOwnable.sol | a58193c6ae7f706de6e47a10dab38ca78cea208f | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **DocumentEngineOwnable** | Implementation | TokenBindingModule, VersionModule, Ownable2Step, ERC2771Context ||| +| └ | | Public ❗️ | 🛑 | Ownable ERC2771Context | +| └ | supportsInterface | Public ❗️ | |NO❗️ | +| └ | _authorizeDocumentManagement | Internal 🔒 | | | +| └ | _msgSender | Internal 🔒 | | | +| └ | _msgData | Internal 🔒 | | | +| └ | _contextSuffixLength | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_IERC1643MultiDocument.sol.md b/doc/surya/surya_report/surya_report_IERC1643MultiDocument.sol.md new file mode 100644 index 0000000..4ae84c1 --- /dev/null +++ b/doc/surya/surya_report/surya_report_IERC1643MultiDocument.sol.md @@ -0,0 +1,30 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./interfaces/IERC1643MultiDocument.sol | ebf7a8b0cc52897f5caefe51d12a4ec7b63cfa38 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **IERC1643MultiDocument** | Interface | ||| +| └ | setDocument | External ❗️ | 🛑 |NO❗️ | +| └ | removeDocument | External ❗️ | 🛑 |NO❗️ | +| └ | getDocument | External ❗️ | |NO❗️ | +| └ | getAllDocuments | External ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_IERC8303.sol.md b/doc/surya/surya_report/surya_report_IERC8303.sol.md new file mode 100644 index 0000000..7ba8997 --- /dev/null +++ b/doc/surya/surya_report/surya_report_IERC8303.sol.md @@ -0,0 +1,27 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./interfaces/IERC8303.sol | d4d17c6161ae92f56abcd7f6b80ea7a5cc6bd99e | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **IERC8303** | Interface | ||| +| └ | version | External ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_ITokenBinding.sol.md b/doc/surya/surya_report/surya_report_ITokenBinding.sol.md new file mode 100644 index 0000000..d50a101 --- /dev/null +++ b/doc/surya/surya_report/surya_report_ITokenBinding.sol.md @@ -0,0 +1,29 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./interfaces/ITokenBinding.sol | be03e0f7cda1b263ac7e94f6e57a9e23ad7ccc4a | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **ITokenBinding** | Interface | ||| +| └ | bindToken | External ❗️ | 🛑 |NO❗️ | +| └ | unbindToken | External ❗️ | 🛑 |NO❗️ | +| └ | isTokenBound | External ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_TokenBindingModule.sol.md b/doc/surya/surya_report/surya_report_TokenBindingModule.sol.md new file mode 100644 index 0000000..f9b4546 --- /dev/null +++ b/doc/surya/surya_report/surya_report_TokenBindingModule.sol.md @@ -0,0 +1,32 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./modules/TokenBindingModule.sol | af09f7e1f947e9bb4fcef227a1be44d97159d0d7 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **TokenBindingModule** | Implementation | DocumentEngineBase, ITokenBinding ||| +| └ | bindToken | External ❗️ | 🛑 |NO❗️ | +| └ | unbindToken | External ❗️ | 🛑 |NO❗️ | +| └ | isTokenBound | Public ❗️ | |NO❗️ | +| └ | _setTokenBinding | Internal 🔒 | 🛑 | | +| └ | _authorizeBoundTokenDocumentManagement | Internal 🔒 | | | +| └ | _checkTokenBound | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/surya/surya_report/surya_report_VersionModule.sol.md b/doc/surya/surya_report/surya_report_VersionModule.sol.md new file mode 100644 index 0000000..b50d39c --- /dev/null +++ b/doc/surya/surya_report/surya_report_VersionModule.sol.md @@ -0,0 +1,28 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./modules/VersionModule.sol | c57266064b38591fd3eda39669e5ce7e0ab759e8 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **VersionModule** | Implementation | IERC8303, ERC165 ||| +| └ | version | Public ❗️ | |NO❗️ | +| └ | supportsInterface | Public ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..423426c --- /dev/null +++ b/foundry.lock @@ -0,0 +1,32 @@ +{ + "lib/CMTAT": { + "tag": { + "name": "v3.3.0-rc3", + "rev": "658672f190d56d3f61663a7d6d51962b8980df70" + } + }, + "lib/RuleEngine": { + "tag": { + "name": "v3.0.0-rc5", + "rev": "ab9def2f19ae71af304127f42d20d9831cad1a2b" + } + }, + "lib/forge-std": { + "tag": { + "name": "v1.7.1", + "rev": "f73c73d2018eb6a111f35e4dae7b4f27401e9421" + } + }, + "lib/openzeppelin-contracts": { + "tag": { + "name": "v5.7.0", + "rev": "cab19933c33c2ad1d4c7a84864a3601dddfd16f3" + } + }, + "lib/openzeppelin-contracts-upgradeable": { + "tag": { + "name": "v5.7.0", + "rev": "14f52c54d3a1eefbda3d4071efba24d3c1e07e8a" + } + } +} diff --git a/foundry.toml b/foundry.toml index acc6e5a..67b08dd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,10 +1,19 @@ [profile.default] -solc = "0.8.26" +solc = "0.8.34" src = "src" out = "out" libs = ["lib"] optimizer = true optimizer_runs = 200 -evm_version = 'cancun' +evm_version = 'prague' + +# `forge fmt` is the canonical formatter for this project (run `forge fmt`). +[fmt] +line_length = 120 +tab_width = 4 +bracket_spacing = false +int_types = "long" +quote_style = "double" +number_underscore = "preserve" # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/CMTAT b/lib/CMTAT index e8048d4..658672f 160000 --- a/lib/CMTAT +++ b/lib/CMTAT @@ -1 +1 @@ -Subproject commit e8048d43b0299afd83f150d3725ab299994b4271 +Subproject commit 658672f190d56d3f61663a7d6d51962b8980df70 diff --git a/lib/RuleEngine b/lib/RuleEngine new file mode 160000 index 0000000..ab9def2 --- /dev/null +++ b/lib/RuleEngine @@ -0,0 +1 @@ +Subproject commit ab9def2f19ae71af304127f42d20d9831cad1a2b diff --git a/lib/forge-std b/lib/forge-std index 1714bee..f73c73d 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1714bee72e286e73f76e320d110e0eaf5c4e649d +Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index dbb6104..cab1993 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit cab19933c33c2ad1d4c7a84864a3601dddfd16f3 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 723f8ca..14f52c5 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 +Subproject commit 14f52c54d3a1eefbda3d4071efba24d3c1e07e8a diff --git a/package-lock.json b/package-lock.json index c44ede8..13a3795 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,12 +4,114 @@ "requires": true, "packages": { "": { + "name": "DocumentEngine", "devDependencies": { - "prettier-plugin-solidity": "^1.4.1", "solidity-docgen": "^0.6.0-beta.36", "surya": "^0.4.11" } }, + "node_modules/@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ethereumjs/util": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-9.1.0.tgz", + "integrity": "sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "@ethereumjs/rlp": "^5.0.2", + "ethereum-cryptography": "^2.2.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ethereumjs/util/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -155,9 +257,9 @@ } }, "node_modules/@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", "dev": true, "funding": [ { @@ -169,9 +271,10 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "license": "MIT", "peer": true, "dependencies": { - "@ethersproject/logger": "^5.7.0" + "@ethersproject/logger": "^5.8.0" } }, "node_modules/@ethersproject/constants": { @@ -244,9 +347,9 @@ } }, "node_modules/@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", "dev": true, "funding": [ { @@ -258,6 +361,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "license": "MIT", "peer": true }, "node_modules/@ethersproject/networks": { @@ -281,9 +385,9 @@ } }, "node_modules/@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", "dev": true, "funding": [ { @@ -295,9 +399,10 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "license": "MIT", "peer": true, "dependencies": { - "@ethersproject/logger": "^5.7.0" + "@ethersproject/logger": "^5.8.0" } }, "node_modules/@ethersproject/rlp": { @@ -322,9 +427,9 @@ } }, "node_modules/@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", "dev": true, "funding": [ { @@ -336,13 +441,14 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], + "license": "MIT", "peer": true, "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", "bn.js": "^5.2.1", - "elliptic": "6.5.4", + "elliptic": "6.6.1", "hash.js": "1.1.7" } }, @@ -430,21 +536,145 @@ "node": ">=14" } }, - "node_modules/@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "peer": true, "dependencies": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "1.7.2" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { @@ -474,209 +704,100 @@ "peer": true }, "node_modules/@nomicfoundation/edr": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.5.2.tgz", - "integrity": "sha512-hW/iLvUQZNTVjFyX/I40rtKvvDOqUEyIi96T28YaLfmPL+3LW2lxmYLUXEJ6MI14HzqxDqrLyhf6IbjAa2r3Dw==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0-next.23.tgz", + "integrity": "sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.5.2", - "@nomicfoundation/edr-darwin-x64": "0.5.2", - "@nomicfoundation/edr-linux-arm64-gnu": "0.5.2", - "@nomicfoundation/edr-linux-arm64-musl": "0.5.2", - "@nomicfoundation/edr-linux-x64-gnu": "0.5.2", - "@nomicfoundation/edr-linux-x64-musl": "0.5.2", - "@nomicfoundation/edr-win32-x64-msvc": "0.5.2" + "@nomicfoundation/edr-darwin-arm64": "0.12.0-next.23", + "@nomicfoundation/edr-darwin-x64": "0.12.0-next.23", + "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.23", + "@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.23", + "@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.23", + "@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.23", + "@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.23" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.5.2.tgz", - "integrity": "sha512-Gm4wOPKhbDjGTIRyFA2QUAPfCXA1AHxYOKt3yLSGJkQkdy9a5WW+qtqKeEKHc/+4wpJSLtsGQfpzyIzggFfo/A==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0-next.23.tgz", + "integrity": "sha512-Amh7mRoDzZyJJ4efqoePqdoZOzharmSOttZuJDlVE5yy07BoE8hL6ZRpa5fNYn0LCqn/KoWs8OHANWxhKDGhvQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.5.2.tgz", - "integrity": "sha512-ClyABq2dFCsrYEED3/UIO0c7p4H1/4vvlswFlqUyBpOkJccr75qIYvahOSJRM62WgUFRhbSS0OJXFRwc/PwmVg==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0-next.23.tgz", + "integrity": "sha512-9wn489FIQm7m0UCD+HhktjWx6vskZzeZD9oDc2k9ZvbBzdXwPp5tiDqUBJ+eQpByAzCDfteAJwRn2lQCE0U+Iw==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.5.2.tgz", - "integrity": "sha512-HWMTVk1iOabfvU2RvrKLDgtFjJZTC42CpHiw2h6rfpsgRqMahvIlx2jdjWYzFNy1jZKPTN1AStQ/91MRrg5KnA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0-next.23.tgz", + "integrity": "sha512-nlk5EejSzEUfEngv0Jkhqq3/wINIfF2ED9wAofc22w/V1DV99ASh9l3/e/MIHOQFecIZ9MDqt0Em9/oDyB1Uew==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.5.2.tgz", - "integrity": "sha512-CwsQ10xFx/QAD5y3/g5alm9+jFVuhc7uYMhrZAu9UVF+KtVjeCvafj0PaVsZ8qyijjqVuVsJ8hD1x5ob7SMcGg==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0-next.23.tgz", + "integrity": "sha512-SJuPBp3Rc6vM92UtVTUxZQ/QlLhLfwTftt2XUiYohmGKB3RjGzpgduEFMCA0LEnucUckU6UHrJNFHiDm77C4PQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.5.2.tgz", - "integrity": "sha512-CWVCEdhWJ3fmUpzWHCRnC0/VLBDbqtqTGTR6yyY1Ep3S3BOrHEAvt7h5gx85r2vLcztisu2vlDq51auie4IU1A==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0-next.23.tgz", + "integrity": "sha512-NU+Qs3u7Qt6t3bJFdmmjd5CsvgI2bPPzO31KifM2Ez96/jsXYho5debtTQnimlb5NAqiHTSlxjh/F8ROcptmeQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.5.2.tgz", - "integrity": "sha512-+aJDfwhkddy2pP5u1ISg3IZVAm0dO836tRlDTFWtvvSMQ5hRGqPcWwlsbobhDQsIxhPJyT7phL0orCg5W3WMeA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0-next.23.tgz", + "integrity": "sha512-F78fZA2h6/ssiCSZOovlgIu0dUeI7ItKPsDDF3UUlIibef052GCXmliMinC90jVPbrjUADMd1BUwjfI0Z8OllQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.5.2.tgz", - "integrity": "sha512-CcvvuA3sAv7liFNPsIR/68YlH6rrybKzYttLlMr80d4GKJjwJ5OKb3YgE6FdZZnOfP19HEHhsLcE0DPLtY3r0w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", - "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", - "dev": true, - "peer": true, - "dependencies": { - "@nomicfoundation/ethereumjs-util": "9.0.4" - } - }, - "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", - "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", - "dev": true, - "peer": true, - "bin": { - "rlp": "bin/rlp.cjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", - "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", - "dev": true, - "peer": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", - "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0-next.23.tgz", + "integrity": "sha512-IfJZQJn7d/YyqhmguBIGoCKjE9dKjbu6V6iNEPApfwf5JyyjHYyyfkLU4rf7hygj57bfH4sl1jtQ6r8HnT62lw==", "dev": true, + "license": "MIT", "peer": true, - "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "ethereum-cryptography": "0.1.3" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "node": ">= 20" } }, "node_modules/@nomicfoundation/solidity-analyzer": { @@ -775,6 +896,18 @@ "node": ">= 12" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@scure/base": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", @@ -929,59 +1062,6 @@ "node": ">=6" } }, - "node_modules/@solidity-parser/parser": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", - "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", - "dev": true - }, - "node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true, - "peer": true - }, - "node_modules/@types/node": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", - "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", - "dev": true, - "peer": true, - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/secp256k1": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", - "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", @@ -1098,25 +1178,12 @@ "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", "dev": true }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, + "license": "Python-2.0", "peer": true }, "node_modules/array-buffer-byte-length": { @@ -1197,43 +1264,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "peer": true - }, - "node_modules/base-x": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", - "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true, + "license": "MIT", "peer": true }, "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/boxen": { @@ -1259,46 +1298,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/boxen/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -1313,27 +1312,14 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0" } }, "node_modules/brorand": { @@ -1348,45 +1334,9 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true, + "license": "ISC", "peer": true }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "peer": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "peer": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "peer": true, - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1394,13 +1344,6 @@ "dev": true, "peer": true }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true, - "peer": true - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1450,73 +1393,52 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "color-convert": "^1.9.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" + "node": ">=8" } }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/ci-info": { @@ -1526,17 +1448,6 @@ "dev": true, "peer": true }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -1618,13 +1529,6 @@ "node": ">= 12" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "peer": true - }, "node_modules/cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -1635,33 +1539,20 @@ "node": ">= 0.6" } }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "peer": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, "node_modules/data-view-buffer": { @@ -1738,6 +1629,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -1791,20 +1683,30 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "engines": { "node": ">=0.3.1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "bn.js": "^4.11.9", @@ -1817,10 +1719,11 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/emoji-regex": { @@ -1996,13 +1899,17 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ethereum-cryptography": { @@ -2018,131 +1925,41 @@ "@scure/bip39": "1.1.1" } }, - "node_modules/ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - } - }, - "node_modules/ethereumjs-abi/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/ethereumjs-util/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", "peer": true, - "dependencies": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - }, "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "peer": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" + "node": ">=12.0.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "locate-path": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat": { @@ -2150,15 +1967,16 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "bin": { "flat": "cli.js" } }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -2166,6 +1984,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "peer": true, "engines": { "node": ">=4.0" @@ -2185,6 +2004,24 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fp-ts": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", @@ -2207,28 +2044,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "peer": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2311,40 +2126,28 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -2393,10 +2196,11 @@ } }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -2414,51 +2218,48 @@ } }, "node_modules/hardhat": { - "version": "2.22.9", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.9.tgz", - "integrity": "sha512-sWiuI/yRdFUPfndIvL+2H18Vs2Gav0XacCFYY5msT5dHOWkhLxESJySIk9j83mXL31aXL8+UMA9OgViFLexklg==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.29.0.tgz", + "integrity": "sha512-tsj5mCSjDCFOhGfBl4vwqDEcwdlES9VUzRWfdrwvEVhus6D8W6u+WfUKRLLwFhKGS/8lKPoXGsjYWPXl3CCpOg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { + "@ethereumjs/util": "^9.1.0", "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.5.2", - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-tx": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", + "@nomicfoundation/edr": "0.12.0-next.23", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", "adm-zip": "^0.4.16", "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", "boxen": "^5.1.2", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", + "chokidar": "^4.0.0", "ci-info": "^2.0.0", "debug": "^4.1.1", "enquirer": "^2.3.0", "env-paths": "^2.2.0", "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", + "find-up": "^5.0.0", "fp-ts": "1.19.3", "fs-extra": "^7.0.1", - "glob": "7.2.0", "immutable": "^4.0.0-rc.12", "io-ts": "1.10.4", + "json-stream-stringify": "^3.1.4", "keccak": "^3.0.2", "lodash": "^4.17.11", + "micro-eth-signer": "^0.14.0", "mnemonist": "^0.38.0", - "mocha": "^10.0.0", + "mocha": "^11.1.0", "p-map": "^4.0.0", + "picocolors": "^1.1.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", "solc": "0.8.26", "source-map-support": "^0.5.13", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.6", "tsort": "0.0.1", "undici": "^5.14.0", "uuid": "^8.3.2", @@ -2500,13 +2301,14 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -2560,21 +2362,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -2619,6 +2406,7 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, + "license": "MIT", "peer": true, "bin": { "he": "bin/he" @@ -2681,10 +2469,11 @@ } }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz", + "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/indent-string": { @@ -2697,18 +2486,6 @@ "node": ">=8" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "peer": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2768,19 +2545,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -2839,16 +2603,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2858,30 +2612,6 @@ "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "peer": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -2894,16 +2624,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", @@ -2919,11 +2639,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -3022,6 +2754,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -3048,6 +2781,31 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -3056,10 +2814,21 @@ "peer": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", "peer": true, "dependencies": { "argparse": "^2.0.1" @@ -3068,6 +2837,17 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-stream-stringify": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.7.tgz", + "integrity": "sha512-F4MWetLtY42YMaAKw5cV4e47zMD5aOT+tjjQWjX18ACtdkQ5Y/vrcfbcQ107Rh+MXjOCIx4KhW0wPmOvG8iQ5w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=7.10.1" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -3084,6 +2864,7 @@ "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "peer": true, "dependencies": { "node-addon-api": "^2.0.0", @@ -3095,24 +2876,28 @@ } }, "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/log-symbols": { @@ -3120,6 +2905,7 @@ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "chalk": "^4.1.0", @@ -3132,73 +2918,81 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "peer": true }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", "dev": true, "peer": true, "engines": { - "node": ">=8" + "node": ">= 0.10.0" } }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/micro-eth-signer": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz", + "integrity": "sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "micro-packed": "~0.7.2" } }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "node_modules/micro-eth-signer/node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "dev": true, - "peer": true + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "node_modules/micro-packed": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", + "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "node_modules/micro-packed/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true, + "license": "MIT", "peer": true, - "engines": { - "node": ">= 0.10.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/minimalistic-assert": { @@ -3216,16 +3010,20 @@ "peer": true }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "peer": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -3237,6 +3035,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mnemonist": { "version": "0.38.5", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", @@ -3248,31 +3057,33 @@ } }, "node_modules/mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", + "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^8.1.0", + "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { @@ -3280,119 +3091,7 @@ "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/mocha/node_modules/ms": { @@ -3400,95 +3099,9 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, + "license": "MIT", "peer": true }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "peer": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3507,13 +3120,15 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, + "license": "MIT", "peer": true, "bin": { "node-gyp-build": "bin.js", @@ -3521,16 +3136,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -3577,16 +3182,6 @@ "dev": true, "peer": true }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "peer": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -3598,29 +3193,37 @@ } }, "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "p-try": "^1.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "p-limit": "^1.1.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-map": { @@ -3639,34 +3242,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } + "license": "BlueOak-1.0.0", + "peer": true }, "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-parse": { @@ -3676,31 +3279,41 @@ "dev": true, "peer": true }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "peer": true, "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -3715,43 +3328,12 @@ "node": ">= 0.4" } }, - "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-solidity": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz", - "integrity": "sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==", - "dev": true, - "dependencies": { - "@solidity-parser/parser": "^0.18.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "prettier": ">=2.3.0" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "^5.1.0" @@ -3778,6 +3360,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -3789,16 +3372,18 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/regexp.prototype.flags": { @@ -3841,30 +3426,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "peer": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^5.2.0" - }, - "bin": { - "rlp": "bin/rlp" - } - }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -3902,6 +3463,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "peer": true }, "node_modules/safe-regex-test": { @@ -3928,46 +3490,12 @@ "dev": true, "peer": true }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true, - "peer": true - }, - "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "randombytes": "^2.1.0" @@ -4005,13 +3533,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "peer": true - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -4019,20 +3540,6 @@ "dev": true, "peer": true }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, "node_modules/sha1-file": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/sha1-file/-/sha1-file-2.0.1.tgz", @@ -4045,6 +3552,31 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -4063,6 +3595,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/solc": { "version": "0.8.26", "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", @@ -4175,6 +3721,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "~5.2.0" @@ -4194,6 +3741,23 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -4255,18 +3819,19 @@ "node": ">=8" } }, - "node_modules/strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "is-hex-prefixed": "1.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=6.5.0", - "npm": ">=3" + "node": ">=8" } }, "node_modules/strip-json-comments": { @@ -4274,6 +3839,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -4283,16 +3849,20 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/surya": { @@ -4331,30 +3901,35 @@ "node >=0.4.0" ] }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "os-tmpdir": "~1.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=0.6.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "peer": true, "dependencies": { - "is-number": "^7.0.0" + "os-tmpdir": "~1.0.2" }, "engines": { - "node": ">=8.0" + "node": ">=0.6.0" } }, "node_modules/toidentifier": { @@ -4390,20 +3965,6 @@ "dev": true, "peer": true }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true, - "peer": true - }, - "node_modules/tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true, - "peer": true - }, "node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -4515,10 +4076,11 @@ } }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -4527,13 +4089,6 @@ "node": ">=14.0" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "peer": true - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -4559,6 +4114,7 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/uuid": { @@ -4571,6 +4127,23 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", @@ -4626,10 +4199,11 @@ "dev": true }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true, + "license": "Apache-2.0", "peer": true }, "node_modules/wrap-ansi": { @@ -4649,18 +4223,32 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "peer": true + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8.3.0" @@ -4719,6 +4307,7 @@ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "camelcase": "^6.0.0", @@ -4735,6 +4324,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -4745,6 +4335,79 @@ } }, "dependencies": { + "@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==", + "dev": true, + "peer": true + }, + "@ethereumjs/util": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-9.1.0.tgz", + "integrity": "sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==", + "dev": true, + "peer": true, + "requires": { + "@ethereumjs/rlp": "^5.0.2", + "ethereum-cryptography": "^2.2.1" + }, + "dependencies": { + "@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dev": true, + "peer": true, + "requires": { + "@noble/hashes": "1.4.0" + } + }, + "@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "peer": true + }, + "@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dev": true, + "peer": true, + "requires": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + } + }, + "@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dev": true, + "peer": true, + "requires": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + } + }, + "ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dev": true, + "peer": true, + "requires": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + } + } + }, "@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -4830,13 +4493,13 @@ } }, "@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", "dev": true, "peer": true, "requires": { - "@ethersproject/logger": "^5.7.0" + "@ethersproject/logger": "^5.8.0" } }, "@ethersproject/constants": { @@ -4879,9 +4542,9 @@ } }, "@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", "dev": true, "peer": true }, @@ -4896,13 +4559,13 @@ } }, "@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", "dev": true, "peer": true, "requires": { - "@ethersproject/logger": "^5.7.0" + "@ethersproject/logger": "^5.8.0" } }, "@ethersproject/rlp": { @@ -4917,17 +4580,17 @@ } }, "@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", "dev": true, "peer": true, "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", "bn.js": "^5.2.1", - "elliptic": "6.5.4", + "elliptic": "6.6.1", "hash.js": "1.1.7" } }, @@ -4982,18 +4645,95 @@ "dev": true, "peer": true }, - "@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "peer": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "peer": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "peer": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "peer": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "peer": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, + "@noble/curves": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "dev": true, "peer": true, "requires": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" + "@noble/hashes": "1.7.2" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "dev": true, + "peer": true + } } }, "@noble/hashes": { @@ -5011,163 +4751,70 @@ "peer": true }, "@nomicfoundation/edr": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.5.2.tgz", - "integrity": "sha512-hW/iLvUQZNTVjFyX/I40rtKvvDOqUEyIi96T28YaLfmPL+3LW2lxmYLUXEJ6MI14HzqxDqrLyhf6IbjAa2r3Dw==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0-next.23.tgz", + "integrity": "sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==", "dev": true, "peer": true, "requires": { - "@nomicfoundation/edr-darwin-arm64": "0.5.2", - "@nomicfoundation/edr-darwin-x64": "0.5.2", - "@nomicfoundation/edr-linux-arm64-gnu": "0.5.2", - "@nomicfoundation/edr-linux-arm64-musl": "0.5.2", - "@nomicfoundation/edr-linux-x64-gnu": "0.5.2", - "@nomicfoundation/edr-linux-x64-musl": "0.5.2", - "@nomicfoundation/edr-win32-x64-msvc": "0.5.2" + "@nomicfoundation/edr-darwin-arm64": "0.12.0-next.23", + "@nomicfoundation/edr-darwin-x64": "0.12.0-next.23", + "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.23", + "@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.23", + "@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.23", + "@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.23", + "@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.23" } }, "@nomicfoundation/edr-darwin-arm64": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.5.2.tgz", - "integrity": "sha512-Gm4wOPKhbDjGTIRyFA2QUAPfCXA1AHxYOKt3yLSGJkQkdy9a5WW+qtqKeEKHc/+4wpJSLtsGQfpzyIzggFfo/A==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0-next.23.tgz", + "integrity": "sha512-Amh7mRoDzZyJJ4efqoePqdoZOzharmSOttZuJDlVE5yy07BoE8hL6ZRpa5fNYn0LCqn/KoWs8OHANWxhKDGhvQ==", "dev": true, "peer": true }, "@nomicfoundation/edr-darwin-x64": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.5.2.tgz", - "integrity": "sha512-ClyABq2dFCsrYEED3/UIO0c7p4H1/4vvlswFlqUyBpOkJccr75qIYvahOSJRM62WgUFRhbSS0OJXFRwc/PwmVg==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0-next.23.tgz", + "integrity": "sha512-9wn489FIQm7m0UCD+HhktjWx6vskZzeZD9oDc2k9ZvbBzdXwPp5tiDqUBJ+eQpByAzCDfteAJwRn2lQCE0U+Iw==", "dev": true, "peer": true }, "@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.5.2.tgz", - "integrity": "sha512-HWMTVk1iOabfvU2RvrKLDgtFjJZTC42CpHiw2h6rfpsgRqMahvIlx2jdjWYzFNy1jZKPTN1AStQ/91MRrg5KnA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0-next.23.tgz", + "integrity": "sha512-nlk5EejSzEUfEngv0Jkhqq3/wINIfF2ED9wAofc22w/V1DV99ASh9l3/e/MIHOQFecIZ9MDqt0Em9/oDyB1Uew==", "dev": true, "peer": true }, "@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.5.2.tgz", - "integrity": "sha512-CwsQ10xFx/QAD5y3/g5alm9+jFVuhc7uYMhrZAu9UVF+KtVjeCvafj0PaVsZ8qyijjqVuVsJ8hD1x5ob7SMcGg==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0-next.23.tgz", + "integrity": "sha512-SJuPBp3Rc6vM92UtVTUxZQ/QlLhLfwTftt2XUiYohmGKB3RjGzpgduEFMCA0LEnucUckU6UHrJNFHiDm77C4PQ==", "dev": true, "peer": true }, "@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.5.2.tgz", - "integrity": "sha512-CWVCEdhWJ3fmUpzWHCRnC0/VLBDbqtqTGTR6yyY1Ep3S3BOrHEAvt7h5gx85r2vLcztisu2vlDq51auie4IU1A==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0-next.23.tgz", + "integrity": "sha512-NU+Qs3u7Qt6t3bJFdmmjd5CsvgI2bPPzO31KifM2Ez96/jsXYho5debtTQnimlb5NAqiHTSlxjh/F8ROcptmeQ==", "dev": true, "peer": true }, "@nomicfoundation/edr-linux-x64-musl": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.5.2.tgz", - "integrity": "sha512-+aJDfwhkddy2pP5u1ISg3IZVAm0dO836tRlDTFWtvvSMQ5hRGqPcWwlsbobhDQsIxhPJyT7phL0orCg5W3WMeA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0-next.23.tgz", + "integrity": "sha512-F78fZA2h6/ssiCSZOovlgIu0dUeI7ItKPsDDF3UUlIibef052GCXmliMinC90jVPbrjUADMd1BUwjfI0Z8OllQ==", "dev": true, "peer": true }, "@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.5.2.tgz", - "integrity": "sha512-CcvvuA3sAv7liFNPsIR/68YlH6rrybKzYttLlMr80d4GKJjwJ5OKb3YgE6FdZZnOfP19HEHhsLcE0DPLtY3r0w==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0-next.23.tgz", + "integrity": "sha512-IfJZQJn7d/YyqhmguBIGoCKjE9dKjbu6V6iNEPApfwf5JyyjHYyyfkLU4rf7hygj57bfH4sl1jtQ6r8HnT62lw==", "dev": true, "peer": true }, - "@nomicfoundation/ethereumjs-common": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", - "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", - "dev": true, - "peer": true, - "requires": { - "@nomicfoundation/ethereumjs-util": "9.0.4" - } - }, - "@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", - "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", - "dev": true, - "peer": true - }, - "@nomicfoundation/ethereumjs-tx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", - "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", - "dev": true, - "peer": true, - "requires": { - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-util": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", - "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", - "dev": true, - "peer": true, - "requires": { - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, "@nomicfoundation/solidity-analyzer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", @@ -5240,6 +4887,14 @@ "optional": true, "peer": true }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "peer": true + }, "@scure/base": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", @@ -5358,59 +5013,6 @@ "tslib": "^1.9.3" } }, - "@solidity-parser/parser": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", - "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", - "dev": true - }, - "@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true, - "peer": true - }, - "@types/node": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", - "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", - "dev": true, - "peer": true, - "requires": { - "undici-types": "~6.19.2" - } - }, - "@types/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "@types/secp256k1": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", - "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, "adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", @@ -5496,17 +5098,6 @@ "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", "dev": true }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "peer": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5570,34 +5161,10 @@ "dev": true, "peer": true }, - "base-x": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", - "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "peer": true - }, - "blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true, - "peer": true - }, "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", "dev": true, "peer": true }, @@ -5606,46 +5173,18 @@ "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", "dev": true, - "peer": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, + "peer": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -5656,24 +5195,13 @@ } }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "peer": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "peer": true, "requires": { - "fill-range": "^7.1.1" + "balanced-match": "^1.0.0" } }, "brorand": { @@ -5690,43 +5218,6 @@ "dev": true, "peer": true }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "peer": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "peer": true, - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "peer": true, - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5734,13 +5225,6 @@ "dev": true, "peer": true }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true, - "peer": true - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5775,61 +5259,36 @@ "peer": true }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "peer": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "peer": true, "requires": { - "color-name": "1.1.3" + "has-flag": "^4.0.0" } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true } } }, "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "peer": true, "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" } }, "ci-info": { @@ -5839,17 +5298,6 @@ "dev": true, "peer": true }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -5910,13 +5358,6 @@ "dev": true, "peer": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "peer": true - }, "cookie": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", @@ -5924,33 +5365,16 @@ "dev": true, "peer": true }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "peer": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "peer": true, "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "data-view-buffer": { @@ -6033,16 +5457,23 @@ "peer": true }, "diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "peer": true + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "peer": true }, "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, "peer": true, "requires": { @@ -6056,9 +5487,9 @@ }, "dependencies": { "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", "dev": true, "peer": true } @@ -6204,9 +5635,9 @@ "dev": true }, "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "peer": true }, @@ -6223,125 +5654,23 @@ "@scure/bip39": "1.1.1" } }, - "ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "peer": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - }, - "dependencies": { - "@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, - "peer": true, - "requires": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "peer": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "peer": true, - "requires": { - "to-regex-range": "^5.0.1" - } + "requires": {} }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "peer": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, "flat": { @@ -6352,9 +5681,9 @@ "peer": true }, "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "peer": true }, @@ -6367,6 +5696,17 @@ "is-callable": "^1.1.3" } }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "peer": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + } + }, "fp-ts": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", @@ -6386,21 +5726,6 @@ "universalify": "^0.1.0" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "peer": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true, - "peer": true - }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6456,28 +5781,18 @@ } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "peer": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "peer": true, - "requires": { - "is-glob": "^4.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, "globalthis": { @@ -6516,9 +5831,9 @@ } }, "handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "requires": { "minimist": "^1.2.5", @@ -6529,51 +5844,47 @@ } }, "hardhat": { - "version": "2.22.9", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.9.tgz", - "integrity": "sha512-sWiuI/yRdFUPfndIvL+2H18Vs2Gav0XacCFYY5msT5dHOWkhLxESJySIk9j83mXL31aXL8+UMA9OgViFLexklg==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.29.0.tgz", + "integrity": "sha512-tsj5mCSjDCFOhGfBl4vwqDEcwdlES9VUzRWfdrwvEVhus6D8W6u+WfUKRLLwFhKGS/8lKPoXGsjYWPXl3CCpOg==", "dev": true, "peer": true, "requires": { + "@ethereumjs/util": "^9.1.0", "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.5.2", - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-tx": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", + "@nomicfoundation/edr": "0.12.0-next.23", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", "adm-zip": "^0.4.16", "aggregate-error": "^3.0.0", "ansi-escapes": "^4.3.0", "boxen": "^5.1.2", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", + "chokidar": "^4.0.0", "ci-info": "^2.0.0", "debug": "^4.1.1", "enquirer": "^2.3.0", "env-paths": "^2.2.0", "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", + "find-up": "^5.0.0", "fp-ts": "1.19.3", "fs-extra": "^7.0.1", - "glob": "7.2.0", "immutable": "^4.0.0-rc.12", "io-ts": "1.10.4", + "json-stream-stringify": "^3.1.4", "keccak": "^3.0.2", "lodash": "^4.17.11", + "micro-eth-signer": "^0.14.0", "mnemonist": "^0.38.0", - "mocha": "^10.0.0", + "mocha": "^11.1.0", "p-map": "^4.0.0", + "picocolors": "^1.1.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", "solc": "0.8.26", "source-map-support": "^0.5.13", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.6", "tsort": "0.0.1", "undici": "^5.14.0", "uuid": "^8.3.2", @@ -6596,9 +5907,9 @@ "dev": true }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "peer": true }, @@ -6632,18 +5943,6 @@ "has-symbols": "^1.0.3" } }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -6729,9 +6028,9 @@ } }, "immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz", + "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==", "dev": true, "peer": true }, @@ -6742,17 +6041,6 @@ "dev": true, "peer": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "peer": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -6800,16 +6088,6 @@ "has-bigints": "^1.0.1" } }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "peer": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -6844,49 +6122,18 @@ "has-tostringtag": "^1.0.0" } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "peer": true - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "peer": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", - "dev": true, - "peer": true - }, "is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "peer": true - }, "is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", @@ -6896,6 +6143,13 @@ "has-tostringtag": "^1.0.0" } }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6977,6 +6231,24 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "peer": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -6985,15 +6257,22 @@ "peer": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "peer": true, "requires": { "argparse": "^2.0.1" } }, + "json-stream-stringify": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.7.tgz", + "integrity": "sha512-F4MWetLtY42YMaAKw5cV4e47zMD5aOT+tjjQWjX18ACtdkQ5Y/vrcfbcQ107Rh+MXjOCIx4KhW0wPmOvG8iQ5w==", + "dev": true, + "peer": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -7017,62 +6296,31 @@ } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "peer": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "peer": true, "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "peer": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "peer": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "lru_map": { @@ -7082,17 +6330,12 @@ "dev": true, "peer": true }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "peer": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } + "peer": true }, "memorystream": { "version": "0.3.1", @@ -7101,6 +6344,46 @@ "dev": true, "peer": true }, + "micro-eth-signer": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz", + "integrity": "sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==", + "dev": true, + "peer": true, + "requires": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "micro-packed": "~0.7.2" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "dev": true, + "peer": true + } + } + }, + "micro-packed": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", + "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", + "dev": true, + "peer": true, + "requires": { + "@scure/base": "~1.2.5" + }, + "dependencies": { + "@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "peer": true + } + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -7116,13 +6399,13 @@ "peer": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "peer": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" } }, "minimist": { @@ -7131,6 +6414,13 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, + "minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "peer": true + }, "mnemonist": { "version": "0.38.5", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", @@ -7142,181 +6432,41 @@ } }, "mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", + "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", "dev": true, "peer": true, "requires": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^8.1.0", + "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "peer": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "peer": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "peer": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "peer": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "peer": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "peer": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "peer": true } } }, @@ -7341,16 +6491,9 @@ "peer": true }, "node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", - "dev": true, - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, "peer": true }, @@ -7385,16 +6528,6 @@ "dev": true, "peer": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "peer": true, - "requires": { - "wrappy": "1" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -7403,23 +6536,23 @@ "peer": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "peer": true, "requires": { - "p-try": "^1.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "peer": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^3.0.2" } }, "p-map": { @@ -7432,24 +6565,24 @@ "aggregate-error": "^3.0.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "peer": true }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "peer": true }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "peer": true }, @@ -7460,24 +6593,28 @@ "dev": true, "peer": true }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "peer": true, "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "peer": true + }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "peer": true }, @@ -7487,23 +6624,6 @@ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true }, - "prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, - "peer": true - }, - "prettier-plugin-solidity": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz", - "integrity": "sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==", - "dev": true, - "requires": { - "@solidity-parser/parser": "^0.18.0", - "semver": "^7.5.4" - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7540,14 +6660,11 @@ } }, "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, - "peer": true, - "requires": { - "picomatch": "^2.2.1" - } + "peer": true }, "regexp.prototype.flags": { "version": "1.5.2", @@ -7577,27 +6694,6 @@ "path-parse": "^1.0.6" } }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "peer": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^5.2.0" - } - }, "safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -7635,31 +6731,6 @@ "dev": true, "peer": true }, - "scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true, - "peer": true - }, - "secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "dev": true, - "peer": true, - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true - }, "serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -7696,13 +6767,6 @@ "has-property-descriptors": "^1.0.2" } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "peer": true - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -7710,17 +6774,6 @@ "dev": true, "peer": true }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "sha1-file": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/sha1-file/-/sha1-file-2.0.1.tgz", @@ -7730,6 +6783,23 @@ "hasha": "^5.2.0" } }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true + }, "side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -7742,6 +6812,13 @@ "object-inspect": "^1.13.1" } }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "peer": true + }, "solc": { "version": "0.8.26", "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", @@ -7850,6 +6927,18 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -7893,14 +6982,14 @@ "ansi-regex": "^5.0.1" } }, - "strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "peer": true, "requires": { - "is-hex-prefixed": "1.0.0" + "ansi-regex": "^5.0.1" } }, "strip-json-comments": { @@ -7911,13 +7000,13 @@ "peer": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "peer": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "surya": { @@ -7952,24 +7041,25 @@ "integrity": "sha512-IsFisGgDKk7qzK9erMIkQe/XwiSUdac7z3wYOsjcLkhPBy3k1SlvLoIh2dAHIlEpgA971CgguMrx9z8fFg7tSA==", "dev": true }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "peer": true, "requires": { - "os-tmpdir": "~1.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "peer": true, "requires": { - "is-number": "^7.0.0" + "os-tmpdir": "~1.0.2" } }, "toidentifier": { @@ -7999,20 +7089,6 @@ "dev": true, "peer": true }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true, - "peer": true - }, - "tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true, - "peer": true - }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -8091,22 +7167,15 @@ } }, "undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", "dev": true, "peer": true, "requires": { "@fastify/busboy": "^2.0.0" } }, - "undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "peer": true - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -8135,6 +7204,16 @@ "dev": true, "peer": true }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "requires": { + "isexe": "^2.0.0" + } + }, "which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", @@ -8178,9 +7257,9 @@ "dev": true }, "workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true, "peer": true }, @@ -8195,17 +7274,22 @@ "strip-ansi": "^6.0.0" } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "peer": true + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } }, "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, "peer": true, "requires": {} diff --git a/package.json b/package.json index a56a8e3..4ca5410 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "devDependencies": { - "prettier-plugin-solidity": "^1.4.1", "solidity-docgen": "^0.6.0-beta.36", "surya": "^0.4.11" } diff --git a/remappings.txt b/remappings.txt index b0803f5..31bf2f2 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,4 @@ CMTAT/=lib/CMTAT/contracts/ +RuleEngine/=lib/RuleEngine/src/ OZ/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file diff --git a/script/DeployDocumentEngine.s.sol b/script/DeployDocumentEngine.s.sol new file mode 100644 index 0000000..343ca9a --- /dev/null +++ b/script/DeployDocumentEngine.s.sol @@ -0,0 +1,52 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import {Script, console2} from "forge-std/Script.sol"; +import {DocumentEngine} from "../src/DocumentEngine.sol"; + +/** + * @title DeployDocumentEngine + * @notice Deploys the role-based {DocumentEngine} (AccessControlEnumerable). + * @dev Configuration via environment variables: + * - `DOCUMENT_ENGINE_ADMIN` : address granted `DEFAULT_ADMIN_ROLE` (default: `msg.sender`) + * - `DOCUMENT_ENGINE_FORWARDER` : ERC-2771 trusted forwarder, `address(0)` disables gasless (default: `address(0)`) + * + * Usage: + * forge script script/DeployDocumentEngine.s.sol \ + * --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast + * + * Warning: the environment variables above and passing a raw key with + * `--private-key` are for local testing only, not for production. For production + * use a secure signing method (encrypted keystore, hardware wallet, ...) as + * described in the Foundry Key Management documentation (getfoundry.sh). + */ +contract DeployDocumentEngine is Script { + /** + * @notice Reads the deployment configuration from the environment and deploys the engine. + * @return documentEngine The freshly deployed {DocumentEngine}. + */ + function run() external returns (DocumentEngine documentEngine) { + address admin = vm.envOr("DOCUMENT_ENGINE_ADMIN", msg.sender); + address forwarder = vm.envOr("DOCUMENT_ENGINE_FORWARDER", address(0)); + + documentEngine = deploy(admin, forwarder); + + console2.log("DocumentEngine deployed at:", address(documentEngine)); + console2.log(" admin :", admin); + console2.log(" trusted forwarder:", forwarder); + console2.log(" version :", documentEngine.version()); + } + + /** + * @notice Deploys the engine with an explicit configuration. + * @dev Broadcasted deployment, isolated from env parsing so it can be reused/tested. + * @param admin address granted `DEFAULT_ADMIN_ROLE` + * @param forwarder ERC-2771 trusted forwarder; `address(0)` disables gasless support + * @return documentEngine The freshly deployed {DocumentEngine}. + */ + function deploy(address admin, address forwarder) public returns (DocumentEngine documentEngine) { + vm.startBroadcast(); + documentEngine = new DocumentEngine(admin, forwarder); + vm.stopBroadcast(); + } +} diff --git a/script/DeployDocumentEngineOwnable.s.sol b/script/DeployDocumentEngineOwnable.s.sol new file mode 100644 index 0000000..a1ef714 --- /dev/null +++ b/script/DeployDocumentEngineOwnable.s.sol @@ -0,0 +1,52 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import {Script, console2} from "forge-std/Script.sol"; +import {DocumentEngineOwnable} from "../src/DocumentEngineOwnable.sol"; + +/** + * @title DeployDocumentEngineOwnable + * @notice Deploys the owner-based {DocumentEngineOwnable} (Ownable2Step). + * @dev Configuration via environment variables: + * - `DOCUMENT_ENGINE_OWNER` : initial owner (default: `msg.sender`) + * - `DOCUMENT_ENGINE_FORWARDER` : ERC-2771 trusted forwarder, `address(0)` disables gasless (default: `address(0)`) + * + * Usage: + * forge script script/DeployDocumentEngineOwnable.s.sol \ + * --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast + * + * Warning: the environment variables above and passing a raw key with + * `--private-key` are for local testing only, not for production. For production + * use a secure signing method (encrypted keystore, hardware wallet, ...) as + * described in the Foundry Key Management documentation (getfoundry.sh). + */ +contract DeployDocumentEngineOwnable is Script { + /** + * @notice Reads the deployment configuration from the environment and deploys the engine. + * @return documentEngine The freshly deployed {DocumentEngineOwnable}. + */ + function run() external returns (DocumentEngineOwnable documentEngine) { + address owner = vm.envOr("DOCUMENT_ENGINE_OWNER", msg.sender); + address forwarder = vm.envOr("DOCUMENT_ENGINE_FORWARDER", address(0)); + + documentEngine = deploy(owner, forwarder); + + console2.log("DocumentEngineOwnable deployed at:", address(documentEngine)); + console2.log(" owner :", owner); + console2.log(" trusted forwarder:", forwarder); + console2.log(" version :", documentEngine.version()); + } + + /** + * @notice Deploys the engine with an explicit configuration. + * @dev Broadcasted deployment, isolated from env parsing so it can be reused/tested. + * @param owner initial owner of the contract + * @param forwarder ERC-2771 trusted forwarder; `address(0)` disables gasless support + * @return documentEngine The freshly deployed {DocumentEngineOwnable}. + */ + function deploy(address owner, address forwarder) public returns (DocumentEngineOwnable documentEngine) { + vm.startBroadcast(); + documentEngine = new DocumentEngineOwnable(owner, forwarder); + vm.stopBroadcast(); + } +} diff --git a/src/DocumentEngine.sol b/src/DocumentEngine.sol index 3f4fff9..6c51be4 100644 --- a/src/DocumentEngine.sol +++ b/src/DocumentEngine.sol @@ -1,35 +1,42 @@ //SPDX-License-Identifier: MPL-2.0 -pragma solidity ^0.8.20; - -import "OZ/access/AccessControl.sol"; -import "OZ/metatx/ERC2771Context.sol"; -import "CMTAT/interfaces/engine/draft-IERC1643.sol"; -import "./DocumentEngineInvariant.sol"; +pragma solidity ^0.8.24; + +import {AccessControl} from "OZ/access/AccessControl.sol"; +import {AccessControlEnumerable} from "OZ/access/extensions/AccessControlEnumerable.sol"; +import {IAccessControl} from "OZ/access/IAccessControl.sol"; +import {Context} from "OZ/utils/Context.sol"; +import {ERC2771Context} from "OZ/metatx/ERC2771Context.sol"; +import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol"; +import {IERC1643MultiDocument} from "./interfaces/IERC1643MultiDocument.sol"; +import {ITokenBinding} from "./interfaces/ITokenBinding.sol"; +import {TokenBindingModule} from "./modules/TokenBindingModule.sol"; +import {VersionModule} from "./modules/VersionModule.sol"; /** * @title DocumentEngine - * @notice contract to manage documents on-chain through ERC-1643 + * @notice Deployment contract to manage documents on-chain through ERC-1643. + * @dev Wires the document-management logic ({DocumentEngineBase}) with a + * concrete access-control implementation. The authorization hooks are defined + * here (role-based `AccessControlEnumerable`, which additionally allows + * enumerating role members), keeping the access control separate from the + * document-management logic (CMTAT / CMTA-RuleEngine pattern). The contract + * version is exposed through the {VersionModule} (ERC-8303), and it also wires + * the ERC-2771 (gasless) meta-transaction support. */ -contract DocumentEngine is - IERC1643, - DocumentEngineInvariant, - AccessControl, - ERC2771Context -{ +contract DocumentEngine is TokenBindingModule, VersionModule, AccessControlEnumerable, ERC2771Context { /** - * @notice - * Get the current version of the smart contract + * @notice Role allowed to manage documents on behalf of any smart contract, and to + * bind/unbind tokens (admin path). + * @dev Token binding uses the shared allowlist in {TokenBindingModule}, not a dedicated role. */ - string public constant VERSION = "0.3.0"; - // Mapping from contract addresses to document names to their corresponding Document structs - mapping(address => mapping(bytes32 => Document)) private _documents; - mapping(address => bytes32[]) private _documentNames; + bytes32 public constant DOCUMENT_MANAGER_ROLE = keccak256("DOCUMENT_MANAGER_ROLE"); - // Constructor to initialize the admin role - constructor( - address admin, - address forwarderIrrevocable - ) ERC2771Context(forwarderIrrevocable) { + /** + * @notice Deploys the engine and grants `admin` the default admin role. + * @param admin address granted `DEFAULT_ADMIN_ROLE`; must not be the null address + * @param forwarderIrrevocable address of the ERC-2771 forwarder (gasless support) + */ + constructor(address admin, address forwarderIrrevocable) ERC2771Context(forwarderIrrevocable) { if (admin == address(0)) { revert AdminWithAddressZeroNotAllowed(); } @@ -37,221 +44,94 @@ contract DocumentEngine is } /*////////////////////////////////////////////////////////////// - PUBLIC/EXTERNAL FUNCTIONS + ACCESS CONTROL (public surface) //////////////////////////////////////////////////////////////*/ /** - * @notice Restricted function to set or update a document - */ - function setDocument( - address smartContract, - bytes32 name_, - string memory uri_, - bytes32 documentHash_ - ) public onlyRole(DOCUMENT_MANAGER_ROLE) { - _setDocument(smartContract, name_, uri_, documentHash_); - } - - /** - * @notice Restricted function to remove a document for a given smart contract and name - */ - function removeDocument( - address smartContract, - bytes32 name_ - ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - _removeDocument(smartContract, name_); - } - - /** - * @notice Batch version of setDocument to handle multiple documents at once - */ - function batchSetDocuments( - address[] calldata smartContracts, - bytes32[] calldata names, - string[] calldata uris, - bytes32[] calldata hashes - ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - if ( - smartContracts.length == 0 || - smartContracts.length != names.length || - names.length != uris.length || - uris.length != hashes.length - ) { - revert InvalidInputLength(); - } - for (uint256 i = 0; i < smartContracts.length; i++) { - _setDocument(smartContracts[i], names[i], uris[i], hashes[i]); - } - } - - /** - * @notice Batch version of setDocument to handle multiple documents at once - */ - function batchSetDocuments( - address smartContract, - bytes32[] calldata names, - string[] calldata uris, - bytes32[] calldata hashes - ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - if ( - names.length == 0 || - names.length != uris.length || - uris.length != hashes.length - ) { - revert InvalidInputLength(); - } - for (uint256 i = 0; i < names.length; ++i) { - _setDocument(smartContract, names[i], uris[i], hashes[i]); - } - } - - /** - * @notice Batch version of removeDocument to handle multiple documents at once - */ - function batchRemoveDocuments( - address[] calldata smartContracts, - bytes32[] calldata names - ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - if ( - smartContracts.length == 0 || - (smartContracts.length != names.length) - ) { - revert InvalidInputLength(); - } - - for (uint256 i = 0; i < smartContracts.length; ++i) { - _removeDocument(smartContracts[i], names[i]); - } - } - - /** - * @notice Batch version of removeDocument to handle multiple documents at once - */ - function batchRemoveDocuments( - address smartContract, - bytes32[] calldata names - ) external onlyRole(DOCUMENT_MANAGER_ROLE) { - if (names.length == 0) { - revert InvalidInputLength(); - } - - for (uint256 i = 0; i < names.length; ++i) { - _removeDocument(smartContract, names[i]); - } - } - - /** - * @notice Public function to get a document from msg.sender - */ - function getDocument( - bytes32 name_ - ) external view override returns (string memory, bytes32, uint256) { - return _getDocument(msg.sender, name_); - } - - /** - * @notice Public function to get a document for a specific contract address - */ - function getDocument( - address smartContract, - bytes32 name_ - ) external view returns (string memory, bytes32, uint256) { - return _getDocument(smartContract, name_); - } - - /** - * @notice Get all document names for msg.sender - */ - function getAllDocuments() - external + * @notice Returns whether `account` holds `role`. + * @dev Returns `true` if `account` has been granted `role`. The default admin + * (`DEFAULT_ADMIN_ROLE`) is treated as holding **every** role. + * + * Note: this virtual "admin has all roles" behavior is NOT reflected by + * {AccessControlEnumerable} enumeration. `getRoleMember` / `getRoleMemberCount` + * report only explicit grants, so a `DEFAULT_ADMIN_ROLE` holder satisfies + * `hasRole(anyRole, admin)` yet does not appear in `getRoleMember(anyRole, ...)`. + * + * WARNING: the same short-circuit makes a role **unrevokable from the default admin**. + * `revokeRole(someRole, admin)` succeeds and emits `RoleRevoked` — the explicit grant is + * genuinely removed, and `getRoleMemberCount` drops — but this function still answers + * `true`, so the admin keeps the access the caller believed it had just removed. Only + * revoking `DEFAULT_ADMIN_ROLE` itself actually withdraws it. This is inherent to the + * "admin has all roles" model rather than a defect (an admin can always re-grant itself + * any role), but the success of the call is misleading. Pinned by + * `testRevokingRoleFromDefaultAdminDoesNotRemoveAccess`. + * @param role The role identifier to check. + * @param account The account to check. + * @return True when `account` holds `role`, or holds `DEFAULT_ADMIN_ROLE`. + */ + function hasRole(bytes32 role, address account) + public view - override - returns (bytes32[] memory) + virtual + override(AccessControl, IAccessControl) + returns (bool) { - return _documentNames[msg.sender]; - } - - /** - * @notice Get all document names for a specific smart contract - */ - function getAllDocuments( - address smartContract - ) external view returns (bytes32[] memory) { - return _documentNames[smartContract]; - } - - /* ============ ACCESS CONTROL ============ */ - /* - * @dev Returns `true` if `account` has been granted `role`. - */ - function hasRole( - bytes32 role, - address account - ) public view virtual override returns (bool) { // The Default Admin has all roles - if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) { + if (super.hasRole(DEFAULT_ADMIN_ROLE, account)) { return true; } - return AccessControl.hasRole(role, account); + return super.hasRole(role, account); } /*////////////////////////////////////////////////////////////// - INTERNAL FUNCTIONS + ERC165 //////////////////////////////////////////////////////////////*/ /** - * @dev Internal function to fetch a document - */ - function _getDocument( - address smartContract, - bytes32 name_ - ) internal view returns (string memory, bytes32, uint256) { - Document memory doc = _documents[smartContract][name_]; - return (doc.uri, doc.documentHash, doc.lastModified); + * @notice Returns whether this contract implements `interfaceId`. + * @dev ERC-165 discovery: advertises ERC-1643 and its multi-subject extension, the token-binding + * surface, the version module (ERC-8303) and `AccessControlEnumerable`. + * + * `type(IERC1643).interfaceId` is advertised because the engine does implement the base + * single-argument functions, which is exactly what the draft conditions the id on. Its audience + * is a **token wiring itself to this engine**: before calling `setDocumentEngine(engine)`, or + * before forwarding `setDocument(name, uri, hash)` to it, a token can confirm through ERC-165 + * that the single-argument ERC-1643 endpoints exist here, rather than finding out from a failed + * call. `type(ITokenBinding).interfaceId` answers the complementary question — whether this + * engine has a binding surface at all — and `isTokenBound(address(this))` whether that + * particular token may use it. + * + * It is **not** an invitation to read documents from this address. The base functions are + * `_msgSender()`-scoped, so a consumer calling `getDocument(name)` here reads its own, empty + * namespace, and this engine emits only the address-carrying `*ForSubject` events. Point + * document consumers at the **subject**, or use the address-scoped `getDocument(subject, name)`. + * + * See {IERC165-supportsInterface}. + * @param interfaceId The ERC-165 interface identifier to query. + * @return True when `interfaceId` is one of the advertised interfaces or is supported by a base + * contract. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(VersionModule, AccessControlEnumerable) + returns (bool) + { + return interfaceId == type(IERC1643).interfaceId || interfaceId == type(IERC1643MultiDocument).interfaceId + || interfaceId == type(ITokenBinding).interfaceId || super.supportsInterface(interfaceId); } + /*////////////////////////////////////////////////////////////// + ACCESS CONTROL (implementation) + //////////////////////////////////////////////////////////////*/ + /** - * @dev Internal helper to remove the document name from the list of document names + * @dev Authorization for the admin document-management path. + * The caller must hold `DOCUMENT_MANAGER_ROLE`. Override to customize. */ - function _removeDocumentName( - address smartContract, - bytes32 name_ - ) internal { - uint256 length = _documentNames[smartContract].length; - for (uint256 i = 0; i < length; ++i) { - if (_documentNames[smartContract][i] == name_) { - _documentNames[smartContract][i] = _documentNames[ - smartContract - ][length - 1]; - _documentNames[smartContract].pop(); - break; - } - } - } - - function _removeDocument(address smartContract, bytes32 name_) internal { - Document memory doc = _documents[smartContract][name_]; - emit DocumentRemoved(smartContract, name_, doc.uri, doc.documentHash); - - delete _documents[smartContract][name_]; - _removeDocumentName(smartContract, name_); - } - - function _setDocument( - address smartContract, - bytes32 name_, - string memory uri_, - bytes32 documentHash_ - ) internal { - Document storage doc = _documents[smartContract][name_]; - if (doc.lastModified == 0) { - // new document - _documentNames[smartContract].push(name_); - } - doc.uri = uri_; - doc.documentHash = documentHash_; - doc.lastModified = block.timestamp; - emit DocumentUpdated(smartContract, name_, uri_, documentHash_); + function _authorizeDocumentManagement() internal view virtual override { + _checkRole(DOCUMENT_MANAGER_ROLE); } /*////////////////////////////////////////////////////////////// @@ -260,37 +140,27 @@ contract DocumentEngine is /** * @dev This surcharge is not necessary if you do not use ERC2771 + * @return sender The transaction sender, unwrapped from the ERC-2771 calldata suffix when the + * call came through the trusted forwarder. */ - function _msgSender() - internal - view - override(ERC2771Context, Context) - returns (address sender) - { + function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) { return ERC2771Context._msgSender(); } /** * @dev This surcharge is not necessary if you do not use ERC2771 + * @return The calldata, stripped of the ERC-2771 sender suffix when the call came through the + * trusted forwarder. */ - function _msgData() - internal - view - override(ERC2771Context, Context) - returns (bytes calldata) - { + function _msgData() internal view virtual override(ERC2771Context, Context) returns (bytes calldata) { return ERC2771Context._msgData(); } /** * @dev This surcharge is not necessary if you do not use the MetaTxModule + * @return The length of the ERC-2771 calldata suffix holding the sender address. */ - function _contextSuffixLength() - internal - view - override(ERC2771Context, Context) - returns (uint256) - { + function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) { return ERC2771Context._contextSuffixLength(); } } diff --git a/src/DocumentEngineBase.sol b/src/DocumentEngineBase.sol new file mode 100644 index 0000000..fd783f6 --- /dev/null +++ b/src/DocumentEngineBase.sol @@ -0,0 +1,363 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import {Context} from "OZ/utils/Context.sol"; +import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol"; +import {IERC1643MultiDocument} from "./interfaces/IERC1643MultiDocument.sol"; +import {DocumentEngineInvariant} from "./DocumentEngineInvariant.sol"; + +/** + * @title DocumentEngineBase + * @notice Document management logic (ERC-1643) for several smart contracts. + * @dev This abstract base holds the document storage and all the + * document-management functions, but it is **agnostic to the access-control + * implementation**. Authorization is delegated to the abstract hooks + * {_authorizeDocumentManagement} and {_authorizeBoundTokenDocumentManagement} + * (through the `onlyDocumentManager` / `onlyBoundToken` modifiers), which a + * deployment contract must implement (see {DocumentEngine}). + * + * This separation (base logic + deployment-defined access control) follows the + * CMTAT and CMTA/RuleEngine pattern. + */ +abstract contract DocumentEngineBase is IERC1643, IERC1643MultiDocument, DocumentEngineInvariant, Context { + /** + * @notice Documents held for each subject, keyed by subject address then document name. + */ + mapping(address => mapping(bytes32 => Document)) private _documents; + + /** + * @notice The names of every document currently tracked for each subject. + */ + mapping(address => bytes32[]) private _documentNames; + + /*////////////////////////////////////////////////////////////// + ACCESS CONTROL (modifiers) + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Restricts a function to accounts allowed to manage documents on + * behalf of any smart contract (admin path). Delegates the authorization + * to {_authorizeDocumentManagement} so that the document-management + * implementation stays separate from the access-control logic. + */ + modifier onlyDocumentManager() { + _authorizeDocumentManagement(); + _; + } + + /** + * @dev Restricts a function to tokens bound to this engine, letting them + * manage their own documents (bound-token path). Delegates to + * {_authorizeBoundTokenDocumentManagement}. + */ + modifier onlyBoundToken() { + _authorizeBoundTokenDocumentManagement(); + _; + } + + /*////////////////////////////////////////////////////////////// + EXTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Restricted function to remove a document for a given smart contract and name + * @param subject The contract the document belongs to. + * @param name_ The document name. + */ + function removeDocument(address subject, bytes32 name_) external override onlyDocumentManager { + _removeDocument(subject, name_); + } + + /* ============ ERC-1643 (bound token) ============ */ + + /** + * @notice ERC-1643 function to set or update a document for the caller. + * @dev The document is stored under the caller (`_msgSender()`) namespace. + * Restricted by the `onlyBoundToken` hook: the caller must be a token bound to + * this engine. How a token is bound is deployment-specific (see the + * {_authorizeBoundTokenDocumentManagement} implementations). A bound token can + * only manage its own documents; it can never affect another contract's documents. + * @param name_ The document name. + * @param uri_ The document location. + * @param documentHash_ The hash of the document contents. + */ + function setDocument(bytes32 name_, string calldata uri_, bytes32 documentHash_) external override onlyBoundToken { + _setDocument(_msgSender(), name_, uri_, documentHash_); + } + + /** + * @notice ERC-1643 function to remove a document for the caller. + * @dev See {setDocument}. Scoped to the caller (`_msgSender()`) namespace. + * @param name_ The document name. + */ + function removeDocument(bytes32 name_) external override onlyBoundToken { + _removeDocument(_msgSender(), name_); + } + + /** + * @notice Batch version of setDocument to handle multiple documents at once + * @dev All-or-nothing: a single invalid entry reverts the whole batch. + * @param subjects The contract each document belongs to, one per entry. + * @param names The document names, one per entry. + * @param uris The document locations, one per entry. + * @param hashes The document content hashes, one per entry. + */ + function batchSetDocuments( + address[] calldata subjects, + bytes32[] calldata names, + string[] calldata uris, + bytes32[] calldata hashes + ) external onlyDocumentManager { + if ( + subjects.length == 0 || subjects.length != names.length || names.length != uris.length + || uris.length != hashes.length + ) { + revert InvalidInputLength(); + } + uint256 length = subjects.length; + for (uint256 i = 0; i < length; ++i) { + _setDocument(subjects[i], names[i], uris[i], hashes[i]); + } + } + + /** + * @notice Batch version of setDocument to handle multiple documents at once + * @dev All-or-nothing: a single invalid entry reverts the whole batch. + * @param subject The contract every document in the batch belongs to. + * @param names The document names, one per entry. + * @param uris The document locations, one per entry. + * @param hashes The document content hashes, one per entry. + */ + function batchSetDocuments( + address subject, + bytes32[] calldata names, + string[] calldata uris, + bytes32[] calldata hashes + ) external onlyDocumentManager { + if (names.length == 0 || names.length != uris.length || uris.length != hashes.length) { + revert InvalidInputLength(); + } + uint256 length = names.length; + for (uint256 i = 0; i < length; ++i) { + _setDocument(subject, names[i], uris[i], hashes[i]); + } + } + + /** + * @notice Batch version of removeDocument to handle multiple documents at once + * @dev All-or-nothing: a single missing document reverts the whole batch. + * @param subjects The contract each document belongs to, one per entry. + * @param names The document names, one per entry. + */ + function batchRemoveDocuments(address[] calldata subjects, bytes32[] calldata names) external onlyDocumentManager { + if (subjects.length == 0 || (subjects.length != names.length)) { + revert InvalidInputLength(); + } + + uint256 length = subjects.length; + for (uint256 i = 0; i < length; ++i) { + _removeDocument(subjects[i], names[i]); + } + } + + /** + * @notice Batch version of removeDocument to handle multiple documents at once + * @dev All-or-nothing: a single missing document reverts the whole batch. + * @param subject The contract every document in the batch belongs to. + * @param names The document names, one per entry. + */ + function batchRemoveDocuments(address subject, bytes32[] calldata names) external onlyDocumentManager { + if (names.length == 0) { + revert InvalidInputLength(); + } + + uint256 length = names.length; + for (uint256 i = 0; i < length; ++i) { + _removeDocument(subject, names[i]); + } + } + + /** + * @notice ERC-1643 function to get a document for the caller (`_msgSender()`) + * @dev Returns the three fields as flat values, matching the ERC-1643 ABI. The `Document` + * struct is kept for storage only: returning it would prepend a struct offset word to the + * returndata, so a consumer decoding per the ERC-1643 signature would silently mis-decode. + * @param name_ The document name. + * @return uri Document location. + * @return documentHash Hash of the document contents. + * @return lastModified Last update timestamp. + */ + function getDocument(bytes32 name_) + external + view + override + returns (string memory uri, bytes32 documentHash, uint256 lastModified) + { + return _getDocument(_msgSender(), name_); + } + + /** + * @notice Public function to get a document for a specific contract address + * @dev Flat return, see {getDocument(bytes32)}. + * @param subject The contract the document belongs to. + * @param name_ The document name. + * @return uri Document location. + * @return documentHash Hash of the document contents. + * @return lastModified Last update timestamp. + */ + function getDocument(address subject, bytes32 name_) + external + view + override + returns (string memory uri, bytes32 documentHash, uint256 lastModified) + { + return _getDocument(subject, name_); + } + + /** + * @notice Get all document names for msg.sender + * @return The names of every document currently tracked for the caller. + */ + function getAllDocuments() external view override returns (bytes32[] memory) { + return _documentNames[_msgSender()]; + } + + /** + * @notice Get all document names for a specific smart contract + * @param subject The contract to enumerate documents for. + * @return The names of every document currently tracked for `subject`. + */ + function getAllDocuments(address subject) external view override returns (bytes32[] memory) { + return _documentNames[subject]; + } + + /*////////////////////////////////////////////////////////////// + PUBLIC FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Restricted function to set or update a document + * @param subject The contract the document belongs to. + * @param name_ The document name. + * @param uri_ The document location. + * @param documentHash_ The hash of the document contents. + */ + function setDocument(address subject, bytes32 name_, string memory uri_, bytes32 documentHash_) + public + override + onlyDocumentManager + { + _setDocument(subject, name_, uri_, documentHash_); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Internal helper to remove the document name from the list of document names + * @param subject The contract the document belongs to. + * @param name_ The document name to remove from the list. + */ + function _removeDocumentName(address subject, bytes32 name_) internal virtual { + bytes32[] storage names = _documentNames[subject]; + uint256 length = names.length; + for (uint256 i = 0; i < length; ++i) { + if (names[i] == name_) { + names[i] = names[length - 1]; + names.pop(); + break; + } + } + } + + /** + * @dev Shared removal implementation: reverts {ERC1643MissingDocument} when the document does + * not exist, then emits the address-carrying extension event and clears the entry. + * @param subject The contract the document belongs to. + * @param name_ The document name. + */ + function _removeDocument(address subject, bytes32 name_) internal virtual { + Document storage doc = _documents[subject][name_]; + // ERC-1643: reverts when the named document does not exist + if (doc.lastModified == 0) { + revert ERC1643MissingDocument(); + } + + // This engine is a shared, multi-subject manager: per the ERC-1643 + // "Emission Responsibility" rules it emits only the address-carrying + // extension event (the base `DocumentRemoved` is the token contract's + // responsibility). + emit DocumentRemovedForSubject(subject, name_, doc.uri, doc.documentHash); + + delete _documents[subject][name_]; + _removeDocumentName(subject, name_); + } + + /** + * @dev Shared create/update implementation: rejects a null `subject` and a null `name_`, tracks + * the name on first write, then stores the document and emits the extension event. + * @param subject The contract the document belongs to. + * @param name_ The document name. + * @param uri_ The document location. + * @param documentHash_ The hash of the document contents. + */ + function _setDocument(address subject, bytes32 name_, string memory uri_, bytes32 documentHash_) internal virtual { + // Multi-token guard: `subject` must be a real contract address, never the + // null namespace. (The bound-token path passes `_msgSender()`, never zero.) + if (subject == address(0)) { + revert MultiDocumentInvalidSubject(); + } + // ERC-1643: reject the null name (ambiguous / default key) + if (name_ == bytes32(0)) { + revert ERC1643InvalidName(); + } + + Document storage doc = _documents[subject][name_]; + if (doc.lastModified == 0) { + // new document + _documentNames[subject].push(name_); + } + doc.uri = uri_; + doc.documentHash = documentHash_; + doc.lastModified = block.timestamp; + + // Shared, multi-subject manager: emit only the address-carrying extension + // event (see the {_removeDocument} note). + emit DocumentUpdatedForSubject(subject, name_, uri_, documentHash_); + } + + /*////////////////////////////////////////////////////////////// + ACCESS CONTROL (hooks) + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Authorization hook for the admin document-management path. + * Implemented by the deployment contract (e.g. a role check). + */ + function _authorizeDocumentManagement() internal view virtual; + + /** + * @dev Authorization hook for the bound-token document-management path. + * Implemented by the deployment contract (e.g. a role check). + */ + function _authorizeBoundTokenDocumentManagement() internal view virtual; + + /** + * @dev Internal function to fetch a document, as flat values + * @param subject The contract the document belongs to. + * @param name_ The document name. + * @return uri Document location. + * @return documentHash Hash of the document contents. + * @return lastModified Last update timestamp. + */ + function _getDocument(address subject, bytes32 name_) + internal + view + virtual + returns (string memory uri, bytes32 documentHash, uint256 lastModified) + { + Document storage doc = _documents[subject][name_]; + return (doc.uri, doc.documentHash, doc.lastModified); + } +} diff --git a/src/DocumentEngineInvariant.sol b/src/DocumentEngineInvariant.sol index b3836d8..d89ae93 100644 --- a/src/DocumentEngineInvariant.sol +++ b/src/DocumentEngineInvariant.sol @@ -1,31 +1,29 @@ //SPDX-License-Identifier: MPL-2.0 -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; -contract DocumentEngineInvariant { - error DocumentNotFound(address smartContract, bytes32 name); +/** + * @title DocumentEngineInvariant + * @notice Shared errors for the DocumentEngine, common to every deployment + * regardless of its access-control model. + * @dev Access-control specifics (roles, owner, ...) are intentionally NOT + * defined here; they belong to the deployment contract (e.g. the role constants + * live in {DocumentEngine}, the owner logic in {DocumentEngineOwnable}). This + * contract is only ever used as a base, never deployed on its own. + */ +abstract contract DocumentEngineInvariant { error InvalidInputLength(); error AdminWithAddressZeroNotAllowed(); - event DocumentUpdated( - address smartContract, - bytes32 name, - string uri, - bytes32 documentHash - ); - event DocumentRemoved( - address smartContract, - bytes32 name, - string uri, - bytes32 documentHash - ); - - // Document structure - struct Document { - string uri; - bytes32 documentHash; - uint256 lastModified; - } - - bytes32 public constant DOCUMENT_MANAGER_ROLE = - keccak256("DOCUMENT_MANAGER_ROLE"); + // Only errors that no interface defines belong here. Every specification error is declared by + // the interface that defines its condition, so that an ABI generated from the interface carries + // it and a contract implementing several interfaces obtains each error exactly once — the + // multi-subject draft's "MUST NOT declare them twice", which the compiler also enforces: + // - `ERC1643InvalidName()` / `ERC1643MissingDocument()` → `IERC1643` (since CMTAT v3.3.0-rc2) + // - `MultiDocumentInvalidSubject()` → `IERC1643MultiDocument` + // - `TokenBindingInvalidToken()` → `ITokenBinding` + // + // `NotBoundToken(address)` is the one exception, declared by `TokenBindingModule` rather than by + // an interface: it reports that the *caller* is not on the module's allowlist, which is an + // implementation detail of how binding is enforced, not a condition any interface specifies. + // `ITokenBinding` deliberately declares only `TokenBindingInvalidToken()`. } diff --git a/src/DocumentEngineOwnable.sol b/src/DocumentEngineOwnable.sol new file mode 100644 index 0000000..8a9fa72 --- /dev/null +++ b/src/DocumentEngineOwnable.sol @@ -0,0 +1,93 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import {Ownable} from "OZ/access/Ownable.sol"; +import {Ownable2Step} from "OZ/access/Ownable2Step.sol"; +import {Context} from "OZ/utils/Context.sol"; +import {ERC2771Context} from "OZ/metatx/ERC2771Context.sol"; +import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol"; +import {IERC1643MultiDocument} from "./interfaces/IERC1643MultiDocument.sol"; +import {ITokenBinding} from "./interfaces/ITokenBinding.sol"; +import {TokenBindingModule} from "./modules/TokenBindingModule.sol"; +import {VersionModule} from "./modules/VersionModule.sol"; + +/** + * @title DocumentEngineOwnable + * @notice Alternative deployment of the DocumentEngine that uses a single owner + * ({Ownable2Step}) instead of role-based access control. + * @dev Reuses the same document-management logic ({DocumentEngineBase}) and token + * binding ({TokenBindingModule}), swapping only the access-control implementation: + * document management and token binding are both restricted to the `owner`, and a + * bound token manages only its own documents. Ownership uses the two-step transfer + * flow for safety, and the contract also exposes its version through ERC-8303 + * ({VersionModule}) and wires ERC-2771. + */ +contract DocumentEngineOwnable is TokenBindingModule, VersionModule, Ownable2Step, ERC2771Context { + /** + * @notice Deploys the engine with `owner_` as its single privileged account. + * @param owner_ initial owner of the contract + * @param forwarderIrrevocable address of the ERC-2771 forwarder (gasless support) + */ + constructor(address owner_, address forwarderIrrevocable) Ownable(owner_) ERC2771Context(forwarderIrrevocable) {} + + /*////////////////////////////////////////////////////////////// + ERC165 + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns whether this contract implements `interfaceId`. + * @dev ERC-165 discovery: advertises ERC-1643 and its multi-subject extension, the token-binding + * surface and the version module (ERC-8303). See the rationale on + * {DocumentEngine-supportsInterface} for what `type(IERC1643).interfaceId` does and does not + * tell a caller here. See {IERC165-supportsInterface}. + * @param interfaceId The ERC-165 interface identifier to query. + * @return True when `interfaceId` is one of the advertised interfaces or is supported by a base + * contract. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(VersionModule) returns (bool) { + return interfaceId == type(IERC1643).interfaceId || interfaceId == type(IERC1643MultiDocument).interfaceId + || interfaceId == type(ITokenBinding).interfaceId || super.supportsInterface(interfaceId); + } + + /*////////////////////////////////////////////////////////////// + ACCESS CONTROL (implementation) + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Authorization for the admin document-management path (and, via + * {TokenBindingModule}, for token binding): only the owner. + */ + function _authorizeDocumentManagement() internal view virtual override { + _checkOwner(); + } + + /*////////////////////////////////////////////////////////////// + ERC2771 + //////////////////////////////////////////////////////////////*/ + + /** + * @dev This surcharge is not necessary if you do not use ERC2771 + * @return sender The transaction sender, unwrapped from the ERC-2771 calldata suffix when the + * call came through the trusted forwarder. + */ + function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) { + return ERC2771Context._msgSender(); + } + + /** + * @dev This surcharge is not necessary if you do not use ERC2771 + * @return The calldata, stripped of the ERC-2771 sender suffix when the call came through the + * trusted forwarder. + */ + function _msgData() internal view virtual override(ERC2771Context, Context) returns (bytes calldata) { + return ERC2771Context._msgData(); + } + + /** + * @dev This surcharge is not necessary if you do not use the MetaTxModule + * @return The length of the ERC-2771 calldata suffix holding the sender address. + */ + function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) { + return ERC2771Context._contextSuffixLength(); + } +} diff --git a/src/interfaces/IERC1643MultiDocument.sol b/src/interfaces/IERC1643MultiDocument.sol new file mode 100644 index 0000000..fde3c6a --- /dev/null +++ b/src/interfaces/IERC1643MultiDocument.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +/** + * @title IERC1643MultiDocument — optional multi-token ERC-1643 extension + * @notice Address-scoped document management for a contract that manages + * documents on behalf of several `subject` contracts. + * @dev Declared **independently of `IERC1643`** (it does not inherit it), so a + * shared management contract can implement the address-scoped surface without + * being forced to implement the base single-argument functions. `subject` is the + * address of the contract the documents belong to — any contract, not only a token. + */ +interface IERC1643MultiDocument { + /** + * @notice Emitted when a document is created or updated for `subject`. + * @param subject The contract the document belongs to. + * @param name The document name. + * @param uri The document location. + * @param documentHash The hash of the document contents. + */ + event DocumentUpdatedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash); + + /** + * @notice Emitted when a document is removed for `subject`. + * @param subject The contract the document belonged to. + * @param name The document name. + * @param uri The document location as it was before removal. + * @param documentHash The hash of the document contents as it was before removal. + */ + event DocumentRemovedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash); + + /** + * @notice Reverts when `setDocument` or `removeDocument` is called with `subject == address(0)`. + * @dev Specific to this proposal; it has no ERC-1643 counterpart, because ERC-1643's + * `setDocument` has no `subject` argument — its subject is implicitly the contract itself, + * which is never the null address. Named after the proposal that defines the condition, not + * after one in which the condition cannot occur; the two errors this interface shares with + * ERC-1643 (`ERC1643InvalidName`, `ERC1643MissingDocument`) keep their prefix for the opposite + * reason, and are declared by `IERC1643`, never here. + */ + error MultiDocumentInvalidSubject(); + + /** + * @notice Creates or updates a document entry for `subject`. + * @dev MUST emit {DocumentUpdatedForSubject} on success. + * @param subject The contract the document belongs to. + * @param name The document name. + * @param uri The document location. + * @param documentHash The hash of the document contents. + */ + function setDocument(address subject, bytes32 name, string calldata uri, bytes32 documentHash) external; + + /** + * @notice Removes an existing document entry for `subject`. + * @dev MUST emit {DocumentRemovedForSubject} on success. + * @param subject The contract the document belongs to. + * @param name The document name. + */ + function removeDocument(address subject, bytes32 name) external; + + /** + * @notice Returns metadata for the document `name` belonging to `subject`. + * @dev Returns the three fields as flat values, matching the specification ABI. A missing + * document yields empty values (`""`, `bytes32(0)`, `0`) and does not revert. + * @param subject The contract the document belongs to. + * @param name The document name. + * @return uri Document location. + * @return documentHash Hash of the document contents. + * @return lastModified Last update timestamp. + */ + function getDocument(address subject, bytes32 name) + external + view + returns (string memory uri, bytes32 documentHash, uint256 lastModified); + + /** + * @notice Returns all document names currently tracked for `subject`. + * @param subject The contract to enumerate documents for. + * @return documentNames The names of every document currently tracked for `subject`. + */ + function getAllDocuments(address subject) external view returns (bytes32[] memory documentNames); +} diff --git a/src/interfaces/IERC8303.sol b/src/interfaces/IERC8303.sol new file mode 100644 index 0000000..4b6194c --- /dev/null +++ b/src/interfaces/IERC8303.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +/** + * @title IERC8303 - Contract Version + * @notice Interface for exposing a contract implementation version string. + * @dev ERC-8303 (Draft) — https://ethereum-magicians.org/t/erc-8303-contract-version/28795 + * The interface id is `0x54fd4d50` (the `version()` selector). + */ +interface IERC8303 { + /// @notice Returns the implementation version string. + /// @return The version value, for example "1.0.0". + function version() external view returns (string memory); +} diff --git a/src/interfaces/ITokenBinding.sol b/src/interfaces/ITokenBinding.sol new file mode 100644 index 0000000..13dd1d8 --- /dev/null +++ b/src/interfaces/ITokenBinding.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +/** + * @title ITokenBinding + * @notice Common token-binding surface shared by every DocumentEngine deployment, + * so integrators bind, unbind and query a token the same way regardless of the + * underlying access-control model (role-based or owner-based). + * @dev A *bound* token is allowed to manage its own documents through the standard + * single-argument ERC-1643 functions (`msg.sender` is the token). Binding is a + * privileged operation; the exact authorization (a role, the owner, ...) and the + * revert raised when a non-bound caller attempts a write are deployment-specific. + */ +interface ITokenBinding { + /** + * @notice Emitted when a token is bound (`bound = true`) or unbound (`bound = false`). + * @dev Emitted only when the binding actually changes, so the event stream contains no + * no-op entries and an indexer can replay it as a sequence of transitions. + * @param token The token whose binding changed. + * @param bound The new binding state: `true` when bound, `false` when unbound. + */ + event TokenBindingSet(address indexed token, bool bound); + + /** + * @notice Thrown when a binding operation targets the null address. + */ + error TokenBindingInvalidToken(); + + /** + * @notice Binds `token`, allowing it to manage its own documents. + * @dev Idempotent: binding an already-bound token succeeds and emits nothing. + * Reverts {TokenBindingInvalidToken} when `token` is the null address. + * @param token The token to bind. + */ + function bindToken(address token) external; + + /** + * @notice Unbinds `token`. + * @dev Idempotent: unbinding a token that is not bound succeeds and emits nothing. + * Reverts {TokenBindingInvalidToken} when `token` is the null address. + * @param token The token to unbind. + */ + function unbindToken(address token) external; + + /** + * @notice Returns whether `token` is currently bound. + * @param token The token to query. + * @return True when `token` is bound, false otherwise. + */ + function isTokenBound(address token) external view returns (bool); +} diff --git a/src/modules/TokenBindingModule.sol b/src/modules/TokenBindingModule.sol new file mode 100644 index 0000000..513b088 --- /dev/null +++ b/src/modules/TokenBindingModule.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import {DocumentEngineBase} from "../DocumentEngineBase.sol"; +import {ITokenBinding} from "../interfaces/ITokenBinding.sol"; + +/** + * @title TokenBindingModule + * @notice Shared token-binding registry (an allowlist) implementing {ITokenBinding}, + * used by every DocumentEngine deployment so binding behaves identically — same + * functions, same event, same revert — regardless of the access-control model. + * @dev A *bound* token may manage its own documents through the standard + * single-argument ERC-1643 functions (`msg.sender` is the token). This module: + * - stores the allowlist and implements `bindToken` / `unbindToken` / `isTokenBound`; + * - wires the base bound-token hook ({_authorizeBoundTokenDocumentManagement}) to + * the allowlist ({_checkTokenBound}); + * - gates binding management with the deployment's document-management + * authorization ({_authorizeDocumentManagement}), so whoever may manage + * documents may also decide bindings. It is therefore access-control agnostic: + * the deployment only implements {_authorizeDocumentManagement}. + */ +abstract contract TokenBindingModule is DocumentEngineBase, ITokenBinding { + /// @dev Tokens bound to the engine, allowed to manage their own documents. + mapping(address => bool) private _boundTokens; + + /// @notice Thrown when a non-bound caller attempts a bound-token operation. + error NotBoundToken(address caller); + + /** + * @inheritdoc ITokenBinding + * @dev Authorized by the deployment's document-management check. + */ + function bindToken(address token) external virtual override { + _authorizeDocumentManagement(); + _setTokenBinding(token, true); + } + + /** + * @inheritdoc ITokenBinding + * @dev Authorized by the deployment's document-management check. + */ + function unbindToken(address token) external virtual override { + _authorizeDocumentManagement(); + _setTokenBinding(token, false); + } + + /** + * @inheritdoc ITokenBinding + */ + function isTokenBound(address token) public view virtual override returns (bool) { + return _boundTokens[token]; + } + + /** + * @dev Shared bind/unbind implementation. + * + * Rejects the null address: `address(0)` can never call the engine, so binding it grants + * nothing, but it would still emit a {TokenBindingSet} that off-chain indexers key on — the + * same data-integrity argument the multi-subject draft makes for rejecting a null `subject`. + * + * Writing and emitting only on an actual change makes both functions idempotent and keeps the + * event stream free of no-op entries, so an indexer can treat every {TokenBindingSet} as a real + * transition rather than having to de-duplicate. The repeated call still succeeds, since the + * caller's intent — "this token is (not) bound" — already holds. + * @param token The token whose binding is being set. + * @param bound The binding state to apply: `true` to bind, `false` to unbind. + */ + function _setTokenBinding(address token, bool bound) internal virtual { + if (token == address(0)) { + revert TokenBindingInvalidToken(); + } + if (_boundTokens[token] == bound) { + return; + } + _boundTokens[token] = bound; + emit TokenBindingSet(token, bound); + } + + /** + * @dev Bound-token document-management authorization: the caller + * (`_msgSender()`) must be a bound token. + */ + function _authorizeBoundTokenDocumentManagement() internal view virtual override { + _checkTokenBound(); + } + + /// @dev Reverts {NotBoundToken} if the caller (`_msgSender()`) is not bound. + function _checkTokenBound() internal view virtual { + if (!_boundTokens[_msgSender()]) { + revert NotBoundToken(_msgSender()); + } + } +} diff --git a/src/modules/VersionModule.sol b/src/modules/VersionModule.sol new file mode 100644 index 0000000..68b561c --- /dev/null +++ b/src/modules/VersionModule.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import {ERC165} from "OZ/utils/introspection/ERC165.sol"; +import {IERC8303} from "../interfaces/IERC8303.sol"; + +/** + * @title VersionModule + * @notice Exposes the current contract version through ERC-8303 (`version()`), + * with optional ERC-165 interface discovery. + * @dev Implements ERC-8303 (Draft). The version string is defined here so the + * version concern is isolated in a dedicated module (CMTAT pattern). A deployment + * contract that also implements ERC-165 must combine this module's + * {supportsInterface} with the others it inherits. + */ +abstract contract VersionModule is IERC8303, ERC165 { + /** + * @notice Get the current version of the smart contract. + * @dev Follows Semantic Versioning 2.0.0 (`MAJOR.MINOR.PATCH`). + */ + string public constant VERSION = "0.4.0"; + + /** + * @inheritdoc IERC8303 + */ + function version() public view virtual override(IERC8303) returns (string memory version_) { + return VERSION; + } + + /** + * @notice Returns whether this contract implements `interfaceId`. + * @dev Advertises ERC-8303 support (interface id `0x54fd4d50`). + * See {IERC165-supportsInterface}. + * @param interfaceId The ERC-165 interface identifier to query. + * @return True when `interfaceId` is ERC-8303 or is supported by a base contract. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC8303).interfaceId || super.supportsInterface(interfaceId); + } +} diff --git a/test/Deploy.t.sol b/test/Deploy.t.sol new file mode 100644 index 0000000..6ad3cdd --- /dev/null +++ b/test/Deploy.t.sol @@ -0,0 +1,78 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {DeployDocumentEngine} from "../script/DeployDocumentEngine.s.sol"; +import {DeployDocumentEngineOwnable} from "../script/DeployDocumentEngineOwnable.s.sol"; +import {DocumentEngine} from "../src/DocumentEngine.sol"; +import {DocumentEngineOwnable} from "../src/DocumentEngineOwnable.sol"; + +contract DeployDocumentEngineTest is Test { + DeployDocumentEngine internal deployer; + address internal admin = makeAddr("admin"); + address internal forwarder = makeAddr("forwarder"); + + function setUp() public { + deployer = new DeployDocumentEngine(); + } + + function testDeploySetsAdminAndForwarder() public { + DocumentEngine engine = deployer.deploy(admin, forwarder); + + assertTrue(engine.hasRole(engine.DEFAULT_ADMIN_ROLE(), admin)); + assertTrue(engine.isTrustedForwarder(forwarder)); + assertEq(engine.version(), "0.4.0"); + } + + function testDeployWithoutForwarder() public { + DocumentEngine engine = deployer.deploy(admin, address(0)); + + assertTrue(engine.hasRole(engine.DEFAULT_ADMIN_ROLE(), admin)); + assertFalse(engine.isTrustedForwarder(forwarder)); + } + + function testRunReadsEnv() public { + vm.setEnv("DOCUMENT_ENGINE_ADMIN", vm.toString(admin)); + vm.setEnv("DOCUMENT_ENGINE_FORWARDER", vm.toString(forwarder)); + + DocumentEngine engine = deployer.run(); + + assertTrue(engine.hasRole(engine.DEFAULT_ADMIN_ROLE(), admin)); + assertTrue(engine.isTrustedForwarder(forwarder)); + } +} + +contract DeployDocumentEngineOwnableTest is Test { + DeployDocumentEngineOwnable internal deployer; + address internal owner = makeAddr("owner"); + address internal forwarder = makeAddr("forwarder"); + + function setUp() public { + deployer = new DeployDocumentEngineOwnable(); + } + + function testDeploySetsOwnerAndForwarder() public { + DocumentEngineOwnable engine = deployer.deploy(owner, forwarder); + + assertEq(engine.owner(), owner); + assertTrue(engine.isTrustedForwarder(forwarder)); + assertEq(engine.version(), "0.4.0"); + } + + function testDeployWithoutForwarder() public { + DocumentEngineOwnable engine = deployer.deploy(owner, address(0)); + + assertEq(engine.owner(), owner); + assertFalse(engine.isTrustedForwarder(forwarder)); + } + + function testRunReadsEnv() public { + vm.setEnv("DOCUMENT_ENGINE_OWNER", vm.toString(owner)); + vm.setEnv("DOCUMENT_ENGINE_FORWARDER", vm.toString(forwarder)); + + DocumentEngineOwnable engine = deployer.run(); + + assertEq(engine.owner(), owner); + assertTrue(engine.isTrustedForwarder(forwarder)); + } +} diff --git a/test/DocumentEngine.t.sol b/test/DocumentEngine.t.sol index fda9890..845496f 100644 --- a/test/DocumentEngine.t.sol +++ b/test/DocumentEngine.t.sol @@ -1,11 +1,81 @@ //SPDX-License-Identifier: MPL-2.0 -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import "forge-std/Test.sol"; import "../src/DocumentEngine.sol"; import "../src/DocumentEngineInvariant.sol"; import "OZ/access/AccessControl.sol"; -import "CMTAT/CMTAT_STANDALONE.sol"; +import {IERC165} from "OZ/utils/introspection/IERC165.sol"; +import {IERC8303} from "../src/interfaces/IERC8303.sol"; +// Imported explicitly rather than relied on transitively through DocumentEngine.sol. +import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol"; +import {IERC1643MultiDocument} from "../src/interfaces/IERC1643MultiDocument.sol"; +import {ITokenBinding} from "../src/interfaces/ITokenBinding.sol"; +import {TokenBindingModule} from "../src/modules/TokenBindingModule.sol"; +import {DocumentEngineModule} from "CMTAT/modules/wrapper/options/DocumentEngineModule.sol"; + +/** + * @dev Minimal token wired to CMTAT's official `DocumentEngineModule`. + * + * Since CMTAT v3, the shipped standalone tokens store documents on-chain + * (`DocumentERC1643Module`) and no longer consume an external document engine + * through their constructor. Integration with an external `DocumentEngine` now + * goes through `DocumentEngineModule`, which this mock exercises with real + * CMTAT code: reads/writes are forwarded to the engine keyed by `msg.sender`. + */ +contract CMTATDocumentEngineMock is DocumentEngineModule { + // No access restriction for the mock: document management is authorized for anyone. + function _authorizeDocumentManagement() internal override {} +} + +/** + * @dev Demonstrates the flexible access control: overriding the authorization + * hook opens the admin document-management path to anyone, without touching the + * document-management implementation. + */ +contract OpenDocumentEngine is DocumentEngine { + constructor(address admin, address forwarder) DocumentEngine(admin, forwarder) {} + + function _authorizeDocumentManagement() internal view override { + // no access restriction (custom authorization) + } +} + +/** + * @dev Guard for the `internal virtual` convention (CLAUDE_ANALYSIS.md E-1). + * + * Overrides three of the internal hooks — one from `DocumentEngineBase`'s write path, one from its + * read path, and one from `TokenBindingModule`'s authorization path. Two things are being pinned: + * dropping `virtual` from any of them stops this contract compiling, and the assertions in + * {DocumentEngineTest-testInternalHooksAreVirtualAndOverridesAreReached} prove each override is + * actually reached rather than silently shadowed. + */ +contract OverridingDocumentEngine is DocumentEngine { + uint256 public setDocumentCalls; + + constructor(address admin_, address forwarder) DocumentEngine(admin_, forwarder) {} + + /// @dev Counts invocations, then defers to the base implementation. + function _setDocument(address subject, bytes32 name_, string memory uri_, bytes32 documentHash_) internal override { + ++setDocumentCalls; + super._setDocument(subject, name_, uri_, documentHash_); + } + + /// @dev Tags the URI so a caller can observe that this override ran. + function _getDocument(address subject, bytes32 name_) + internal + view + override + returns (string memory uri, bytes32 documentHash, uint256 lastModified) + { + (uri, documentHash, lastModified) = super._getDocument(subject, name_); + uri = string.concat("override:", uri); + } + + /// @dev Deliberately permissive: every caller passes the bound-token gate. + function _checkTokenBound() internal view override {} +} + contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { DocumentEngine public documentEngine; address public admin = address(0x1); @@ -17,45 +87,41 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { string public documentURI = "https://example.com/doc1"; bytes32 public documentHash = keccak256("doc1Hash"); bytes32 public constant DOCUMENT_ROLE = keccak256("DOCUMENT_ROLE"); + // Roles are defined on the role-based deployment (DocumentEngine), not on the + // shared DocumentEngineInvariant; mirrored here for the assertions. + bytes32 public constant DOCUMENT_MANAGER_ROLE = keccak256("DOCUMENT_MANAGER_ROLE"); address AddressZero = address(0); - CMTAT_STANDALONE cmtat; + + // Local copies of the extension events, so `vm.expectEmit` can emit and match them. + event DocumentUpdatedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash); + event DocumentRemovedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash); + event TokenBindingSet(address indexed token, bool bound); + // Base ERC-1643 event signatures (this shared engine must NOT emit them). + bytes32 internal constant BASE_UPDATED_SIG = keccak256("DocumentUpdated(bytes32,string,bytes32)"); + bytes32 internal constant BASE_REMOVED_SIG = keccak256("DocumentRemoved(bytes32,string,bytes32)"); + + /** + * @dev Since CMTAT `v3.3.0-rc2`, `getDocument` returns the three ERC-1643 fields as flat + * values instead of a `Document` struct. These helpers repack them so the assertions below + * stay readable; {testGetDocumentReturnsFlatErc1643Abi} pins the wire format itself. + */ + function _doc(IERC1643MultiDocument engine_, address subject, bytes32 name_) + internal + view + returns (IERC1643.Document memory document) + { + (document.uri, document.documentHash, document.lastModified) = engine_.getDocument(subject, name_); + } + + /// @dev See {_doc(IERC1643MultiDocument,address,bytes32)}; caller-scoped ERC-1643 read. + function _doc(IERC1643 engine_, bytes32 name_) internal view returns (IERC1643.Document memory document) { + (document.uri, document.documentHash, document.lastModified) = engine_.getDocument(name_); + } + function setUp() public { documentEngine = new DocumentEngine(admin, AddressZero); vm.prank(admin); - documentEngine.setDocument( - testContract, - documentName, - documentURI, - documentHash - ); - - // CMTAT - ICMTATConstructor.ERC20Attributes - memory erc20Attributes = ICMTATConstructor.ERC20Attributes( - "CMTA Token", - "CMTAT", - 0 - ); - ICMTATConstructor.BaseModuleAttributes - memory baseModuleAttributes = ICMTATConstructor - .BaseModuleAttributes( - "CMTAT_ISIN", - "https://cmta.ch", - "CMTAT_info" - ); - ICMTATConstructor.Engine memory engines = ICMTATConstructor.Engine( - IRuleEngine(AddressZero), - IDebtEngine(AddressZero), - IAuthorizationEngine(AddressZero), - IERC1643(AddressZero) - ); - cmtat = new CMTAT_STANDALONE( - AddressZero, - admin, - erc20Attributes, - baseModuleAttributes, - engines - ); + documentEngine.setDocument(testContract, documentName, documentURI, documentHash); } /*////////////////////////////////////////////////////////////// @@ -69,9 +135,7 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { // Forwarder assertEq(documentEngine.isTrustedForwarder(forwarder), true); // admin - vm.expectRevert( - abi.encodeWithSelector(AdminWithAddressZeroNotAllowed.selector) - ); + vm.expectRevert(abi.encodeWithSelector(AdminWithAddressZeroNotAllowed.selector)); documentEngine = new DocumentEngine(AddressZero, forwarder); } @@ -82,28 +146,62 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { function testCannotNonAdminSetDocument() public { vm.prank(attacker); vm.expectRevert( - abi.encodeWithSelector( - AccessControlUnauthorizedAccount.selector, - attacker, - DOCUMENT_MANAGER_ROLE - ) - ); - documentEngine.setDocument( - testContract, - documentName, - documentURI, - documentHash + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) ); + documentEngine.setDocument(testContract, documentName, documentURI, documentHash); + } + + /** + * @dev Pins the consequence of the "default admin holds every role" {hasRole} override: + * `revokeRole` against the default admin SUCCEEDS and emits `RoleRevoked`, yet the admin + * keeps the access. The revocation is not silently ignored by mistake — the explicit grant + * really is removed (`getRoleMemberCount` drops) — but `hasRole` still answers `true` + * because the override short-circuits on `DEFAULT_ADMIN_ROLE`, so the authorization gate + * still lets the admin through. Recorded in CLAUDE_ANALYSIS.md (H-1). + */ + function testRevokingRoleFromDefaultAdminDoesNotRemoveAccess() public { + assertTrue(documentEngine.hasRole(DOCUMENT_MANAGER_ROLE, admin), "admin implicitly holds the role"); + + vm.prank(admin); + documentEngine.revokeRole(DOCUMENT_MANAGER_ROLE, admin); // succeeds, no revert + + assertTrue(documentEngine.hasRole(DOCUMENT_MANAGER_ROLE, admin), "revoke does NOT take the role from the admin"); + assertEq(documentEngine.getRoleMemberCount(DOCUMENT_MANAGER_ROLE), 0, "no explicit grant remains"); + + // and the admin still passes the authorization gate + vm.prank(admin); + documentEngine.setDocument(testContract, documentName, documentURI, documentHash); + } + + /** + * @dev See {OverridingDocumentEngine}. Compilation alone proves the three hooks are `virtual`; + * these assertions prove each override is on the real call path. + */ + function testInternalHooksAreVirtualAndOverridesAreReached() public { + OverridingDocumentEngine engine = new OverridingDocumentEngine(admin, AddressZero); + + // _setDocument override reached on the admin write path + vm.prank(admin); + engine.setDocument(testContract, documentName, documentURI, documentHash); + assertEq(engine.setDocumentCalls(), 1, "_setDocument override not reached"); + + // _getDocument override reached on the read path + (string memory uri,,) = engine.getDocument(testContract, documentName); + assertEq(uri, string.concat("override:", documentURI), "_getDocument override not reached"); + + // _checkTokenBound override reached: an UNBOUND caller now passes the bound-token gate, + // which would otherwise revert NotBoundToken. + vm.prank(attacker); + engine.setDocument(documentName, documentURI, documentHash); + assertEq(engine.setDocumentCalls(), 2, "bound-token path did not run"); + (string memory ownUri,,) = engine.getDocument(attacker, documentName); + assertEq(ownUri, string.concat("override:", documentURI), "_checkTokenBound override not reached"); } function testCannotNonAdminRemoveDocument() public { vm.prank(attacker); vm.expectRevert( - abi.encodeWithSelector( - AccessControlUnauthorizedAccount.selector, - attacker, - DOCUMENT_MANAGER_ROLE - ) + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) ); documentEngine.removeDocument(testContract, documentName); } @@ -127,21 +225,13 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { vm.prank(attacker); vm.expectRevert( - abi.encodeWithSelector( - AccessControlUnauthorizedAccount.selector, - attacker, - DOCUMENT_MANAGER_ROLE - ) + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) ); documentEngine.batchSetDocuments(smartContracts, names, uris, hashes); vm.prank(attacker); vm.expectRevert( - abi.encodeWithSelector( - AccessControlUnauthorizedAccount.selector, - attacker, - DOCUMENT_MANAGER_ROLE - ) + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) ); documentEngine.batchSetDocuments(testContract, names, uris, hashes); } @@ -157,58 +247,309 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { vm.prank(attacker); vm.expectRevert( - abi.encodeWithSelector( - AccessControlUnauthorizedAccount.selector, - attacker, - DOCUMENT_MANAGER_ROLE - ) + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) ); documentEngine.batchRemoveDocuments(smartContracts, names); vm.prank(attacker); vm.expectRevert( - abi.encodeWithSelector( - AccessControlUnauthorizedAccount.selector, - attacker, - DOCUMENT_MANAGER_ROLE - ) + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) ); documentEngine.batchRemoveDocuments(testContract, names); } /*////////////////////////////////////////////////////////////// - Get + Get //////////////////////////////////////////////////////////////*/ - function testGetAllDocuments() public view { + function testGetAllDocuments() public { bytes32[] memory docs = documentEngine.getAllDocuments(testContract); assertEq(docs.length, 1); assertEq(docs[0], documentName); } + /*////////////////////////////////////////////////////////////// + CMTAT integration (external engine via DocumentEngineModule) + //////////////////////////////////////////////////////////////*/ + function testCanReturnCMTATDocument() public { - // Arrange + // Arrange: a CMTAT-style token bound to the engine + CMTATDocumentEngineMock cmtat = new CMTATDocumentEngineMock(); + cmtat.setDocumentEngine(documentEngine); + uint256 lastModif = block.timestamp; vm.prank(admin); - documentEngine.setDocument( - address(cmtat), - documentName, - documentURI, - documentHash - ); - vm.prank(admin); - cmtat.setDocumentEngine(documentEngine); + documentEngine.setDocument(address(cmtat), documentName, documentURI, documentHash); - // Call from CMTAT, return document + // Call from CMTAT, forwarded to the engine bytes32[] memory docs = cmtat.getAllDocuments(); assertEq(docs.length, 1); assertEq(docs[0], documentName); - (string memory uri, bytes32 hash, uint256 lastModified) = cmtat - .getDocument(documentName); + IERC1643.Document memory doc = _doc(cmtat, documentName); + assertEq(doc.uri, documentURI); + assertEq(doc.documentHash, documentHash); + assertEq(doc.lastModified, lastModif); + } + + /*////////////////////////////////////////////////////////////// + Bound token (shared ITokenBinding allowlist / TokenBindingModule) + //////////////////////////////////////////////////////////////*/ + + function testBoundTokenCanManageOwnDocument() public { + // Bind the token to the engine (shared ITokenBinding surface) + vm.prank(admin); + documentEngine.bindToken(testContract); + assertTrue(documentEngine.isTokenBound(testContract)); + + // The bound token manages its own document namespace (msg.sender) + bytes32 selfName = keccak256("self-doc"); + string memory selfURI = "https://example.com/self"; + bytes32 selfHash = keccak256("selfHash"); + + vm.prank(testContract); + documentEngine.setDocument(selfName, selfURI, selfHash); + + IERC1643.Document memory doc = _doc(documentEngine, testContract, selfName); + assertEq(doc.uri, selfURI); + assertEq(doc.documentHash, selfHash); + assertEq(doc.lastModified, block.timestamp); + + // and can remove it + vm.prank(testContract); + documentEngine.removeDocument(selfName); + doc = _doc(documentEngine, testContract, selfName); + assertEq(doc.uri, ""); + assertEq(doc.documentHash, ""); + assertEq(doc.lastModified, 0); + } + + function testNonAdminCannotBindToken() public { + vm.prank(attacker); + vm.expectRevert( + abi.encodeWithSelector(AccessControlUnauthorizedAccount.selector, attacker, DOCUMENT_MANAGER_ROLE) + ); + documentEngine.bindToken(testContract); + } + + function testAdminCanUnbindToken() public { + vm.prank(admin); + documentEngine.bindToken(testContract); + assertTrue(documentEngine.isTokenBound(testContract)); + + vm.prank(admin); + documentEngine.unbindToken(testContract); + assertFalse(documentEngine.isTokenBound(testContract)); + } + + function testCannotBindZeroAddress() public { + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(ITokenBinding.TokenBindingInvalidToken.selector)); + documentEngine.bindToken(AddressZero); + + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(ITokenBinding.TokenBindingInvalidToken.selector)); + documentEngine.unbindToken(AddressZero); + + assertFalse(documentEngine.isTokenBound(AddressZero)); + } + + /** + * @dev Binding is idempotent: the repeated call succeeds, because the caller's intent already + * holds, but emits nothing — so every {TokenBindingSet} in the log is a real transition and an + * indexer never has to de-duplicate. + */ + function testBindTokenIsIdempotentAndDoesNotReEmit() public { + vm.prank(admin); + vm.expectEmit(true, false, false, true); + emit TokenBindingSet(testContract, true); + documentEngine.bindToken(testContract); + + // second bind: succeeds, changes nothing, emits nothing + vm.recordLogs(); + vm.prank(admin); + documentEngine.bindToken(testContract); + assertEq(vm.getRecordedLogs().length, 0, "re-binding must not emit"); + assertTrue(documentEngine.isTokenBound(testContract)); + } + + function testUnbindTokenIsIdempotentAndDoesNotReEmit() public { + // unbinding a token that was never bound: succeeds, emits nothing + vm.recordLogs(); + vm.prank(admin); + documentEngine.unbindToken(testContract); + assertEq(vm.getRecordedLogs().length, 0, "unbinding an unbound token must not emit"); + assertFalse(documentEngine.isTokenBound(testContract)); + + vm.prank(admin); + documentEngine.bindToken(testContract); + + vm.prank(admin); + vm.expectEmit(true, false, false, true); + emit TokenBindingSet(testContract, false); + documentEngine.unbindToken(testContract); + + vm.recordLogs(); + vm.prank(admin); + documentEngine.unbindToken(testContract); + assertEq(vm.getRecordedLogs().length, 0, "re-unbinding must not emit"); + assertFalse(documentEngine.isTokenBound(testContract)); + } + + function testUnboundContractCannotSetOwnDocument() public { + bytes32 selfName = keccak256("self-doc"); + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(TokenBindingModule.NotBoundToken.selector, attacker)); + documentEngine.setDocument(selfName, documentURI, documentHash); + } + + function testUnboundContractCannotRemoveOwnDocument() public { + bytes32 selfName = keccak256("self-doc"); + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(TokenBindingModule.NotBoundToken.selector, attacker)); + documentEngine.removeDocument(selfName); + } + + /*////////////////////////////////////////////////////////////// + Flexible access control (overridable authorization hook) + //////////////////////////////////////////////////////////////*/ + + function testFlexibleAuthorizationCanBeOverridden() public { + OpenDocumentEngine openEngine = new OpenDocumentEngine(admin, AddressZero); + + // attacker holds no role, yet can manage documents because the + // authorization hook was overridden to allow anyone. + vm.prank(attacker); + openEngine.setDocument(testContract, documentName, documentURI, documentHash); + + IERC1643.Document memory doc = _doc(openEngine, testContract, documentName); + assertEq(doc.uri, documentURI); + assertEq(doc.documentHash, documentHash); + } + + /*////////////////////////////////////////////////////////////// + Version (ERC-8303) + //////////////////////////////////////////////////////////////*/ + + function testVersionReturnsNonEmptyString() public { + string memory v = documentEngine.version(); + assertGt(bytes(v).length, 0); + assertEq(v, "0.4.0"); + // the public VERSION constant matches version() + assertEq(documentEngine.VERSION(), v); + } + + function testSupportsInterfaceERC8303() public { + // interface id declared by ERC-8303 + assertEq(type(IERC8303).interfaceId, bytes4(0x54fd4d50)); + assertTrue(documentEngine.supportsInterface(type(IERC8303).interfaceId)); + } + + function testSupportsERC1643Interfaces() public { + // Pinned literals: an interface id is the XOR of the selectors, which depend only on the + // function names and argument types. The CMTAT `v3.3.0-rc2` change of the `getDocument` + // return shape therefore moved neither id — which is exactly why that change was + // undetectable through ERC-165 (see {testGetDocumentReturnsFlatErc1643Abi}). + assertEq(type(IERC1643).interfaceId, bytes4(0xecfecec8)); + assertEq(type(IERC1643MultiDocument).interfaceId, bytes4(0xa2b1179b)); + + // implements the base single-argument functions, so a token can detect them here... + assertTrue(documentEngine.supportsInterface(type(IERC1643).interfaceId)); + // ...the address-scoped multi-subject interface... + assertTrue(documentEngine.supportsInterface(type(IERC1643MultiDocument).interfaceId)); + // ...and the shared token-binding surface + assertTrue(documentEngine.supportsInterface(type(ITokenBinding).interfaceId)); + } + + /** + * @dev What `type(IERC1643).interfaceId` does and does not promise here. + * + * It promises the base single-argument functions exist, which is what a token checks before + * wiring itself to the engine. It does **not** make this address a document endpoint for third + * parties: those functions are `_msgSender()`-scoped, so an external reader gets its own empty + * namespace rather than the subject's documents — silently, with no revert. That asymmetry is + * asserted here so it stays a documented property rather than a surprise. + */ + function testBaseERC1643IsAdvertisedButReadsAreCallerScoped() public { + assertTrue(documentEngine.supportsInterface(type(IERC1643).interfaceId)); + + // `documentName` exists — but only under `testContract`, not under an arbitrary reader. + (,, uint256 lastModifiedForSubject) = documentEngine.getDocument(testContract, documentName); + assertGt(lastModifiedForSubject, 0); + + vm.prank(user); + (string memory uri, bytes32 hash_, uint256 lastModified) = documentEngine.getDocument(documentName); + assertEq(uri, ""); + assertEq(hash_, bytes32(0)); + assertEq(lastModified, 0, "a caller-scoped read returns the caller's own namespace, not the subject's"); + } + + /** + * @dev Pins the `getDocument` wire format to the flat ERC-1643 ABI. + * + * Return types do not take part in a function signature, so returning a `Document` struct + * instead of the three flat values leaves both the selector and `type(IERC1643).interfaceId` + * unchanged: ERC-165 discovery cannot catch the difference, and a consumer built from the + * specification ABI would silently decode a struct return as garbage. The only way to catch a + * regression is to inspect the returndata, so assert the first word is the string offset + * (`0x60`) of a flat `(string,bytes32,uint256)` and not the `0x20` struct offset. + */ + function testGetDocumentReturnsFlatErc1643Abi() public { + (bool okSubject, bytes memory subjectScoped) = address(documentEngine) + .staticcall(abi.encodeWithSignature("getDocument(address,bytes32)", testContract, documentName)); + assertTrue(okSubject); + assertEq(_firstWord(subjectScoped), 0x60, "getDocument(address,bytes32) must return flat values"); + + vm.prank(testContract); + (bool okSelf, bytes memory selfScoped) = + address(documentEngine).staticcall(abi.encodeWithSignature("getDocument(bytes32)", documentName)); + assertTrue(okSelf); + assertEq(_firstWord(selfScoped), 0x60, "getDocument(bytes32) must return flat values"); + + // The decoded values must round-trip through the specification's own signature. + (string memory uri, bytes32 hash_, uint256 lastModified) = abi.decode(subjectScoped, (string, bytes32, uint256)); assertEq(uri, documentURI); - assertEq(hash, documentHash); - assertEq(lastModif, lastModified); + assertEq(hash_, documentHash); + assertEq(lastModified, block.timestamp); + } + + function _firstWord(bytes memory data) private pure returns (uint256 word) { + assembly { + word := mload(add(data, 0x20)) + } + } + + /*////////////////////////////////////////////////////////////// + ERC-1643 input validation + //////////////////////////////////////////////////////////////*/ + + function testCannotSetDocumentWithZeroName() public { + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IERC1643.ERC1643InvalidName.selector)); + documentEngine.setDocument(testContract, bytes32(0), documentURI, documentHash); + } + + function testCannotRemoveMissingDocument() public { + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IERC1643.ERC1643MissingDocument.selector)); + documentEngine.removeDocument(testContract, keccak256("does-not-exist")); + } + + function testBoundTokenCannotSetZeroName() public { + vm.prank(admin); + documentEngine.bindToken(testContract); + vm.prank(testContract); + vm.expectRevert(abi.encodeWithSelector(IERC1643.ERC1643InvalidName.selector)); + documentEngine.setDocument(bytes32(0), documentURI, documentHash); + } + + function testSupportsInterfaceERC165AndAccessControl() public { + assertTrue(documentEngine.supportsInterface(type(IERC165).interfaceId)); + assertTrue(documentEngine.supportsInterface(type(IAccessControl).interfaceId)); + } + + function testDoesNotSupportInvalidInterface() public { + assertFalse(documentEngine.supportsInterface(bytes4(0xffffffff))); } /*////////////////////////////////////////////////////////////// @@ -217,29 +558,18 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { function testAdminCanSetDocument() public { uint256 lastModif = block.timestamp; vm.prank(admin); - documentEngine.setDocument( - testContract, - documentName, - documentURI, - documentHash - ); + documentEngine.setDocument(testContract, documentName, documentURI, documentHash); - (string memory uri, bytes32 hash, uint256 lastModified) = documentEngine - .getDocument(testContract, documentName); - assertEq(uri, documentURI); - assertEq(hash, documentHash); - assertEq(lastModif, lastModified); + IERC1643.Document memory doc = _doc(documentEngine, testContract, documentName); + assertEq(doc.uri, documentURI); + assertEq(doc.documentHash, documentHash); + assertEq(doc.lastModified, lastModif); } function testAdminCanSetDocumentAgain() public { // Arrange vm.prank(admin); - documentEngine.setDocument( - testContract, - documentName, - documentURI, - documentHash - ); + documentEngine.setDocument(testContract, documentName, documentURI, documentHash); bytes32[] memory docs = documentEngine.getAllDocuments(testContract); assertEq(docs.length, 1); assertEq(docs[0], documentName); @@ -248,19 +578,13 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { string memory documentURIV2 = "https://example.com/doc1"; bytes32 documentHashV2 = keccak256("doc1Hash"); vm.prank(admin); - documentEngine.setDocument( - testContract, - documentName, - documentURIV2, - documentHashV2 - ); + documentEngine.setDocument(testContract, documentName, documentURIV2, documentHashV2); // Assert - (string memory uri, bytes32 hash, uint256 lastModified) = documentEngine - .getDocument(testContract, documentName); - assertEq(uri, documentURIV2); - assertEq(hash, documentHashV2); - assertEq(lastModif, lastModified); + IERC1643.Document memory doc = _doc(documentEngine, testContract, documentName); + assertEq(doc.uri, documentURIV2); + assertEq(doc.documentHash, documentHashV2); + assertEq(doc.lastModified, lastModif); docs = documentEngine.getAllDocuments(testContract); assertEq(docs.length, 1); assertEq(docs[0], documentName); @@ -287,24 +611,16 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { documentEngine.batchSetDocuments(smartContracts, names, uris, hashes); // Check the first document - ( - string memory uri1, - bytes32 hash1, - uint256 lastModified1 - ) = documentEngine.getDocument(testContract, documentName); - assertEq(uri1, documentURI); - assertEq(hash1, documentHash); - assertEq(lastModified1, block.timestamp); + IERC1643.Document memory doc1 = _doc(documentEngine, testContract, documentName); + assertEq(doc1.uri, documentURI); + assertEq(doc1.documentHash, documentHash); + assertEq(doc1.lastModified, block.timestamp); // Check the second document - ( - string memory uri2, - bytes32 hash2, - uint256 lastModified2 - ) = documentEngine.getDocument(anotherSmartContract, names[1]); - assertEq(uri2, uris[1]); - assertEq(hash2, hashes[1]); - assertEq(lastModified2, block.timestamp); + IERC1643.Document memory doc2 = _doc(documentEngine, anotherSmartContract, names[1]); + assertEq(doc2.uri, uris[1]); + assertEq(doc2.documentHash, hashes[1]); + assertEq(doc2.lastModified, block.timestamp); } function testAdminCanBatchSetDocumentsForTheSameContract() public { @@ -328,24 +644,16 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { documentEngine.batchSetDocuments(smartContracts, names, uris, hashes); // Check the first document - ( - string memory uri1, - bytes32 hash1, - uint256 lastModified1 - ) = documentEngine.getDocument(testContract, documentName); - assertEq(uri1, documentURI); - assertEq(hash1, documentHash); - assertEq(lastModified1, block.timestamp); + IERC1643.Document memory doc1 = _doc(documentEngine, testContract, documentName); + assertEq(doc1.uri, documentURI); + assertEq(doc1.documentHash, documentHash); + assertEq(doc1.lastModified, block.timestamp); // Check the second document - ( - string memory uri2, - bytes32 hash2, - uint256 lastModified2 - ) = documentEngine.getDocument(testContract, names[1]); - assertEq(uri2, uris[1]); - assertEq(hash2, hashes[1]); - assertEq(lastModified2, block.timestamp); + IERC1643.Document memory doc2 = _doc(documentEngine, testContract, names[1]); + assertEq(doc2.uri, uris[1]); + assertEq(doc2.documentHash, hashes[1]); + assertEq(doc2.lastModified, block.timestamp); } function testCannotAddBatchDocumentIfLengthMismatch_A() public { @@ -412,11 +720,10 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { // Check that both documents are removed // Check the second document - (string memory uri, bytes32 hash, uint256 lastModified) = documentEngine - .getDocument(testContract, documentName); - assertEq(uri, ""); - assertEq(hash, ""); - assertEq(lastModified, 0); + IERC1643.Document memory doc = _doc(documentEngine, testContract, documentName); + assertEq(doc.uri, ""); + assertEq(doc.documentHash, ""); + assertEq(doc.lastModified, 0); bytes32[] memory docs = documentEngine.getAllDocuments(testContract); assertEq(docs.length, 0); } @@ -439,22 +746,17 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { // Check that both documents are removed // Check the second document - (string memory uri, bytes32 hash, uint256 lastModified) = documentEngine - .getDocument(testContract, documentName); - assertEq(uri, ""); - assertEq(hash, ""); - assertEq(lastModified, 0); + IERC1643.Document memory doc = _doc(documentEngine, testContract, documentName); + assertEq(doc.uri, ""); + assertEq(doc.documentHash, ""); + assertEq(doc.lastModified, 0); bytes32[] memory docs = documentEngine.getAllDocuments(testContract); assertEq(docs.length, 0); - ( - string memory uri2, - bytes32 hash2, - uint256 lastModified2 - ) = documentEngine.getDocument(anotherSmartContract, names[1]); - assertEq(uri2, ""); - assertEq(hash2, ""); - assertEq(lastModified2, 0); + IERC1643.Document memory doc2 = _doc(documentEngine, anotherSmartContract, names[1]); + assertEq(doc2.uri, ""); + assertEq(doc2.documentHash, ""); + assertEq(doc2.lastModified, 0); docs = documentEngine.getAllDocuments(anotherSmartContract); assertEq(docs.length, 0); } @@ -500,24 +802,16 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { documentEngine.batchSetDocuments(testContract, names, uris, hashes); // Check the first document - ( - string memory uri1, - bytes32 hash1, - uint256 lastModified1 - ) = documentEngine.getDocument(testContract, documentName); - assertEq(uri1, documentURI); - assertEq(hash1, documentHash); - assertEq(lastModified1, block.timestamp); + IERC1643.Document memory doc1 = _doc(documentEngine, testContract, documentName); + assertEq(doc1.uri, documentURI); + assertEq(doc1.documentHash, documentHash); + assertEq(doc1.lastModified, block.timestamp); // Check the second document - ( - string memory uri2, - bytes32 hash2, - uint256 lastModified2 - ) = documentEngine.getDocument(testContract, names[1]); - assertEq(uri2, uris[1]); - assertEq(hash2, hashes[1]); - assertEq(lastModified2, block.timestamp); + IERC1643.Document memory doc2 = _doc(documentEngine, testContract, names[1]); + assertEq(doc2.uri, uris[1]); + assertEq(doc2.documentHash, hashes[1]); + assertEq(doc2.lastModified, block.timestamp); } function testAdminCanBatchRemoveDocumentsForOnlyOneContract() public { @@ -534,30 +828,216 @@ contract DocumentEngineTest is Test, DocumentEngineInvariant, AccessControl { // Check that both documents are removed // Check the second document - (string memory uri, bytes32 hash, uint256 lastModified) = documentEngine - .getDocument(testContract, documentName); - assertEq(uri, ""); - assertEq(hash, ""); - assertEq(lastModified, 0); + IERC1643.Document memory doc = _doc(documentEngine, testContract, documentName); + assertEq(doc.uri, ""); + assertEq(doc.documentHash, ""); + assertEq(doc.lastModified, 0); bytes32[] memory docs = documentEngine.getAllDocuments(testContract); assertEq(docs.length, 0); - ( - string memory uri2, - bytes32 hash2, - uint256 lastModified2 - ) = documentEngine.getDocument(testContract, names[1]); - assertEq(uri2, ""); - assertEq(hash2, ""); - assertEq(lastModified2, 0); - } - - function testCannotRemoveBatchDocumentIfEmptyLengthForOnlyOneContract() - public - { + IERC1643.Document memory doc2 = _doc(documentEngine, testContract, names[1]); + assertEq(doc2.uri, ""); + assertEq(doc2.documentHash, ""); + assertEq(doc2.lastModified, 0); + } + + function testCannotRemoveBatchDocumentIfEmptyLengthForOnlyOneContract() public { bytes32[] memory names = new bytes32[](0); vm.expectRevert(abi.encodeWithSelector(InvalidInputLength.selector)); vm.prank(admin); documentEngine.batchRemoveDocuments(testContract, names); } + + /*////////////////////////////////////////////////////////////// + Events (emission responsibility) + //////////////////////////////////////////////////////////////*/ + + function testSetDocumentEmitsForSubjectEvent() public { + bytes32 name = keccak256("evt-doc"); + vm.expectEmit(true, true, false, true, address(documentEngine)); + emit DocumentUpdatedForSubject(testContract, name, documentURI, documentHash); + vm.prank(admin); + documentEngine.setDocument(testContract, name, documentURI, documentHash); + } + + function testRemoveDocumentEmitsForSubjectEvent() public { + // `documentName` for `testContract` was registered in setUp + vm.expectEmit(true, true, false, true, address(documentEngine)); + emit DocumentRemovedForSubject(testContract, documentName, documentURI, documentHash); + vm.prank(admin); + documentEngine.removeDocument(testContract, documentName); + } + + function testSetDocumentDoesNotEmitBaseEvent() public { + vm.recordLogs(); + vm.prank(admin); + documentEngine.setDocument(testContract, keccak256("evt-doc"), documentURI, documentHash); + _assertBaseEventNotEmitted(BASE_UPDATED_SIG); + } + + function testRemoveDocumentDoesNotEmitBaseEvent() public { + vm.recordLogs(); + vm.prank(admin); + documentEngine.removeDocument(testContract, documentName); + _assertBaseEventNotEmitted(BASE_REMOVED_SIG); + } + + /// @dev Asserts no recorded log emitted by the engine carries the base ERC-1643 signature. + function _assertBaseEventNotEmitted(bytes32 baseSig) internal { + Vm.Log[] memory logs = vm.getRecordedLogs(); + for (uint256 i = 0; i < logs.length; ++i) { + if (logs[i].emitter == address(documentEngine)) { + assertTrue(logs[i].topics[0] != baseSig, "base ERC-1643 event must not be emitted"); + } + } + } + + /*////////////////////////////////////////////////////////////// + msg.sender-scoped reads (base ERC-1643) + //////////////////////////////////////////////////////////////*/ + + function testMsgSenderScopedReads() public { + // setUp registered `documentName` for `testContract`; read it as that caller + vm.prank(testContract); + IERC1643.Document memory doc = _doc(documentEngine, documentName); + assertEq(doc.uri, documentURI); + assertEq(doc.documentHash, documentHash); + + vm.prank(testContract); + bytes32[] memory names = documentEngine.getAllDocuments(); + assertEq(names.length, 1); + assertEq(names[0], documentName); + } + + function testMsgSenderScopedReadReturnsEmptyForOther() public { + // `attacker` has no documents of its own + vm.prank(attacker); + IERC1643.Document memory doc = _doc(documentEngine, documentName); + assertEq(doc.uri, ""); + assertEq(doc.documentHash, ""); + assertEq(doc.lastModified, 0); + + vm.prank(attacker); + assertEq(documentEngine.getAllDocuments().length, 0); + } + + /*////////////////////////////////////////////////////////////// + Batch edge cases (name==0 / missing doc) + //////////////////////////////////////////////////////////////*/ + + function testCannotSetDocumentForZeroSubject() public { + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IERC1643MultiDocument.MultiDocumentInvalidSubject.selector)); + documentEngine.setDocument(AddressZero, documentName, documentURI, documentHash); + } + + function testBatchSetRevertsOnZeroSubject() public { + address[] memory subjects = new address[](1); + subjects[0] = AddressZero; + bytes32[] memory names = new bytes32[](1); + names[0] = documentName; + string[] memory uris = new string[](1); + uris[0] = documentURI; + bytes32[] memory hashes = new bytes32[](1); + hashes[0] = documentHash; + + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IERC1643MultiDocument.MultiDocumentInvalidSubject.selector)); + documentEngine.batchSetDocuments(subjects, names, uris, hashes); + } + + function testBatchSetRevertsOnZeroName() public { + address[] memory subjects = new address[](1); + subjects[0] = testContract; + bytes32[] memory names = new bytes32[](1); + names[0] = bytes32(0); + string[] memory uris = new string[](1); + uris[0] = documentURI; + bytes32[] memory hashes = new bytes32[](1); + hashes[0] = documentHash; + + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IERC1643.ERC1643InvalidName.selector)); + documentEngine.batchSetDocuments(subjects, names, uris, hashes); + } + + function testBatchRemoveRevertsOnMissingDocument() public { + address[] memory subjects = new address[](1); + subjects[0] = testContract; + bytes32[] memory names = new bytes32[](1); + names[0] = keccak256("never-set"); + + vm.prank(admin); + vm.expectRevert(abi.encodeWithSelector(IERC1643.ERC1643MissingDocument.selector)); + documentEngine.batchRemoveDocuments(subjects, names); + } + + /*////////////////////////////////////////////////////////////// + Enumeration & fuzz + //////////////////////////////////////////////////////////////*/ + + function testEnumerationAfterMixedOps() public { + address subj = address(0xBEEF); + bytes32 n1 = keccak256("n1"); + bytes32 n2 = keccak256("n2"); + bytes32 n3 = keccak256("n3"); + + vm.startPrank(admin); + documentEngine.setDocument(subj, n1, "u1", bytes32(0)); + documentEngine.setDocument(subj, n2, "u2", bytes32(0)); + documentEngine.setDocument(subj, n3, "u3", bytes32(0)); + assertEq(documentEngine.getAllDocuments(subj).length, 3); + + // overwrite does not add a new entry + documentEngine.setDocument(subj, n2, "u2-updated", bytes32(0)); + assertEq(documentEngine.getAllDocuments(subj).length, 3); + + // removal shrinks the set (swap-and-pop) + documentEngine.removeDocument(subj, n2); + vm.stopPrank(); + + bytes32[] memory names = documentEngine.getAllDocuments(subj); + assertEq(names.length, 2); + assertTrue( + (names[0] == n1 && names[1] == n3) || (names[0] == n3 && names[1] == n1), + "remaining names must be n1 and n3" + ); + } + + function testFuzzSetGetRemoveRoundTrip(address subject, bytes32 name, string calldata uri, bytes32 hash) public { + vm.assume(name != bytes32(0)); // the null name reverts by design + vm.assume(subject != AddressZero); // the null subject reverts by design + + vm.prank(admin); + documentEngine.setDocument(subject, name, uri, hash); + + IERC1643.Document memory doc = _doc(documentEngine, subject, name); + assertEq(doc.uri, uri); + assertEq(doc.documentHash, hash); + assertEq(doc.lastModified, block.timestamp); + + vm.prank(admin); + documentEngine.removeDocument(subject, name); + + doc = _doc(documentEngine, subject, name); + assertEq(doc.uri, ""); + assertEq(doc.documentHash, ""); + assertEq(doc.lastModified, 0); + } + + function testFuzzDocumentsAreIsolatedPerSubject(address subjectA, address subjectB, bytes32 name) public { + vm.assume(name != bytes32(0)); + vm.assume(subjectA != subjectB); + vm.assume(subjectA != AddressZero); // the null subject reverts by design + // `testContract` is pre-populated in setUp; exclude it from the "untouched" subject + vm.assume(subjectB != testContract); + + vm.prank(admin); + documentEngine.setDocument(subjectA, name, documentURI, documentHash); + + // subjectB is unaffected + IERC1643.Document memory docB = _doc(documentEngine, subjectB, name); + assertEq(docB.lastModified, 0); + assertEq(documentEngine.getAllDocuments(subjectB).length, 0); + } } diff --git a/test/DocumentEngineOwnable.t.sol b/test/DocumentEngineOwnable.t.sol new file mode 100644 index 0000000..0cb2049 --- /dev/null +++ b/test/DocumentEngineOwnable.t.sol @@ -0,0 +1,156 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import "../src/DocumentEngineOwnable.sol"; +import {Ownable} from "OZ/access/Ownable.sol"; +import {IAccessControl} from "OZ/access/IAccessControl.sol"; +import {IERC165} from "OZ/utils/introspection/IERC165.sol"; +import {IERC8303} from "../src/interfaces/IERC8303.sol"; +import {IERC1643} from "CMTAT/interfaces/tokenization/draft-IERC1643.sol"; +import {IERC1643MultiDocument} from "../src/interfaces/IERC1643MultiDocument.sol"; +import {ITokenBinding} from "../src/interfaces/ITokenBinding.sol"; +import {TokenBindingModule} from "../src/modules/TokenBindingModule.sol"; + +contract DocumentEngineOwnableTest is Test { + DocumentEngineOwnable public engine; + address public owner = address(0x1); + address public newOwner = address(0x2); + address public attacker = address(0x3); + address private testContract = address(0x4); + bytes32 public documentName = keccak256("doc1"); + string public documentURI = "https://example.com/doc1"; + bytes32 public documentHash = keccak256("doc1Hash"); + address AddressZero = address(0); + + /** + * @dev Since CMTAT `v3.3.0-rc2`, `getDocument` returns the three ERC-1643 fields as flat + * values instead of a `Document` struct; this helper repacks them so the assertions below + * stay readable. The wire format is pinned by `DocumentEngineTest`. + */ + function _doc(IERC1643MultiDocument engine_, address subject, bytes32 name_) + internal + view + returns (IERC1643.Document memory document) + { + (document.uri, document.documentHash, document.lastModified) = engine_.getDocument(subject, name_); + } + + function setUp() public { + engine = new DocumentEngineOwnable(owner, AddressZero); + } + + /* ============ DEPLOYMENT ============ */ + + function testDeploySetsOwner() public { + assertEq(engine.owner(), owner); + } + + function testDeployRevertsWithZeroOwner() public { + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableInvalidOwner.selector, AddressZero)); + new DocumentEngineOwnable(AddressZero, AddressZero); + } + + /* ============ ADMIN PATH (owner) ============ */ + + function testOwnerCanSetAndRemoveDocument() public { + vm.prank(owner); + engine.setDocument(testContract, documentName, documentURI, documentHash); + + IERC1643.Document memory doc = _doc(engine, testContract, documentName); + assertEq(doc.uri, documentURI); + assertEq(doc.documentHash, documentHash); + assertEq(doc.lastModified, block.timestamp); + + vm.prank(owner); + engine.removeDocument(testContract, documentName); + doc = _doc(engine, testContract, documentName); + assertEq(doc.lastModified, 0); + } + + function testNonOwnerCannotSetDocument() public { + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, attacker)); + engine.setDocument(testContract, documentName, documentURI, documentHash); + } + + /* ============ BOUND-TOKEN PATH ============ */ + + function testOwnerCanBindToken() public { + vm.prank(owner); + engine.bindToken(testContract); + assertTrue(engine.isTokenBound(testContract)); + } + + function testNonOwnerCannotBindToken() public { + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, attacker)); + engine.bindToken(testContract); + } + + function testBoundTokenCanManageOwnDocument() public { + vm.prank(owner); + engine.bindToken(testContract); + + vm.prank(testContract); + engine.setDocument(documentName, documentURI, documentHash); + + IERC1643.Document memory doc = _doc(engine, testContract, documentName); + assertEq(doc.uri, documentURI); + + vm.prank(testContract); + engine.removeDocument(documentName); + doc = _doc(engine, testContract, documentName); + assertEq(doc.lastModified, 0); + } + + function testUnbindTokenRevokesSelfManagement() public { + vm.prank(owner); + engine.bindToken(testContract); + assertTrue(engine.isTokenBound(testContract)); + + vm.prank(owner); + engine.unbindToken(testContract); + assertFalse(engine.isTokenBound(testContract)); + + // once unbound, the token can no longer self-manage + vm.prank(testContract); + vm.expectRevert(abi.encodeWithSelector(TokenBindingModule.NotBoundToken.selector, testContract)); + engine.setDocument(documentName, documentURI, documentHash); + } + + function testUnboundTokenCannotSelfManage() public { + vm.prank(attacker); + vm.expectRevert(abi.encodeWithSelector(TokenBindingModule.NotBoundToken.selector, attacker)); + engine.setDocument(documentName, documentURI, documentHash); + } + + /* ============ TWO-STEP OWNERSHIP ============ */ + + function testTwoStepOwnershipTransfer() public { + vm.prank(owner); + engine.transferOwnership(newOwner); + // ownership not transferred until accepted + assertEq(engine.owner(), owner); + assertEq(engine.pendingOwner(), newOwner); + + vm.prank(newOwner); + engine.acceptOwnership(); + assertEq(engine.owner(), newOwner); + assertEq(engine.pendingOwner(), AddressZero); + } + + /* ============ VERSION (ERC-8303) ============ */ + + function testVersionAndInterface() public { + assertEq(engine.version(), "0.4.0"); + assertTrue(engine.supportsInterface(type(IERC8303).interfaceId)); + assertTrue(engine.supportsInterface(type(IERC165).interfaceId)); + assertTrue(engine.supportsInterface(type(IERC1643).interfaceId)); + assertTrue(engine.supportsInterface(type(IERC1643MultiDocument).interfaceId)); + assertTrue(engine.supportsInterface(type(ITokenBinding).interfaceId)); + // no role-based access control here + assertFalse(engine.supportsInterface(type(IAccessControl).interfaceId)); + assertFalse(engine.supportsInterface(bytes4(0xffffffff))); + } +}