Skip to content

Commit 040926e

Browse files
authored
docs(protocol-contracts): write first integration doc for wrappers (#1647)
1 parent b116634 commit 040926e

File tree

2 files changed

+2123
-750
lines changed

2 files changed

+2123
-750
lines changed
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# Confidential Wrapper
2+
3+
The **Confidential Wrapper** is a smart contract that wraps standard ERC-20 tokens into confidential ERC-7984 tokens. Built on Zama's FHEVM, it enables privacy-preserving token transfers where balances and transfer amounts remain encrypted.
4+
5+
## Terminology
6+
7+
- **Confidential Token**: The ERC-7984 confidential token wrapper.
8+
- **Underlying Token**: The standard ERC-20 token wrapped by the confidential wrapper.
9+
- **Wrapping**: Converting ERC-20 tokens into confidential tokens.
10+
- **Unwrapping**: Converting confidential tokens back into ERC-20 tokens.
11+
- **Rate**: The conversion ratio between underlying token units and confidential token units (due to decimal differences).
12+
- **Operator**: An address authorized to transfer confidential tokens on behalf of another address.
13+
- **Owner**: The owner of the wrapper contract. In the FHEVM protocol, this is initially set to a DAO governance contract handled by Zama. Ownership will then be transferred to the underlying token's owner.
14+
- **Registry**: The registry contract that maps ERC-20 tokens to their corresponding confidential wrappers. More information [here](../../confidential-token-wrappers-registry/docs/README.md).
15+
- **ACL**: The ACL contract that manages the ACL permissions for encrypted amounts. More information in the [FHEVM library documentation](https://docs.zama.org/protocol/protocol/overview/library#access-control).
16+
- **Input proof**: A proof that the encrypted amount is valid. More information in the [`relayer-sdk` documentation](https://docs.zama.org/protocol/relayer-sdk-guides/fhevm-relayer/input).
17+
- **Public decryption**: A request to publicly decrypt an encrypted amount. More information in the [`relayer-sdk` documentation](https://docs.zama.org/protocol/relayer-sdk-guides/fhevm-relayer/decryption/public-decryption).
18+
19+
## Quick Start
20+
21+
> ⚠️ **Decimal conversion:** The wrapper enforces a maximum of **6 decimals** for the confidential token. When wrapping, amounts are rounded down and excess tokens are refunded.
22+
23+
> ⚠️ **Unsupported tokens:** Non-standard tokens such as fee-on-transfer or any deflationary-type tokens are NOT supported.
24+
25+
### Get the confidential wrapper address of an ERC-20 token
26+
27+
Zama provides a registry contract that maps ERC-20 tokens to their corresponding verified confidential wrappers. Make sure to check the registry contract to ensure the confidential wrapper is valid before wrapping. More information [here](../../confidential-token-wrappers-registry/docs/README.md).
28+
29+
### Wrap ERC-20 → Confidential Token
30+
31+
**Important:** Prior to wrapping, the confidential wrapper contract must be approved by the `msg.sender` on the underlying token.
32+
33+
```solidity
34+
wrapper.wrap(to, amount);
35+
```
36+
37+
The wrapper will mint the corresponding confidential token to the `to` address and refund the excess tokens to the `msg.sender` (due to decimal conversion). Considerations:
38+
- `amount` must be a value using the same decimal precision as the underlying token.
39+
- `to` must not be the zero address.
40+
41+
> ℹ️ **Low amount handling:** If the amount is less than the rate, the wrapping will succeed but the recipient will receive 0 confidential tokens and the excess tokens will be refunded to the `msg.sender`.
42+
43+
44+
### Unwrap Confidential Token → ERC-20
45+
46+
Unwrapping is a **two-step asynchronous process**: an `unwrap` must be first made and then finalized with `finalizeUnwrap`. The `unwrap` function can be called with or without an input proof.
47+
48+
#### 1) Unwrap request
49+
50+
> ⚠️ **Unsupported `from`:** Accounts with a zero balance that have never held tokens cannot be the `from` address in unwrap requests.
51+
52+
##### With input proof
53+
54+
> ℹ️ **Input proof:** To unwrap any amount of confidential tokens, the `from` address must first create an encrypted input to generate an `encryptedAmount` (`externalEuint64`) along its `inputProof`. The amount to be encrypted must use the same decimal precision as the confidential wrapper. More information in the [`relayer-sdk` documentation](https://docs.zama.org/protocol/relayer-sdk-guides/fhevm-relayer/input).
55+
56+
```solidity
57+
wrapper.unwrap(from, to, encryptedAmount, inputProof);
58+
```
59+
60+
Alternatively, an unwrap request can be made without an input proof if the encrypted amount (`euint64`) is known to `from`. For example, this can be the confidential balance of `from`.
61+
62+
This requests an unwrap request of `encryptedAmount` confidential tokens from `from`. Considerations:
63+
- `msg.sender` must be `from` or an approved operator for `from`.
64+
- `from` mut not be the zero address.
65+
- `encryptedAmount` will be burned in the request.
66+
- **NO** transfer of underlying tokens is made in this request.
67+
68+
69+
It emits an `UnwrapRequested` event:
70+
```solidity
71+
event UnwrapRequested(address indexed receiver, euint64 amount);
72+
```
73+
74+
###### Without input proof
75+
76+
Alternatively, an unwrap request can be made without an input proof if the encrypted amount (`euint64`) is known to `from`. For example, this can be the confidential balance of `from`.
77+
78+
```solidity
79+
wrapper.unwrap(from, to, encryptedAmount);
80+
```
81+
82+
On top of the above unwrap request considerations:
83+
- `msg.sender` must be approved by ACL for the given `encryptedAmount` ⚠️ (see [ACL documentation](https://docs.zama.org/protocol/protocol/overview/library#access-control)).
84+
85+
86+
#### 2) Finalize unwrap
87+
88+
> ℹ️ **Public decryption:** The encrypted burned amount `burntAmount` emitted by the `UnwrapRequested` event must be publicly decrypted to get the `cleartextAmount` along its `decryptionProof`. More information in the [`relayer-sdk` documentation](https://docs.zama.org/protocol/relayer-sdk-guides/fhevm-relayer/decryption/public-decryption).
89+
90+
```solidity
91+
wrapper.finalizeUnwrap(burntAmount, cleartextAmount, decryptionProof);
92+
```
93+
94+
This finalizes the unwrap request by sending the corresponding amount of underlying tokens to the `to` defined in the `unwrap` request.
95+
96+
### Transfer confidential tokens
97+
98+
> ℹ️ **Transfer with input proof:** Similarly to the unwrap process, transfers can be made with or without an input proof and the encrypted amount must be approved by the ACL for the `msg.sender`.
99+
100+
> ⚠️ **Unsupported `from`:** Accounts with a zero balance that have never held tokens cannot be the `from` address in confidential transfers.
101+
102+
#### Direct transfer
103+
104+
```solidity
105+
token.confidentialTransfer(to, encryptedAmount, inputProof);
106+
107+
token.confidentialTransfer(to, encryptedAmount);
108+
```
109+
110+
#### Operator-based transfer
111+
112+
```solidity
113+
token.confidentialTransferFrom(from, to, encryptedAmount, inputProof);
114+
115+
token.confidentialTransferFrom(from, to, encryptedAmount);
116+
```
117+
118+
Considerations:
119+
- `msg.sender` must be `from` or an approved operator for `from`.
120+
121+
#### Transfer with callback
122+
123+
The callback can be used along an ERC-7984 receiver contract.
124+
125+
```solidity
126+
token.confidentialTransferAndCall(to, encryptedAmount, inputProof, callbackData);
127+
128+
token.confidentialTransferAndCall(to, encryptedAmount, callbackData);
129+
```
130+
131+
#### Operator-based transfer with callback
132+
133+
The callback can be used along an ERC-7984 receiver contract.
134+
135+
```solidity
136+
token.confidentialTransferFromAndCall(from, to, encryptedAmount, inputProof, callbackData);
137+
138+
token.confidentialTransferFromAndCall(from, to, encryptedAmount, callbackData);
139+
```
140+
141+
Considerations:
142+
- `msg.sender` must be `from` or an approved operator for `from`.
143+
144+
### Check the conversion rate and decimals
145+
146+
```solidity
147+
uint256 conversionRate = wrapper.rate();
148+
uint8 wrapperDecimals = wrapper.decimals();
149+
```
150+
151+
**Examples:**
152+
| Underlying Decimals | Wrapper Decimals | Rate | Effect |
153+
|---------------------|------------------|------|--------|
154+
| 18 | 6 | 10^12 | 1 wrapped = 10^12 underlying |
155+
| 6 | 6 | 1 | 1:1 mapping |
156+
| 2 | 2 | 1 | 1:1 mapping |
157+
158+
### Check supplies
159+
160+
#### Non-confidential total supply
161+
162+
The wrapper exposes a non-confidential view of the total supply, computed from the underlying ERC20 balance held by the wrapper contract. This value may be higher than `confidentialTotalSupply()` if tokens are sent directly to the wrapper outside of the wrapping process.
163+
164+
> ℹ️ **Total Value Shielded (TVS):** This view function is useful for getting a good approximation of the wrapper's Total Value Shielded (TVS).
165+
166+
```solidity
167+
uint256 nonConfidentialSupply = wrapper.totalSupply();
168+
```
169+
170+
#### Encrypted (confidential) total supply
171+
172+
The actual supply tracked by the confidential token contract, represented as an encrypted value. To determine the cleartext value, you need to request decryption and appropriate ACL authorization.
173+
174+
```solidity
175+
euint64 encryptedSupply = wrapper.confidentialTotalSupply();
176+
```
177+
178+
#### Maximum total supply
179+
180+
The maximum number of wrapped tokens supported by the encrypted datatype (uint64 limit). If this maximum is exceeded, wrapping new tokens will revert.
181+
182+
```solidity
183+
uint256 maxSupply = wrapper.maxTotalSupply();
184+
```
185+
186+
---
187+
188+
## Integration Patterns
189+
190+
### Operator system
191+
192+
Delegate transfer capabilities with time-based expiration:
193+
194+
```solidity
195+
// Grant operator permission until a specific timestamp
196+
token.setOperator(operatorAddress, validUntilTimestamp);
197+
198+
// Check if an address is an authorized operator
199+
bool isAuthorized = token.isOperator(holder, spender);
200+
```
201+
202+
### Amount disclosure
203+
204+
Optionally reveal encrypted amounts publicly:
205+
206+
```solidity
207+
// Request disclosure (initiates async decryption)
208+
token.requestDiscloseEncryptedAmount(encryptedAmount);
209+
210+
// Complete disclosure with proof
211+
token.discloseEncryptedAmount(encryptedAmount, cleartextAmount, decryptionProof);
212+
```
213+
214+
### Check ACL permissions
215+
216+
Before using encrypted amounts in transactions, callers must be authorized:
217+
218+
```solidity
219+
require(FHE.isAllowed(encryptedAmount, msg.sender), "Unauthorized");
220+
```
221+
222+
Transfer functions with `euint64` (not `externalEuint64`) require the caller to already have ACL permission for that ciphertext. More information in the [FHEVM library documentation](https://docs.zama.org/protocol/protocol/overview/library#access-control).
223+
224+
---
225+
226+
## Architecture
227+
228+
```
229+
┌─────────────────────────────────────────────────────────────────┐
230+
│ ConfidentialWrapper │
231+
│ (UUPS Upgradeable, Ownable2Step) │
232+
├─────────────────────────────────────────────────────────────────┤
233+
│ ERC7984ERC20WrapperUpgradeable │
234+
│ (Wrapping/Unwrapping Logic, ERC1363 Receiver) │
235+
├─────────────────────────────────────────────────────────────────┤
236+
│ ERC7984Upgradeable │
237+
│ (Confidential Token Standard - Encrypted Balances/Transfers) │
238+
├─────────────────────────────────────────────────────────────────┤
239+
│ ZamaEthereumConfigUpgradeable │
240+
│ (FHE Coprocessor Configuration) │
241+
└─────────────────────────────────────────────────────────────────┘
242+
```
243+
244+
---
245+
246+
## Events
247+
248+
| Event | Description |
249+
|-------|-------------|
250+
| `ConfidentialTransfer(from, to, encryptedAmount)` | Emitted on every transfer (including mint/burn) |
251+
| `OperatorSet(holder, operator, until)` | Emitted when operator permissions change |
252+
| `UnwrapRequested(receiver, encryptedAmount)` | Emitted when unwrap is initiated |
253+
| `UnwrapFinalized(receiver, encryptedAmount, cleartextAmount)` | Emitted when unwrap completes |
254+
| `AmountDiscloseRequested(encryptedAmount, requester)` | Emitted when disclosure is requested |
255+
| `AmountDisclosed(encryptedAmount, cleartextAmount)` | Emitted when amount is publicly disclosed |
256+
257+
---
258+
259+
## Errors
260+
261+
| Error | Cause |
262+
|-------|-------|
263+
| `ERC7984InvalidReceiver(address)` | Transfer to zero address |
264+
| `ERC7984InvalidSender(address)` | Transfer from zero address |
265+
| `ERC7984UnauthorizedSpender(holder, spender)` | Caller not authorized as operator |
266+
| `ERC7984ZeroBalance(holder)` | Sender has never held tokens |
267+
| `ERC7984UnauthorizedUseOfEncryptedAmount(amount, user)` | Caller lacks ACL permission for ciphertext |
268+
| `ERC7984UnauthorizedCaller(caller)` | Invalid caller for operation |
269+
| `InvalidUnwrapRequest(amount)` | Finalizing non-existent unwrap request |
270+
| `ERC7984TotalSupplyOverflow()` | Minting would exceed uint64 max |
271+
272+
---
273+
274+
## Important Considerations
275+
276+
### Ciphertext uniqueness assumption
277+
278+
The unwrap mechanism stores requests in a mapping keyed by ciphertext and the current implementation assumes these ciphertexts are unique. This holds in this very specific case but be aware of this architectural decision as it is **NOT** true in the general case.
279+
280+
---
281+
282+
## Interface Support (ERC-165)
283+
284+
```solidity
285+
wrapper.supportsInterface(type(IERC7984).interfaceId);
286+
wrapper.supportsInterface(type(IERC7984ERC20Wrapper).interfaceId);
287+
wrapper.supportsInterface(type(IERC165).interfaceId);
288+
```
289+
290+
---
291+
292+
## Upgradeability
293+
294+
The contract uses **UUPS (Universal Upgradeable Proxy Standard)** with 2-step ownership transfer. Only the owner can upgrade the contract. Initially, the owner is set to a DAO governance contract handled by Zama. Ownership will then be transferred to the underlying token's owner.

0 commit comments

Comments
 (0)