Skip to content
Merged

Dev #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Project Guide

## Purpose
Modular compliance-rule library for CMTAT / ERC-3643 security tokens. Each rule enforces a transfer restriction (whitelist, blacklist, sanctions, max supply, identity, conditional approval) and can be used standalone or composed via a `RuleEngine`.
Modular compliance-rule library for CMTAT / ERC-3643 security tokens. Each rule enforces a transfer restriction (whitelist, spender whitelist, blacklist, sanctions, max supply, identity, conditional approval) and can be used standalone or composed via a `RuleEngine`.

## Key Directories
| Path | Description |
Expand All @@ -23,6 +23,7 @@ Modular compliance-rule library for CMTAT / ERC-3643 security tokens. Each rule
| `RuleSanctionsList` | Block sanctioned addresses via Chainalysis oracle |
| `RuleMaxTotalSupply` | Cap minting so total supply never exceeds a maximum |
| `RuleIdentityRegistry` | Check ERC-3643 identity registry for participant verification |
| `RuleSpenderWhitelist` / `RuleSpenderWhitelistOwnable2Step` | Allow `transferFrom` only when spender is whitelisted; direct transfers are always allowed |
| `RuleERC2980` | ERC-2980 Swiss Compliant rule: whitelist (recipient-only) + frozenlist (blocks sender and recipient); frozenlist takes priority |
| `RuleERC2980Ownable2Step` | Ownable2Step variant of RuleERC2980 |
| `RuleConditionalTransferLight` | Require operator approval before each transfer |
Expand Down Expand Up @@ -57,6 +58,7 @@ Foundry config: `foundry.toml` (solc 0.8.34, EVM prague, optimizer 200 runs).
| RuleMaxTotalSupply | 50 |
| RuleIdentityRegistry | 55–57 |
| RuleERC2980 | 60–63 |
| RuleSpenderWhitelist | 66 |

## Conventions
- Each rule has an `InvariantStorage` abstract contract holding its constants, custom errors, and events.
Expand All @@ -67,7 +69,8 @@ Foundry config: `foundry.toml` (solc 0.8.34, EVM prague, optimizer 200 runs).
- Batch add/remove operations are non-reverting (skip duplicates); single-item operations revert on invalid input.
- All `internal` functions should be marked `virtual`.
- Do not create git commits; provide commit messages only when requested.
- Always run tests after modifying contracts.
- Always run full tests (`forge test`) after any code modification, including lint-driven or mechanical refactors, before reporting completion.
- Use `require(condition, CustomError(...))` for custom errors; avoid direct `revert CustomError(...)`.
- `AGENTS.md` and `CLAUDE.md` are identical — always update both together.
- Always update README.md with the latest change
- New rule or features implemented: create/update technical documentation in `doc/technical`, update README, create/update test (target: 100% of code coverage), update CHANGELOG.md. Code coverage, run `forge coverage --report summary`
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ Commit: `TBD`

### Added

- `RuleSpenderWhitelist` — validation rule that blocks `transferFrom` when spender is not listed; direct transfers are always allowed. Restriction code 66.
- `RuleSpenderWhitelistOwnable2Step` — Ownable2Step variant of `RuleSpenderWhitelist`.
- Technical documentation file `doc/technical/RuleSpenderWhitelist.md`.
- Transfer-context mocks in `src/mocks`: `MockERC20WithTransferContext` and `MockERC721WithTransferContext`.
- Transfer-context mocks in `src/mocks` now inherit OpenZeppelin `ERC20` / `ERC721` and emit rule callbacks through `ITransferContext`.
- Transfer-context tests for ERC-20/ERC-721 mock integration in `test/TransferContext/TransferContextMocks.t.sol`.
- `RuleERC2980` — ERC-2980 Swiss Compliant rule combining a whitelist (recipient-only) and a frozenlist (blocks sender, recipient, and spender); frozenlist takes priority. Restriction codes 60–63.
- `RuleERC2980Ownable2Step` — Ownable2Step variant of `RuleERC2980`.
- `IERC2980` interface with NatSpec documenting the deviation from the ERC example interfaces (single-item functions revert on invalid input rather than returning `bool`).
Expand All @@ -72,6 +78,8 @@ Commit: `TBD`
- `RuleConditionalTransferLight` and `RuleMaxTotalSupply` are ERC-20 only; ERC-721/1155 compliance interfaces are limited to validation rules.
- Address list batch updates emit only add/remove events (no summary events).
- Reorganized validation contracts into `abstract/base`, `abstract/core`, `abstract/invariant`, and `deployment` folders.
- Rule transfer-context dispatch now treats `sender == from` as direct transfer (non-spender path) in `RuleNFTAdapter`.
- Concrete utilities and harness contracts used by tests were moved from `test/` into `src/mocks` and `src/mocks/harness`.

### Dependencies

Expand Down
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Project Guide

## Purpose
Modular compliance-rule library for CMTAT / ERC-3643 security tokens. Each rule enforces a transfer restriction (whitelist, blacklist, sanctions, max supply, identity, conditional approval) and can be used standalone or composed via a `RuleEngine`.
Modular compliance-rule library for CMTAT / ERC-3643 security tokens. Each rule enforces a transfer restriction (whitelist, spender whitelist, blacklist, sanctions, max supply, identity, conditional approval) and can be used standalone or composed via a `RuleEngine`.

## Key Directories
| Path | Description |
Expand All @@ -23,6 +23,7 @@ Modular compliance-rule library for CMTAT / ERC-3643 security tokens. Each rule
| `RuleSanctionsList` | Block sanctioned addresses via Chainalysis oracle |
| `RuleMaxTotalSupply` | Cap minting so total supply never exceeds a maximum |
| `RuleIdentityRegistry` | Check ERC-3643 identity registry for participant verification |
| `RuleSpenderWhitelist` / `RuleSpenderWhitelistOwnable2Step` | Allow `transferFrom` only when spender is whitelisted; direct transfers are always allowed |
| `RuleERC2980` | ERC-2980 Swiss Compliant rule: whitelist (recipient-only) + frozenlist (blocks sender and recipient); frozenlist takes priority |
| `RuleERC2980Ownable2Step` | Ownable2Step variant of RuleERC2980 |
| `RuleConditionalTransferLight` | Require operator approval before each transfer |
Expand Down Expand Up @@ -57,16 +58,19 @@ Foundry config: `foundry.toml` (solc 0.8.34, EVM prague, optimizer 200 runs).
| RuleMaxTotalSupply | 50 |
| RuleIdentityRegistry | 55–57 |
| RuleERC2980 | 60–63 |
| RuleSpenderWhitelist | 66 |

## Conventions
- Each rule has an `InvariantStorage` abstract contract holding its constants, custom errors, and events.
- Access control is implemented via an abstract `_authorize*()` method overridden by concrete subclasses (template method pattern).
- AccessControl variants must use `onlyRole(ROLE)` in `_authorize*()` methods (avoid direct `_checkRole`).
- AccessControl variants treat the default admin as having all roles via `hasRole`, but the admin may not appear in role member enumerations unless explicitly granted.
- All rules implement `IERC3643Version` via `VersionModule`; the current version string is `"0.2.0"`.
- Batch add/remove operations are non-reverting (skip duplicates); single-item operations revert on invalid input.
- All `internal` functions should be marked `virtual`.
- Do not create git commits; provide commit messages only when requested.
- Always run tests after modifying contracts.
- Always run full tests (`forge test`) after any code modification, including lint-driven or mechanical refactors, before reporting completion.
- Use `require(condition, CustomError(...))` for custom errors; avoid direct `revert CustomError(...)`.
- `AGENTS.md` and `CLAUDE.md` are identical — always update both together.
- Always update README.md with the latest change
- New rule or features implemented: create/update technical documentation in `doc/technical`, update README, create/update test (target: 100% of code coverage), update CHANGELOG.md. Code coverage, run `forge coverage --report summary`
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Each rule can be used **standalone**, directly plugged into a CMTAT token, **or*

**Status:** *Repository under active development*

Latest update: completed 100% coverage across `src/` contracts, including direct coverage for `RuleAddressSet.contains(address)`, mint/burn-path coverage for `RuleConditionalTransferLightBase.detectTransferRestriction(...)`, and removal of an unreachable duplicate admin-zero check in `RuleConditionalTransferLight` constructor.
Latest update: transfer-context token mocks now use OpenZeppelin ERC-20/ERC-721; test utilities and harness contracts were moved from `test/` to `src/mocks` and `src/mocks/harness`; context spender handling remains `sender == from` => direct transfer; audit L-8 fixed by removing an unused conditional-transfer custom error.

## Schema

Expand Down Expand Up @@ -169,7 +169,9 @@ Here the list of codes used by the different rules
| | CODE_ADDRESS_TO_IS_FROZEN | 61 |
| | CODE_ADDRESS_SPENDER_IS_FROZEN | 62 |
| | CODE_ADDRESS_TO_NOT_WHITELISTED | 63 |
| | Reserved slot | 64-65 |
| | Reserved slot | 64-65 |
| RuleSpenderWhitelist | CODE_ADDRESS_SPENDER_NOT_WHITELISTED | 66 |
| | Reserved slot | 67-70 |

Note:

Expand Down Expand Up @@ -304,7 +306,7 @@ Validation rules only read blockchain state — they never modify it during a tr

All validation rules implement `IRuleEngine` to be usable both standalone (plugged directly into CMTAT) and via the RuleEngine.

Available validation rules: `RuleWhitelist`, `RuleWhitelistWrapper`, `RuleBlacklist`, `RuleSanctionsList`, `RuleMaxTotalSupply`, `RuleIdentityRegistry`, `RuleERC2980`.
Available validation rules: `RuleWhitelist`, `RuleWhitelistWrapper`, `RuleSpenderWhitelist`, `RuleBlacklist`, `RuleSanctionsList`, `RuleMaxTotalSupply`, `RuleIdentityRegistry`, `RuleERC2980`.

A community made project, [RuleSelf](https://github.com/rya-sge/ruleself), which uses [Self](https://self.xyz), a zero-knowledge identity is also available but is not developed or maintained by CMTA.

Expand Down Expand Up @@ -342,6 +344,7 @@ Several rules are available in multiple access-control variants. Use the simples
- Examples:
- Whitelist
- Whitelist Wrapper
- Spender Whitelist
- Blacklist
- Sanction list (Chainalysis)
- ERC-2980 (whitelist + frozenlist)
Expand All @@ -364,6 +367,7 @@ Several rules are available in multiple access-control variants. Use the simples
| RuleSanctionList | Read-Only | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | The purpose of this contract is to use the oracle contract from [Chainalysis](https://go.chainalysis.com/chainalysis-oracle-docs.html) to forbid transfer from/to an address included in a sanctions designation (US, EU, or UN). |
| RuleMaxTotalSupply | Read-Only | <strong><span style="color: #b00020;">&#x2718;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | This rule limits minting so that the total supply never exceeds a configured maximum. |
| RuleIdentityRegistry | Read-Only | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | This rule checks the ERC-3643 Identity Registry for transfer participants when configured. |
| RuleSpenderWhitelist | Read-Only | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | This rule blocks `transferFrom` when the spender is not in the whitelist. Direct transfers are always allowed. |
| RuleERC2980 | Read-Only | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | ERC-2980 Swiss Compliant rule combining a whitelist (recipient-only) and a frozenlist (blocks both sender and recipient). Frozenlist takes priority over whitelist. |
| RuleConditionalTransferLight | Read-Write | <strong><span style="color: #b00020;">&#x2718;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | This rule requires that transfers have to be approved by an operator before being executed. Each approval is consumed once and the same transfer can be approved multiple times. |
| [RuleConditionalTransfer](https://github.com/CMTA/RuleConditionalTransfer) (external) | Read-Write | <strong><span style="color: #b00020;">&#x2718;</span></strong> | <strong><span style="color: #1e7e34;">&#x2714;</span></strong> | <strong><span style="color: #b00020;">&#x2718;</span></strong><br /> (experimental rule) | Full-featured approval-based transfer rule implementing Swiss law *Vinkulierung*. Supports automatic approval after three months, automatic transfer execution, and a conditional whitelist for address pairs that bypass approval. Maintained in a separate repository. |
Expand All @@ -383,6 +387,7 @@ Detailed technical documentation for each rule is available in [`doc/technical/`
| RuleSanctionsList | [RuleSanctionList.md](./doc/technical/RuleSanctionList.md) |
| RuleMaxTotalSupply | [RuleMaxTotalSupply.md](./doc/technical/RuleMaxTotalSupply.md) |
| RuleIdentityRegistry | [RuleIdentityRegistry.md](./doc/technical/RuleIdentityRegistry.md) |
| RuleSpenderWhitelist | [RuleSpenderWhitelist.md](./doc/technical/RuleSpenderWhitelist.md) |
| RuleERC2980 | [RuleERC2980.md](./doc/technical/RuleERC2980.md) |
| RuleConditionalTransferLight | [RuleConditionalTransferLight.md](./doc/technical/RuleConditionalTransferLight.md) |

Expand All @@ -395,6 +400,7 @@ Detailed technical documentation for each rule is available in [`doc/technical/`
- `RuleMaxTotalSupply` trusts the configured `tokenContract` to return an accurate `totalSupply()`.
- `RuleMaxTotalSupply` does not allow clearing the token contract; disable the rule by removing it from the RuleEngine or token.
- `RuleWhitelistWrapper` requires child rules that implement `IAddressList`. Gas cost grows with the number of rules, and a wrapper with zero rules will reject all transfers.
- `RuleSpenderWhitelist` only checks the spender in `transferFrom`; direct transfers always pass this rule.
- Read-only rules still implement `transferred()` to comply with ERC-3643 and RuleEngine interfaces, but they do not change state.
- `RuleConditionalTransferLight` approvals are keyed by `(from, to, value)` and are not nonce-based.
- `RuleConditionalTransferLight` provides `approveAndTransferIfAllowed` to approve and immediately execute `transferFrom` when this rule has allowance; it assumes the token calls back `transferred()` during the transfer.
Expand All @@ -409,7 +415,7 @@ Detailed technical documentation for each rule is available in [`doc/technical/`

### Read-only (validation) rule

Currently, there are seven validation rules: whitelist, whitelistWrapper, blacklist, sanctionlist, max total supply, identity registry, and ERC-2980.
Currently, there are eight validation rules: whitelist, whitelistWrapper, spender whitelist, blacklist, sanctionlist, max total supply, identity registry, and ERC-2980.

#### Whitelist

Expand All @@ -431,6 +437,20 @@ An operator configures CMTAT to use `RuleWhitelist`. The issuer tries to mint to

![surya_inheritance_RuleWhitelist.sol](./doc/surya/surya_inheritance/surya_inheritance_RuleWhitelist.sol.png)

#### Spender whitelist

This rule only checks `transferFrom` spender authorization:

- Direct transfers (`transfer`) are always allowed by this rule.
- `transferFrom` is rejected when `spender` is not listed.
- Restriction code: `66` (`CODE_ADDRESS_SPENDER_NOT_WHITELISTED`).

**Usage scenario**

The operator deploys `RuleSpenderWhitelist` and sets it in the token or `RuleEngine`. Alice calls `transfer` to Bob and it passes this rule. Bob then tries `transferFrom(Alice, Bob, amount)` and it is rejected until the operator calls `addAddress(Bob)` (or whichever spender account should be authorized).

![surya_inheritance_RuleSpenderWhitelist.sol](./doc/surya/surya_inheritance/surya_inheritance_RuleSpenderWhitelist.sol.png)

#### Whitelist wrapper

Allows independent whitelist groups managed by different operators.
Expand Down Expand Up @@ -1639,11 +1659,12 @@ Static analysis was performed with [Slither](https://github.com/crytic/slither).
|---|---|---|---|
| arbitrary-send-erc20 | High | 1 | False positive — `from` is guarded by `onlyTransferApprover`, ERC-20 allowance check, and a pre-recorded approval |
| unused-return | Medium | 6 | False positive — existence pre-checked at public layer before calling internal helper |
| calls-loop | Low | 15 | Acknowledged — by design; wrapper must query each child rule; child rules are read-only |
| dead-code | Informational | 14 | False positive — `_msgData()` overrides required for ERC-2771 diamond resolution; `_transferred` override is reachable |
| calls-loop | Low | 16 | Acknowledged — by design; wrapper must query each child rule; child rules are read-only |
| assembly | Informational | 1 | Acknowledged — intentional gas optimisation in `_transferHash`; minimal and well-scoped |
| missing-inheritance | Informational | 1 | Acknowledged — `TotalSupplyMock` is a test-only mock; strict interface declaration unnecessary |
| naming-convention | Informational | 2 | Acknowledged — parameter names match ERC-2980 spec |
| unindexed-event-address | Informational | 2 | Out of scope (both in `lib/RuleEngine`); `IAddressList` events previously fixed |
| unused-state | Informational | 48 | False positive — `RuleNFTAdapter` constants used in base dispatch logic; Slither per-contract analysis limitation |
| unused-state | Informational | 60 | False positive — `RuleNFTAdapter` constants used in base dispatch logic; Slither per-contract analysis limitation |

## Intellectual property

Expand Down
15 changes: 1 addition & 14 deletions doc/TOOLCHAIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

[TOC]



## Node.JS package

This part describe the list of libraries present in the file `package.json`.
Expand All @@ -16,23 +14,12 @@ This section concerns the packages installed in the section `devDependencies` of

[Hardhat](https://hardhat.org/) plugin for integration with Foundry

**[Ethlint](https://github.com/duaraghav8/Ethlint)**
Solidity static analyzer.

**[prettier-plugin-solidity](https://github.com/prettier-solidity/prettier-plugin-solidity)**

A [Prettier plugin](https://prettier.io/docs/en/plugins.html) for automatically formatting your [Solidity](https://github.com/ethereum/solidity) code.

#### Documentation

**[sol2uml](https://github.com/naddison36/sol2uml)**

Generate UML for smart contracts

**[solidity-docgen](https://github.com/OpenZeppelin/solidity-docgen)**

Program that extracts documentation for a Solidity project.

**[Surya](https://github.com/ConsenSys/surya)**

Utility tool for smart contract systems.
Expand Down Expand Up @@ -116,7 +103,7 @@ npm run-script surya:report
>Slither is a Solidity static analysis framework written in Python3

```bash
slither . --checklist --filter-paths "openzeppelin-contracts|test|CMTAT|forge-std" > slither-report.md
slither . --checklist --filter-paths "openzeppelin-contracts|test|mocks|CMTAT|forge-std" > slither-report.md
```


Expand Down
Loading