You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+53Lines changed: 53 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -81,6 +81,59 @@ access-control policy, in either an `AccessControl` or an `Ownable2Step` flavour
81
81
Codes must stay unique across rules, since a RuleEngine returns the first non-zero one.
82
82
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).
83
83
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
function transferred(address _from, address _to, uint256 _amount) external;
90
90
```
91
91
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.)
93
93
94
94
The alternative to use a Rule with an ERC-3643 token is through the RuleEngine, which implements the whole `ICompliance` interface.
95
95
@@ -99,6 +99,46 @@ The diagram below shows the recommended integration: the ERC-3643 token drives t
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
+
102
142
### ERC-721/ERC-1155
103
143
104
144
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