create-fhevm-example 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +3 -4
  2. package/contracts/concepts/antipatterns/ControlFlow.sol +160 -0
  3. package/contracts/concepts/antipatterns/OperationsGasNoise.sol +190 -0
  4. package/contracts/concepts/antipatterns/Permissions.sol +254 -0
  5. package/dist/scripts/commands/add-mode.d.ts.map +1 -1
  6. package/dist/scripts/commands/add-mode.js +10 -21
  7. package/dist/scripts/commands/doctor.js +76 -2
  8. package/dist/scripts/commands/generate-config.js +13 -2
  9. package/dist/scripts/commands/generate-docs.js +4 -1
  10. package/dist/scripts/index.js +43 -24
  11. package/dist/scripts/shared/builders.d.ts.map +1 -1
  12. package/dist/scripts/shared/builders.js +8 -14
  13. package/dist/scripts/shared/config.d.ts.map +1 -1
  14. package/dist/scripts/shared/config.js +96 -67
  15. package/dist/scripts/shared/utils.d.ts.map +1 -1
  16. package/dist/scripts/shared/utils.js +5 -4
  17. package/package.json +1 -1
  18. package/test/concepts/antipatterns/ControlFlow.ts +125 -0
  19. package/test/concepts/antipatterns/OperationsGasNoise.ts +187 -0
  20. package/test/concepts/antipatterns/Permissions.ts +327 -0
  21. package/contracts/concepts/FHEAntiPatterns.sol +0 -300
  22. package/test/concepts/FHEAntiPatterns.ts +0 -111
  23. /package/contracts/concepts/{FHEAccessControl.sol → core/FHEAccessControl.sol} +0 -0
  24. /package/contracts/concepts/{FHEHandles.sol → core/FHEHandles.sol} +0 -0
  25. /package/contracts/concepts/{FHEInputProof.sol → core/FHEInputProof.sol} +0 -0
  26. /package/test/concepts/{FHEAccessControl.ts → core/FHEAccessControl.ts} +0 -0
  27. /package/test/concepts/{FHEHandles.ts → core/FHEHandles.ts} +0 -0
  28. /package/test/concepts/{FHEInputProof.ts → core/FHEInputProof.ts} +0 -0
package/README.md CHANGED
@@ -56,8 +56,8 @@ npx create-fhevm-example --category basic
56
56
  npx create-fhevm-example --add
57
57
  npx create-fhevm-example --add --target ./my-existing-project
58
58
 
59
- # With auto-install and testing
60
- npx create-fhevm-example --example fhe-counter --output ./my-project --install --test
59
+ # With auto-install
60
+ npx create-fhevm-example --example fhe-counter --output ./my-project --install
61
61
  ```
62
62
 
63
63
  ---
@@ -72,7 +72,6 @@ npx create-fhevm-example --example fhe-counter --output ./my-project --install -
72
72
  | `--target <dir>` | Target directory for `--add` mode (default: current dir) |
73
73
  | `--output <dir>` | Output directory for new projects |
74
74
  | `--install` | Auto-install dependencies after scaffolding |
75
- | `--test` | Auto-run tests (requires `--install`) |
76
75
  | `--help`, `-h` | Show help information |
77
76
 
78
77
  ---
@@ -85,7 +84,7 @@ npx create-fhevm-example --example fhe-counter --output ./my-project --install -
85
84
 
86
85
  **FHE Operations** (4): `fhe-add`, `fhe-if-then-else`, `fhe-arithmetic`, `fhe-comparison`
87
86
 
88
- **Concepts** (4): `fhe-access-control`, `fhe-input-proof`, `fhe-handles`, `fhe-anti-patterns`
87
+ **Concepts** (6): `fhe-access-control`, `fhe-input-proof`, `fhe-handles`, `control-flow`, `permissions`, `operations-gas-noise`
89
88
 
90
89
  **Gaming** (3): `rock-paper-scissors`, `encrypted-lottery`, `encrypted-poker`
91
90
 
@@ -0,0 +1,160 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint32,
7
+ ebool,
8
+ externalEuint32
9
+ } from "@fhevm/solidity/lib/FHE.sol";
10
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
+
12
+ /**
13
+ * @notice Control flow anti-patterns in FHE development.
14
+ * Demonstrates common mistakes when using conditional logic
15
+ * and loops with encrypted values.
16
+ *
17
+ * @dev Covers 3 critical patterns: if/else branching, require statements,
18
+ * and encrypted loop iterations.
19
+ * Each shows ❌ WRONG and ✅ CORRECT implementations.
20
+ */
21
+ contract FHEControlFlowAntiPatterns is ZamaEthereumConfig {
22
+ euint32 private _secretBalance;
23
+ euint32 private _threshold;
24
+ ebool private _validationResult;
25
+
26
+ /// @notice Initialize contract with encrypted balance (threshold fixed for simplicity)
27
+ function initialize(
28
+ externalEuint32 balance,
29
+ bytes calldata inputProof
30
+ ) external {
31
+ _secretBalance = FHE.fromExternal(balance, inputProof);
32
+ _threshold = FHE.asEuint32(100); // Fixed threshold for simpler testing
33
+
34
+ FHE.allowThis(_secretBalance);
35
+ FHE.allowThis(_threshold);
36
+ FHE.allow(_secretBalance, msg.sender);
37
+ FHE.allow(_threshold, msg.sender);
38
+ }
39
+
40
+ // ═══════════════════════════════════════════════════════════════════════
41
+ // ANTI-PATTERN 1: If/Else Branching on Encrypted Values
42
+ // ═══════════════════════════════════════════════════════════════════════
43
+
44
+ /**
45
+ * ❌ WRONG: Using if/else with encrypted comparison
46
+ * @dev This pattern leaks information through control flow
47
+ */
48
+ function wrongBranching() external returns (uint256) {
49
+ // ❌ This would decrypt the comparison result!
50
+ // The branch taken reveals encrypted information
51
+ // if (decrypt(_secretBalance > _threshold)) {
52
+ // return 1;
53
+ // }
54
+ // return 0;
55
+
56
+ // Placeholder to make function compile
57
+ return 0;
58
+ }
59
+
60
+ /**
61
+ * ✅ CORRECT: Use FHE.select for conditional logic
62
+ * @dev All computation stays encrypted
63
+ */
64
+ function correctConditional() external {
65
+ ebool isAboveThreshold = FHE.gt(_secretBalance, _threshold);
66
+
67
+ // Apply penalty if above threshold, otherwise keep balance
68
+ euint32 penaltyAmount = FHE.asEuint32(10);
69
+ euint32 balanceMinusPenalty = FHE.sub(_secretBalance, penaltyAmount);
70
+
71
+ _secretBalance = FHE.select(
72
+ isAboveThreshold,
73
+ balanceMinusPenalty,
74
+ _secretBalance
75
+ );
76
+
77
+ FHE.allowThis(_secretBalance);
78
+ FHE.allow(_secretBalance, msg.sender);
79
+ }
80
+
81
+ // ═══════════════════════════════════════════════════════════════════════
82
+ // ANTI-PATTERN 2: Require/Revert with Encrypted Conditions
83
+ // ═══════════════════════════════════════════════════════════════════════
84
+
85
+ /**
86
+ * ❌ WRONG: Cannot use require with encrypted boolean
87
+ * @dev This doesn't compile - ebool cannot be used in require
88
+ */
89
+ function wrongRequire() external pure returns (string memory) {
90
+ // ❌ COMPILE ERROR: require expects bool, not ebool
91
+ // ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
92
+ // require(hasEnough, "Insufficient balance");
93
+
94
+ return "This pattern doesn't work with encrypted values";
95
+ }
96
+
97
+ /**
98
+ * ✅ CORRECT: Store encrypted boolean for client to check
99
+ * @dev Let the client decrypt via getter and handle validation
100
+ */
101
+ function correctValidation() external {
102
+ ebool hasEnough = FHE.ge(_secretBalance, FHE.asEuint32(100));
103
+
104
+ _validationResult = hasEnough;
105
+ FHE.allowThis(_validationResult);
106
+ FHE.allow(_validationResult, msg.sender);
107
+ }
108
+
109
+ /// @notice Get validation result
110
+ function getValidationResult() external view returns (ebool) {
111
+ return _validationResult;
112
+ }
113
+
114
+ // ═══════════════════════════════════════════════════════════════════════
115
+ // ANTI-PATTERN 3: Encrypted Loop Iterations
116
+ // ═══════════════════════════════════════════════════════════════════════
117
+
118
+ /**
119
+ * ❌ WRONG: Loop count based on encrypted value
120
+ * @dev Gas consumption reveals the loop count
121
+ */
122
+ function wrongEncryptedLoop() external pure returns (string memory) {
123
+ // ❌ GAS LEAK: Number of iterations visible through gas cost!
124
+ // for (uint i = 0; i < decrypt(_secretBalance); i++) {
125
+ // // Each iteration costs gas
126
+ // }
127
+
128
+ return "Loop iterations leak through gas consumption";
129
+ }
130
+
131
+ /**
132
+ * ✅ CORRECT: Fixed iterations with FHE.select
133
+ * @dev Always loop maximum times, conditionally apply operations
134
+ */
135
+ function correctFixedIterations() external {
136
+ uint256 MAX_ITERATIONS = 5;
137
+ euint32 result = FHE.asEuint32(0);
138
+
139
+ for (uint256 i = 0; i < MAX_ITERATIONS; i++) {
140
+ // Check if we should add (i < _secretBalance)
141
+ ebool shouldAdd = FHE.lt(FHE.asEuint32(uint32(i)), _secretBalance);
142
+
143
+ // Add 1 if condition true, 0 otherwise
144
+ euint32 toAdd = FHE.select(
145
+ shouldAdd,
146
+ FHE.asEuint32(1),
147
+ FHE.asEuint32(0)
148
+ );
149
+ result = FHE.add(result, toAdd);
150
+ }
151
+
152
+ FHE.allowThis(result);
153
+ FHE.allow(result, msg.sender);
154
+ }
155
+
156
+ /// @notice Helper to get balance for testing
157
+ function getBalance() external view returns (euint32) {
158
+ return _secretBalance;
159
+ }
160
+ }
@@ -0,0 +1,190 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint32,
7
+ euint256,
8
+ externalEuint32
9
+ } from "@fhevm/solidity/lib/FHE.sol";
10
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
+
12
+ /**
13
+ * @notice Operations, gas, and noise anti-patterns in FHE development.
14
+ * Demonstrates performance issues, side-channel leaks, and
15
+ * inefficient encrypted computation patterns.
16
+ *
17
+ * @dev Covers 4 patterns: gas/timing side channels, noise accumulation,
18
+ * deprecated APIs, and type mismatches.
19
+ */
20
+ contract FHEOperationsGasNoiseAntiPatterns is ZamaEthereumConfig {
21
+ euint32 private _secretValue;
22
+
23
+ // ═══════════════════════════════════════════════════════════════════════
24
+ // ANTI-PATTERN 1: Gas/Timing Side Channel Attacks
25
+ // ═══════════════════════════════════════════════════════════════════════
26
+
27
+ /**
28
+ * ❌ WRONG: Different code paths have different gas costs
29
+ * @dev Gas consumption reveals which branch was taken
30
+ */
31
+ function wrongGasLeak(
32
+ externalEuint32 input,
33
+ bytes calldata inputProof
34
+ ) external {
35
+ euint32 value = FHE.fromExternal(input, inputProof);
36
+
37
+ // ❌ These operations have different gas costs!
38
+ // If value > 100: expensive operation
39
+ // If value <= 100: cheap operation
40
+ // Gas cost reveals the comparison result!
41
+
42
+ // Simulating different paths (commented to avoid actual leak)
43
+ // if (decrypt(value > 100)) {
44
+ // // Expensive: 10 multiplications
45
+ // } else {
46
+ // // Cheap: 1 addition
47
+ // }
48
+
49
+ _secretValue = value;
50
+ FHE.allowThis(_secretValue);
51
+ }
52
+
53
+ /**
54
+ * ✅ CORRECT: Constant-time operations
55
+ * @dev All paths consume same gas
56
+ */
57
+ function correctConstantTime(
58
+ externalEuint32 input,
59
+ bytes calldata inputProof
60
+ ) external {
61
+ euint32 value = FHE.fromExternal(input, inputProof);
62
+
63
+ // ✅ Always perform same operations regardless of value
64
+ euint32 result = FHE.mul(value, FHE.asEuint32(2));
65
+
66
+ _secretValue = result;
67
+ FHE.allowThis(_secretValue);
68
+ FHE.allow(_secretValue, msg.sender);
69
+ }
70
+
71
+ // ═══════════════════════════════════════════════════════════════════════
72
+ // ANTI-PATTERN 2: Noise Accumulation (Too Many Operations)
73
+ // ═══════════════════════════════════════════════════════════════════════
74
+
75
+ /**
76
+ * ❌ WRONG: Long chain of FHE operations
77
+ * @dev Each operation adds noise, eventually corrupting ciphertext
78
+ */
79
+ function wrongNoiseAccumulation(
80
+ externalEuint32 input,
81
+ bytes calldata inputProof
82
+ ) external {
83
+ euint32 value = FHE.fromExternal(input, inputProof);
84
+
85
+ // ❌ Too many chained operations!
86
+ // Each mul adds significant noise
87
+ euint32 result = value;
88
+ for (uint i = 0; i < 5; i++) {
89
+ result = FHE.mul(result, FHE.asEuint32(2)); // High noise per operation
90
+ }
91
+
92
+ _secretValue = result;
93
+ FHE.allowThis(_secretValue);
94
+ }
95
+
96
+ /**
97
+ * ✅ CORRECT: Minimize operation chains
98
+ * @dev Use mathematical optimization to reduce operations
99
+ */
100
+ function correctMinimizeOperations(
101
+ externalEuint32 input,
102
+ bytes calldata inputProof
103
+ ) external {
104
+ euint32 value = FHE.fromExternal(input, inputProof);
105
+
106
+ // ✅ Instead of 5 multiplications by 2, use single multiplication
107
+ // 2^5 = 32
108
+ euint32 result = FHE.mul(value, FHE.asEuint32(32));
109
+
110
+ _secretValue = result;
111
+ FHE.allowThis(_secretValue);
112
+ FHE.allow(_secretValue, msg.sender);
113
+ }
114
+
115
+ // ═══════════════════════════════════════════════════════════════════════
116
+ // ANTI-PATTERN 3: Using Deprecated FHEVM APIs
117
+ // ═══════════════════════════════════════════════════════════════════════
118
+
119
+ /**
120
+ * ❌ WRONG: Using old TFHE.decrypt() pattern
121
+ * @dev Deprecated in FHEVM v0.9+
122
+ */
123
+ function wrongDeprecatedAPI() external pure returns (string memory) {
124
+ // ❌ OLD (v0.8 and earlier):
125
+ // TFHE.decrypt() - went through Zama Oracle
126
+ //
127
+ // This pattern is deprecated and no longer supported
128
+
129
+ return "Don't use TFHE.decrypt() - it's deprecated";
130
+ }
131
+
132
+ /**
133
+ * ✅ CORRECT: Use modern FHEVM v0.9+ APIs
134
+ * @dev Use FHE.makePubliclyDecryptable() or userDecrypt pattern
135
+ */
136
+ function correctModernAPI() external pure returns (string memory) {
137
+ // ✅ NEW (v0.9+):
138
+ // - FHE.makePubliclyDecryptable() for public decryption
139
+ // - userDecrypt pattern via fhevm.js for user decryption
140
+
141
+ return "Use FHE.makePubliclyDecryptable() or fhevm.js userDecrypt()";
142
+ }
143
+
144
+ // ═══════════════════════════════════════════════════════════════════════
145
+ // ANTI-PATTERN 4: Unnecessary Large Data Types
146
+ // ═══════════════════════════════════════════════════════════════════════
147
+
148
+ /**
149
+ * ❌ WRONG: Using euint256 when euint32 is sufficient
150
+ * @dev Larger types = more gas + more noise
151
+ */
152
+ function wrongOversizedType(
153
+ externalEuint32 input,
154
+ bytes calldata inputProof
155
+ ) external {
156
+ euint32 value = FHE.fromExternal(input, inputProof);
157
+
158
+ // ❌ Unnecessarily converting to euint256!
159
+ // euint256 operations are limited and more expensive
160
+ // This conversion is wasteful when euint32 would suffice
161
+ euint256 largeValue = FHE.asEuint256(value);
162
+
163
+ // Converting back to euint32 (double conversion waste!)
164
+ _secretValue = FHE.asEuint32(largeValue);
165
+ FHE.allowThis(_secretValue);
166
+ }
167
+
168
+ /**
169
+ * ✅ CORRECT: Use smallest sufficient data type
170
+ * @dev euint32 is cheaper than euint256
171
+ */
172
+ function correctRightSizedType(
173
+ externalEuint32 input,
174
+ bytes calldata inputProof
175
+ ) external {
176
+ euint32 value = FHE.fromExternal(input, inputProof);
177
+
178
+ // ✅ Keep using euint32 if values fit
179
+ euint32 result = FHE.mul(value, FHE.asEuint32(2));
180
+
181
+ _secretValue = result;
182
+ FHE.allowThis(_secretValue);
183
+ FHE.allow(_secretValue, msg.sender);
184
+ }
185
+
186
+ /// @notice Helper to get value for testing
187
+ function getValue() external view returns (euint32) {
188
+ return _secretValue;
189
+ }
190
+ }
@@ -0,0 +1,254 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol";
5
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
6
+
7
+ /**
8
+ * @notice Permission management anti-patterns in FHE development.
9
+ * Demonstrates common mistakes with allowThis, allow, and
10
+ * permission propagation across transfers and contracts.
11
+ *
12
+ * @dev Covers 6 critical patterns: missing allowThis, missing allow(user),
13
+ * view functions without permissions, unauthenticated re-encryption,
14
+ * transfer permission issues, and cross-contract delegation.
15
+ */
16
+ contract FHEPermissionsAntiPatterns is ZamaEthereumConfig {
17
+ euint32 private _secretValue;
18
+ mapping(address => euint32) private _balances;
19
+
20
+ // ═══════════════════════════════════════════════════════════════════════
21
+ // ANTI-PATTERN 1: Missing allowThis After Computation
22
+ // ═══════════════════════════════════════════════════════════════════════
23
+
24
+ /**
25
+ * ❌ WRONG: Compute but forget allowThis
26
+ * @dev Result exists but contract can't use it in future operations
27
+ */
28
+ function wrongMissingAllowThis(
29
+ externalEuint32 input,
30
+ bytes calldata inputProof
31
+ ) external {
32
+ _secretValue = FHE.fromExternal(input, inputProof);
33
+ euint32 doubled = FHE.mul(_secretValue, FHE.asEuint32(2));
34
+ _secretValue = doubled;
35
+
36
+ // ❌ Missing FHE.allowThis! Contract can't use this value later
37
+ FHE.allow(_secretValue, msg.sender);
38
+ }
39
+
40
+ /**
41
+ * ✅ CORRECT: Always grant allowThis after computation
42
+ * @dev Contract needs permission to use encrypted values
43
+ */
44
+ function correctWithAllowThis(
45
+ externalEuint32 input,
46
+ bytes calldata inputProof
47
+ ) external {
48
+ _secretValue = FHE.fromExternal(input, inputProof);
49
+ euint32 doubled = FHE.mul(_secretValue, FHE.asEuint32(2));
50
+ _secretValue = doubled;
51
+
52
+ FHE.allowThis(_secretValue);
53
+ FHE.allow(_secretValue, msg.sender);
54
+ }
55
+
56
+ // ═══════════════════════════════════════════════════════════════════════
57
+ // ANTI-PATTERN 2: Missing allow(user)
58
+ // ═══════════════════════════════════════════════════════════════════════
59
+
60
+ /**
61
+ * ❌ WRONG: Only allowThis without user permission
62
+ * @dev No one can decrypt the value
63
+ */
64
+ function wrongMissingUserAllow(
65
+ externalEuint32 input,
66
+ bytes calldata inputProof
67
+ ) external {
68
+ _secretValue = FHE.fromExternal(input, inputProof);
69
+
70
+ // ❌ Contract can compute but no one can decrypt!
71
+ FHE.allowThis(_secretValue);
72
+ }
73
+
74
+ /**
75
+ * ✅ CORRECT: Grant both allowThis and allow(user)
76
+ * @dev User can decrypt after contract operations
77
+ */
78
+ function correctWithUserAllow(
79
+ externalEuint32 input,
80
+ bytes calldata inputProof
81
+ ) external {
82
+ _secretValue = FHE.fromExternal(input, inputProof);
83
+
84
+ FHE.allowThis(_secretValue);
85
+ FHE.allow(_secretValue, msg.sender);
86
+ }
87
+
88
+ // ═══════════════════════════════════════════════════════════════════════
89
+ // ANTI-PATTERN 3: View Function Without Permissions
90
+ // ═══════════════════════════════════════════════════════════════════════
91
+
92
+ /**
93
+ * ❌ WRONG: Store value without granting permission to caller
94
+ * @dev When caller tries to get value via view, they can't decrypt it
95
+ */
96
+ function wrongStoreWithoutPermission(
97
+ externalEuint32 input,
98
+ bytes calldata inputProof
99
+ ) external {
100
+ _secretValue = FHE.fromExternal(input, inputProof);
101
+
102
+ // ❌ Only allowThis, caller has no permission!
103
+ FHE.allowThis(_secretValue);
104
+ }
105
+
106
+ /**
107
+ * ✅ CORRECT: Grant permission to caller when storing
108
+ * @dev Caller can now decrypt value returned from view function
109
+ */
110
+ function correctStoreWithPermission(
111
+ externalEuint32 input,
112
+ bytes calldata inputProof
113
+ ) external {
114
+ _secretValue = FHE.fromExternal(input, inputProof);
115
+
116
+ FHE.allowThis(_secretValue);
117
+ FHE.allow(_secretValue, msg.sender); // ✅ Grant permission!
118
+ }
119
+
120
+ /// @notice View function to get stored value
121
+ function getValue() external view returns (euint32) {
122
+ return _secretValue;
123
+ }
124
+
125
+ // ═══════════════════════════════════════════════════════════════════════
126
+ // ANTI-PATTERN 4: Unauthenticated Re-encryption
127
+ // ═══════════════════════════════════════════════════════════════════════
128
+
129
+ /**
130
+ * ❌ WRONG: Re-encrypt without verifying public key ownership
131
+ * @dev Anyone can provide any public key and steal encrypted data
132
+ */
133
+ function wrongReencryptWithoutAuth(
134
+ bytes32 publicKey
135
+ ) external view returns (bytes memory) {
136
+ // ❌ SECURITY RISK: No verification that caller owns this public key!
137
+ // Attacker can provide victim's public key and get their data
138
+
139
+ // This would allow impersonation attacks:
140
+ // return Gateway.reencrypt(_secretValue, publicKey);
141
+
142
+ return ""; // Placeholder
143
+ }
144
+
145
+ /**
146
+ * ✅ CORRECT: Use FHEVM's built-in authentication
147
+ * @dev fhevm.js SDK verifies EIP-712 signature automatically
148
+ * Only the owner of the public key can decrypt
149
+ */
150
+ function correctReencryptWithAuth() external view returns (euint32) {
151
+ // ✅ Return handle directly
152
+ // Client uses fhevm.instance.reencrypt() which:
153
+ // 1. Signs request with their private key (EIP-712)
154
+ // 2. Gateway verifies signature matches public key
155
+ // 3. Only then re-encrypts for that public key
156
+
157
+ return _secretValue;
158
+ }
159
+
160
+ // ═══════════════════════════════════════════════════════════════════════
161
+ // ANTI-PATTERN 5: Transfer Without Permission Propagation
162
+ // ═══════════════════════════════════════════════════════════════════════
163
+
164
+ /**
165
+ * @notice Initialize balance for msg.sender
166
+ * @dev Required before using transfer functions
167
+ */
168
+ function initializeBalance(
169
+ externalEuint32 initialBalance,
170
+ bytes calldata inputProof
171
+ ) external {
172
+ _balances[msg.sender] = FHE.fromExternal(initialBalance, inputProof);
173
+ FHE.allowThis(_balances[msg.sender]);
174
+ FHE.allow(_balances[msg.sender], msg.sender);
175
+ }
176
+
177
+ /**
178
+ * ❌ WRONG: Transfer without granting permissions
179
+ * @dev Recipient gets balance but can't use or decrypt it
180
+ */
181
+ function wrongTransferWithoutPermission(
182
+ address recipient,
183
+ externalEuint32 amount,
184
+ bytes calldata inputProof
185
+ ) external {
186
+ euint32 transferAmount = FHE.fromExternal(amount, inputProof);
187
+
188
+ _balances[msg.sender] = FHE.sub(_balances[msg.sender], transferAmount);
189
+ _balances[recipient] = FHE.add(_balances[recipient], transferAmount);
190
+
191
+ // ❌ Recipient has no permission to use their new balance!
192
+ }
193
+
194
+ /**
195
+ * ✅ CORRECT: Grant permissions after transfer
196
+ * @dev Both parties can use and decrypt their updated balances
197
+ */
198
+ function correctTransferWithPermission(
199
+ address recipient,
200
+ externalEuint32 amount,
201
+ bytes calldata inputProof
202
+ ) external {
203
+ euint32 transferAmount = FHE.fromExternal(amount, inputProof);
204
+
205
+ _balances[msg.sender] = FHE.sub(_balances[msg.sender], transferAmount);
206
+ _balances[recipient] = FHE.add(_balances[recipient], transferAmount);
207
+
208
+ // ✅ Grant permissions to both parties
209
+ FHE.allowThis(_balances[msg.sender]);
210
+ FHE.allow(_balances[msg.sender], msg.sender);
211
+ FHE.allowThis(_balances[recipient]);
212
+ FHE.allow(_balances[recipient], recipient);
213
+ }
214
+
215
+ // ═══════════════════════════════════════════════════════════════════════
216
+ // ANTI-PATTERN 6: Cross-Contract Permission Delegation
217
+ // ═══════════════════════════════════════════════════════════════════════
218
+
219
+ /**
220
+ * ❌ WRONG: Call another contract without granting permission
221
+ * @dev Other contract can't use the encrypted value
222
+ */
223
+ function wrongCrossContractCall(address processor) external returns (bool) {
224
+ // ❌ processor contract has no permission to use _secretValue!
225
+ // This call will fail or return garbage
226
+ (bool success, ) = processor.call(
227
+ abi.encodeWithSignature("process(uint256)", _secretValue)
228
+ );
229
+ return success;
230
+ }
231
+
232
+ /**
233
+ * ✅ CORRECT: Grant temporary permission before cross-contract call
234
+ * @dev Use allowTransient for gas-efficient temporary access
235
+ */
236
+ function correctCrossContractCall(
237
+ address processor
238
+ ) external returns (bool) {
239
+ // ✅ Grant temporary permission (expires at end of transaction)
240
+ FHE.allowTransient(_secretValue, processor);
241
+
242
+ // Now processor can use _secretValue in this transaction
243
+ (bool success, ) = processor.call(
244
+ abi.encodeWithSignature("process(uint256)", _secretValue)
245
+ );
246
+
247
+ return success;
248
+ }
249
+
250
+ /// @notice Helper to get balance for testing
251
+ function getBalance(address user) external view returns (euint32) {
252
+ return _balances[user];
253
+ }
254
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"add-mode.d.ts","sourceRoot":"","sources":["../../../scripts/commands/add-mode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2OH;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ElE"}
1
+ {"version":3,"file":"add-mode.d.ts","sourceRoot":"","sources":["../../../scripts/commands/add-mode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuOH;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ElE"}