Skip to content

Commit 84a3082

Browse files
committed
docs: document the ERC-3643 integration surface (compliance slot, identity registry, spec semantics, real-token tests) in both READMEs
1 parent 34cdb0b commit 84a3082

2 files changed

Lines changed: 94 additions & 1 deletion

File tree

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,59 @@ access-control policy, in either an `AccessControl` or an `Ownable2Step` flavour
8181
Codes must stay unique across rules, since a RuleEngine returns the first non-zero one.
8282
Per-rule detail is in [`doc/technical/`](./doc/technical/); the semantics that differ between rules (who is screened, mint/burn handling, unset-oracle behaviour) are tabulated in [`RULE_SEMANTICS.md`](./doc/technical/RULE_SEMANTICS.md).
8383

84+
## ERC-3643
85+
86+
An [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643) token has **two** pluggable slots, and this library fills both, from opposite directions.
87+
88+
| Slot | Filled with | Direction |
89+
| --- | --- | --- |
90+
| **Compliance** (`ICompliance`) | A `RuleEngine` holding rules | The token asks the rules whether a transfer may proceed |
91+
| **Identity registry** (`IIdentityRegistry`) | `IdentityRegistryWhitelist` | The token asks it whether a wallet is a verified investor |
92+
93+
### Compliance: go through a RuleEngine
94+
95+
Use `RuleEngine`, not a bare rule. ERC-3643 drives mint and burn through `created` and `destroyed`, which the **validation rules do not implement** — they only expose `canTransfer` / `transferred`.
96+
97+
`RuleEngine` implements the full `ICompliance` surface and forwards to the rules, so it is the supported path.
98+
99+
The operation rules do implement `created` / `destroyed`, but they are bound to a single token and are not a compliance contract on
100+
their own.
101+
102+
### Identity verification
103+
104+
ERC-3643 decides who may hold a token by asking an **identity registry** one question:
105+
`isVerified(wallet)` — is this a verified investor? A normal registry answers it by checking the wallet's
106+
on-chain identity contract (ONCHAINID) for the required claims.
107+
108+
This library provides **both sides of that exchange**, which is the part worth getting straight:
109+
110+
| Contract | What it is | Where it plugs in |
111+
| --- | --- | --- |
112+
| `RuleIdentityRegistry` | The side that **asks the question**: a transfer rule that calls `isVerified` on whatever registry the token uses, and blocks the transfer when the answer is no. | Added to a RuleEngine, like any other rule |
113+
| `IdentityRegistryWhitelist` | The side that **answers it**: a registry implementation that replies from a whitelist instead of reading ONCHAINIDs, so no identity contracts need deploying. | `token.setIdentityRegistry(...)`. It is **not** a rule, implements no `IRule`, and must never be added to a RuleEngine |
114+
115+
Pick by whichever half you are missing. Already operate an identity registry and want its verdict enforced on
116+
transfers? You need the rule. Want ERC-3643 eligibility without the ONCHAINID machinery? You need the registry.
117+
118+
They are independent, and they also **compose**: the rule can consult the whitelist-backed registry, so one
119+
whitelist drives both the token's own eligibility checks and the transfer rule. That pairing is covered by
120+
`test/IdentityRegistryWhitelist/CMTATRuleIdentityRegistryComposition.t.sol`.
121+
122+
### Matching the spec's semantics
123+
124+
`RuleReceiverWhitelist` reproduces ERC-3643 eligibility exactly: **only the receiver** is screened. The spec
125+
checks the receiver alone on purpose, so a de-listed holder can still exit a position; screening the sender
126+
would trap them. `RuleIdentityRegistry` follows the same default, with sender and spender checks available as
127+
explicit opt-ins.
128+
129+
Every rule and the registry implement `IERC3643Version`, so `version()` is queryable on-chain.
130+
131+
### Tested against a real ERC-3643 token
132+
133+
`test/ERC3643Real/` runs against the actual ERC-3643 `Token.sol` and `IdentityRegistry.sol`, not mocks: the
134+
RuleEngine integration, the identity rule against a real registry, and receiver-whitelist parity with the
135+
spec's eligibility. 31 tests, run with `FOUNDRY_PROFILE=erc3643 forge test`.
136+
84137
## Quick start
85138

86139
```bash

doc/README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function canTransfer(address _from, address _to, uint256 _amount) external view
8989
function transferred(address _from, address _to, uint256 _amount) external;
9090
```
9191

92-
However, contrary to the RuleEngine, the whole interface is currently not implemented (e.g. `created` and `destroyed`) and as a result, the rule cannot directly support ERC-3643 token.
92+
However, contrary to the RuleEngine, the whole interface is not implemented: the **validation rules** do not declare `created` and `destroyed`, so a validation rule cannot back an ERC-3643 token on its own. (The operation rules — `RuleConditionalTransferLight`, `…MultiToken` and `RuleMintAllowance` — do implement both, but each is bound to a single token and is not a general compliance contract.)
9393

9494
The alternative to use a Rule with an ERC-3643 token is through the RuleEngine, which implements the whole `ICompliance` interface.
9595

@@ -99,6 +99,46 @@ The diagram below shows the recommended integration: the ERC-3643 token drives t
9999

100100
_Diagram source: doc/img/readme-erc3643-integration.puml._
101101

102+
#### The identity registry slot
103+
104+
An ERC-3643 token has a **second** pluggable slot besides compliance, and this library fills it too. Two
105+
contracts face the registry from opposite directions:
106+
107+
| Contract | Relationship | Installed with | Use when |
108+
| --- | --- | --- | --- |
109+
| `RuleIdentityRegistry` | **Consults** a registry, calling `isVerified` | Added to a RuleEngine like any rule | You already operate an ERC-3643 identity registry with ONCHAINIDs |
110+
| [`IdentityRegistryWhitelist`](./technical/IdentityRegistryWhitelist.md) | **Is** a registry (implements `IIdentityRegistryERC3643`) | `token.setIdentityRegistry(...)` | You want ERC-3643 eligibility without deploying ONCHAINIDs |
111+
112+
`IdentityRegistryWhitelist` answers `isVerified` from a whitelist and keeps **no identity state**: `_identity`
113+
and `_country` are accepted for signature compatibility then discarded. It is not a rule, implements no
114+
`IRule`, and must never be added to a RuleEngine. The token itself must hold `IDENTITY_REGISTRAR_ROLE`.
115+
116+
#### Matching the spec's screening semantics
117+
118+
ERC-3643 requires that **only the receiver** be verified: `transferFrom` works the same way, `mint` and
119+
`forcedTransfer` check only the receiver, and `burn` bypasses eligibility entirely. That asymmetry is
120+
deliberate — screening the sender would trap a de-listed holder in their position.
121+
122+
- [`RuleReceiverWhitelist`](./technical/RuleReceiverWhitelist.md) reproduces this exactly (code `81`).
123+
- [`RuleIdentityRegistry`](./technical/RuleIdentityRegistry.md) defaults to the same behaviour, with
124+
`checkSender` / `checkSpender` available as explicit opt-ins, both `false` by default.
125+
126+
Every rule and the registry implement `IERC3643Version`, so `version()` is queryable on-chain.
127+
128+
#### Tested against a real ERC-3643 token
129+
130+
[`test/ERC3643Real/`](../test/ERC3643Real/) exercises the integration against the actual ERC-3643 `Token.sol`
131+
and `IdentityRegistry.sol` rather than mocks:
132+
133+
| Suite | Covers |
134+
| --- | --- |
135+
| `ERC3643RealTokenRuleEngine.t.sol` | A real ERC-3643 token driving rules through a RuleEngine |
136+
| `RuleIdentityRegistryWithRealERC3643Registry.t.sol` | The identity rule against a real `IdentityRegistry` |
137+
| `ERC3643ReceiverWhitelistParity.t.sol` | Receiver-whitelist parity with the spec's eligibility |
138+
139+
31 tests, run with `FOUNDRY_PROFILE=erc3643 forge test`. They are **not** part of a plain `forge test`: the
140+
vendored `Token.sol` pins solc `0.8.30` exactly and cannot share a compilation unit with our `0.8.34`.
141+
102142
### ERC-721/ERC-1155
103143

104144
To improve compatibility with [ERC-721](https://eips.ethereum.org/EIPS/eip-721) and [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155), most validation rules implement the interface `IERC7943NonFungibleComplianceExtend` which includes compliance functions with the `tokenId` argument.

0 commit comments

Comments
 (0)