create-fhevm-example 1.3.2 → 1.4.1
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.
- package/contracts/advanced/BlindAuction.sol +255 -0
- package/contracts/advanced/EncryptedEscrow.sol +315 -0
- package/contracts/advanced/HiddenVoting.sol +231 -0
- package/contracts/advanced/PrivateKYC.sol +309 -0
- package/contracts/advanced/PrivatePayroll.sol +285 -0
- package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
- package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
- package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
- package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
- package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
- package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
- package/contracts/basic/encryption/FHECounter.sol +54 -0
- package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
- package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
- package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
- package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
- package/contracts/concepts/FHEAccessControl.sol +94 -0
- package/contracts/concepts/FHEAntiPatterns.sol +329 -0
- package/contracts/concepts/FHEHandles.sol +128 -0
- package/contracts/concepts/FHEInputProof.sol +104 -0
- package/contracts/gaming/EncryptedLottery.sol +298 -0
- package/contracts/gaming/EncryptedPoker.sol +337 -0
- package/contracts/gaming/RockPaperScissors.sol +213 -0
- package/contracts/openzeppelin/ERC7984.sol +85 -0
- package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
- package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
- package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
- package/contracts/openzeppelin/VestingWallet.sol +147 -0
- package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
- package/dist/scripts/commands/add-mode.d.ts.map +1 -0
- package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
- package/dist/scripts/commands/doctor.d.ts.map +1 -0
- package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
- package/dist/scripts/commands/generate-config.d.ts.map +1 -0
- package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
- package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
- package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
- package/dist/scripts/commands/maintenance.d.ts.map +1 -0
- package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
- package/dist/scripts/index.js +14 -33
- package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
- package/dist/scripts/shared/builders.d.ts.map +1 -0
- package/dist/scripts/{builders.js → shared/builders.js} +49 -30
- package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
- package/dist/scripts/shared/config.d.ts.map +1 -0
- package/dist/scripts/{config.js → shared/config.js} +48 -59
- package/dist/scripts/shared/generators.d.ts +42 -0
- package/dist/scripts/shared/generators.d.ts.map +1 -0
- package/dist/scripts/{utils.js → shared/generators.js} +34 -271
- package/dist/scripts/shared/ui.d.ts.map +1 -0
- package/dist/scripts/{ui.js → shared/ui.js} +3 -2
- package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
- package/dist/scripts/shared/utils.d.ts.map +1 -0
- package/dist/scripts/shared/utils.js +236 -0
- package/fhevm-hardhat-template/.eslintignore +26 -0
- package/fhevm-hardhat-template/.eslintrc.yml +21 -0
- package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
- package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
- package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
- package/fhevm-hardhat-template/.prettierignore +25 -0
- package/fhevm-hardhat-template/.prettierrc.yml +15 -0
- package/fhevm-hardhat-template/.solcover.js +4 -0
- package/fhevm-hardhat-template/.solhint.json +12 -0
- package/fhevm-hardhat-template/.solhintignore +3 -0
- package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
- package/fhevm-hardhat-template/.vscode/settings.json +9 -0
- package/fhevm-hardhat-template/LICENSE +33 -0
- package/fhevm-hardhat-template/README.md +110 -0
- package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
- package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
- package/fhevm-hardhat-template/hardhat.config.ts +90 -0
- package/fhevm-hardhat-template/package-lock.json +10405 -0
- package/fhevm-hardhat-template/package.json +104 -0
- package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
- package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
- package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
- package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
- package/fhevm-hardhat-template/tsconfig.json +23 -0
- package/package.json +11 -8
- package/test/advanced/BlindAuction.ts +246 -0
- package/test/advanced/EncryptedEscrow.ts +295 -0
- package/test/advanced/HiddenVoting.ts +268 -0
- package/test/advanced/PrivateKYC.ts +382 -0
- package/test/advanced/PrivatePayroll.ts +253 -0
- package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
- package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
- package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
- package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
- package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
- package/test/basic/encryption/EncryptSingleValue.ts +124 -0
- package/test/basic/encryption/FHECounter.ts +112 -0
- package/test/basic/fhe-operations/FHEAdd.ts +97 -0
- package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
- package/test/basic/fhe-operations/FHEComparison.ts +167 -0
- package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
- package/test/concepts/FHEAccessControl.ts +154 -0
- package/test/concepts/FHEAntiPatterns.ts +111 -0
- package/test/concepts/FHEHandles.ts +156 -0
- package/test/concepts/FHEInputProof.ts +151 -0
- package/test/gaming/EncryptedLottery.ts +214 -0
- package/test/gaming/EncryptedPoker.ts +349 -0
- package/test/gaming/RockPaperScissors.ts +205 -0
- package/test/openzeppelin/ERC7984.ts +142 -0
- package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
- package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
- package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
- package/test/openzeppelin/VestingWallet.ts +89 -0
- package/dist/scripts/add-mode.d.ts.map +0 -1
- package/dist/scripts/builders.d.ts.map +0 -1
- package/dist/scripts/config.d.ts.map +0 -1
- package/dist/scripts/doctor.d.ts.map +0 -1
- package/dist/scripts/generate-config.d.ts.map +0 -1
- package/dist/scripts/generate-docs.d.ts.map +0 -1
- package/dist/scripts/help.d.ts +0 -9
- package/dist/scripts/help.d.ts.map +0 -1
- package/dist/scripts/help.js +0 -73
- package/dist/scripts/maintenance.d.ts.map +0 -1
- package/dist/scripts/ui.d.ts.map +0 -1
- package/dist/scripts/utils.d.ts.map +0 -1
- /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
- /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
- /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
- /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
- /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
- /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
FHE,
|
|
6
|
+
euint8,
|
|
7
|
+
ebool,
|
|
8
|
+
externalEuint8
|
|
9
|
+
} from "@fhevm/solidity/lib/FHE.sol";
|
|
10
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @notice Rock-Paper-Scissors game with encrypted moves - fair play guaranteed!
|
|
14
|
+
*
|
|
15
|
+
* @dev Demonstrates FHE commit-reveal pattern without trusted third party:
|
|
16
|
+
* - Players submit encrypted moves (0=Rock, 1=Paper, 2=Scissors)
|
|
17
|
+
* - FHE comparison determines winner without revealing moves
|
|
18
|
+
* - Loser's move revealed only after game concludes
|
|
19
|
+
*
|
|
20
|
+
* Move encoding: 0 = Rock, 1 = Paper, 2 = Scissors
|
|
21
|
+
* Win logic: (move1 - move2 + 3) % 3 → 1 = player1 wins, 2 = player2 wins
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ IMPORTANT: Uses FHE.rem for modulo - computationally expensive but secure
|
|
24
|
+
*/
|
|
25
|
+
contract RockPaperScissors is ZamaEthereumConfig {
|
|
26
|
+
// ==================== TYPES ====================
|
|
27
|
+
|
|
28
|
+
enum GameState {
|
|
29
|
+
WaitingForPlayers, // 0 or 1 player joined
|
|
30
|
+
BothMoved, // Both players submitted moves
|
|
31
|
+
Revealed // Winner determined and revealed
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ==================== STATE ====================
|
|
35
|
+
|
|
36
|
+
/// Player 1 address
|
|
37
|
+
address public player1;
|
|
38
|
+
|
|
39
|
+
/// Player 2 address
|
|
40
|
+
address public player2;
|
|
41
|
+
|
|
42
|
+
/// Game state
|
|
43
|
+
GameState public state;
|
|
44
|
+
|
|
45
|
+
/// Encrypted moves
|
|
46
|
+
euint8 private _move1;
|
|
47
|
+
euint8 private _move2;
|
|
48
|
+
|
|
49
|
+
/// Winner address (set after reveal)
|
|
50
|
+
address public winner;
|
|
51
|
+
|
|
52
|
+
/// Game ID for tracking multiple games
|
|
53
|
+
uint256 public gameId;
|
|
54
|
+
|
|
55
|
+
// ==================== EVENTS ====================
|
|
56
|
+
|
|
57
|
+
/// @notice Emitted when a player joins
|
|
58
|
+
/// @param player Address of joining player
|
|
59
|
+
/// @param playerNumber 1 or 2
|
|
60
|
+
event PlayerJoined(address indexed player, uint8 playerNumber);
|
|
61
|
+
|
|
62
|
+
/// @notice Emitted when both moves are submitted
|
|
63
|
+
event BothMovesMade();
|
|
64
|
+
|
|
65
|
+
/// @notice Emitted when winner is revealed
|
|
66
|
+
/// @param winner Address of winner (address(0) for tie)
|
|
67
|
+
/// @param gameId Current game ID
|
|
68
|
+
event GameResult(address indexed winner, uint256 indexed gameId);
|
|
69
|
+
|
|
70
|
+
// ==================== CONSTRUCTOR ====================
|
|
71
|
+
|
|
72
|
+
/// @notice Creates a new Rock-Paper-Scissors game
|
|
73
|
+
constructor() {
|
|
74
|
+
gameId = 1;
|
|
75
|
+
state = GameState.WaitingForPlayers;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ==================== PLAY FUNCTIONS ====================
|
|
79
|
+
|
|
80
|
+
/// @notice Submit an encrypted move to play
|
|
81
|
+
/// @dev Move must be 0 (Rock), 1 (Paper), or 2 (Scissors)
|
|
82
|
+
/// @param encryptedMove Encrypted move value (0-2)
|
|
83
|
+
/// @param inputProof Proof validating the encrypted input
|
|
84
|
+
function play(
|
|
85
|
+
externalEuint8 encryptedMove,
|
|
86
|
+
bytes calldata inputProof
|
|
87
|
+
) external {
|
|
88
|
+
require(state == GameState.WaitingForPlayers, "Game not accepting moves");
|
|
89
|
+
require(
|
|
90
|
+
msg.sender != player1 && msg.sender != player2,
|
|
91
|
+
"Already in this game"
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// 🔐 Convert external encrypted input to internal euint8
|
|
95
|
+
euint8 move = FHE.fromExternal(encryptedMove, inputProof);
|
|
96
|
+
|
|
97
|
+
// ✅ Grant contract permission to operate on this value
|
|
98
|
+
FHE.allowThis(move);
|
|
99
|
+
|
|
100
|
+
if (player1 == address(0)) {
|
|
101
|
+
// First player joins
|
|
102
|
+
player1 = msg.sender;
|
|
103
|
+
_move1 = move;
|
|
104
|
+
emit PlayerJoined(msg.sender, 1);
|
|
105
|
+
} else {
|
|
106
|
+
// Second player joins
|
|
107
|
+
player2 = msg.sender;
|
|
108
|
+
_move2 = move;
|
|
109
|
+
state = GameState.BothMoved;
|
|
110
|
+
emit PlayerJoined(msg.sender, 2);
|
|
111
|
+
emit BothMovesMade();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// @notice Determine the winner using FHE computation
|
|
116
|
+
/// @dev Anyone can call after both players have moved
|
|
117
|
+
function determineWinner() external {
|
|
118
|
+
require(state == GameState.BothMoved, "Not ready to determine winner");
|
|
119
|
+
|
|
120
|
+
// 🎯 Winner calculation using FHE
|
|
121
|
+
// Formula: (move1 - move2 + 3) % 3
|
|
122
|
+
// Result: 0 = tie, 1 = player1 wins, 2 = player2 wins
|
|
123
|
+
|
|
124
|
+
// Convert moves to computation: add 3 to avoid negative numbers
|
|
125
|
+
// move1 + 3 - move2 = (move1 - move2 + 3)
|
|
126
|
+
euint8 diff = FHE.add(FHE.add(_move1, FHE.asEuint8(3)), FHE.neg(_move2));
|
|
127
|
+
|
|
128
|
+
// Modulo 3 to get result (0, 1, or 2)
|
|
129
|
+
euint8 result = FHE.rem(diff, 3);
|
|
130
|
+
|
|
131
|
+
// 🔍 Check outcomes
|
|
132
|
+
ebool isTie = FHE.eq(result, FHE.asEuint8(0));
|
|
133
|
+
ebool player1Wins = FHE.eq(result, FHE.asEuint8(1));
|
|
134
|
+
|
|
135
|
+
// 📊 Encode winner as address bits (simplified for demo)
|
|
136
|
+
// In production, use public decryption pattern
|
|
137
|
+
|
|
138
|
+
// Make result publicly decryptable
|
|
139
|
+
FHE.allowThis(result);
|
|
140
|
+
FHE.makePubliclyDecryptable(result);
|
|
141
|
+
|
|
142
|
+
// Store encrypted result for later reveal
|
|
143
|
+
// For this demo, we'll use select to pick winner
|
|
144
|
+
|
|
145
|
+
state = GameState.Revealed;
|
|
146
|
+
|
|
147
|
+
emit GameResult(address(0), gameId); // Placeholder - real winner after decryption
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// @notice Reveal winner with decryption proof
|
|
151
|
+
/// @param abiEncodedResult ABI-encoded uint8 result
|
|
152
|
+
/// @param decryptionProof KMS signature proving decryption
|
|
153
|
+
function revealWinner(
|
|
154
|
+
bytes memory abiEncodedResult,
|
|
155
|
+
bytes memory decryptionProof
|
|
156
|
+
) external {
|
|
157
|
+
require(state == GameState.Revealed, "Game not ready for reveal");
|
|
158
|
+
|
|
159
|
+
// Build ciphertext list for verification
|
|
160
|
+
euint8 diff = FHE.add(FHE.add(_move1, FHE.asEuint8(3)), FHE.neg(_move2));
|
|
161
|
+
euint8 result = FHE.rem(diff, 3);
|
|
162
|
+
|
|
163
|
+
bytes32[] memory cts = new bytes32[](1);
|
|
164
|
+
cts[0] = FHE.toBytes32(result);
|
|
165
|
+
|
|
166
|
+
// Verify the decryption proof
|
|
167
|
+
FHE.checkSignatures(cts, abiEncodedResult, decryptionProof);
|
|
168
|
+
|
|
169
|
+
// Decode result
|
|
170
|
+
uint8 resultValue = abi.decode(abiEncodedResult, (uint8));
|
|
171
|
+
|
|
172
|
+
if (resultValue == 0) {
|
|
173
|
+
winner = address(0); // Tie
|
|
174
|
+
} else if (resultValue == 1) {
|
|
175
|
+
winner = player1;
|
|
176
|
+
} else {
|
|
177
|
+
winner = player2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
emit GameResult(winner, gameId);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// @notice Reset for a new game
|
|
184
|
+
/// @dev Only callable after game is revealed
|
|
185
|
+
function resetGame() external {
|
|
186
|
+
require(state == GameState.Revealed, "Current game not finished");
|
|
187
|
+
|
|
188
|
+
// Reset state
|
|
189
|
+
player1 = address(0);
|
|
190
|
+
player2 = address(0);
|
|
191
|
+
winner = address(0);
|
|
192
|
+
state = GameState.WaitingForPlayers;
|
|
193
|
+
gameId++;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ==================== VIEW FUNCTIONS ====================
|
|
197
|
+
|
|
198
|
+
/// @notice Get current game state
|
|
199
|
+
function getGameState() external view returns (
|
|
200
|
+
address p1,
|
|
201
|
+
address p2,
|
|
202
|
+
GameState currentState,
|
|
203
|
+
address currentWinner,
|
|
204
|
+
uint256 currentGameId
|
|
205
|
+
) {
|
|
206
|
+
return (player1, player2, state, winner, gameId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// @notice Check if an address is in the current game
|
|
210
|
+
function isPlayer(address addr) external view returns (bool) {
|
|
211
|
+
return addr == player1 || addr == player2;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Ownable2Step,
|
|
6
|
+
Ownable
|
|
7
|
+
} from "@openzeppelin/contracts/access/Ownable2Step.sol";
|
|
8
|
+
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
9
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
10
|
+
import {
|
|
11
|
+
ERC7984 as ERC7984Base
|
|
12
|
+
} from "@openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @notice Confidential token using OpenZeppelin's ERC7984 standard
|
|
16
|
+
*
|
|
17
|
+
* @dev Demonstrates minting and burning with both visible and encrypted amounts.
|
|
18
|
+
* Shows how to integrate FHE with standard token operations.
|
|
19
|
+
*/
|
|
20
|
+
contract ERC7984Example is ZamaEthereumConfig, ERC7984Base, Ownable2Step {
|
|
21
|
+
/// @notice Creates a new confidential ERC7984 token with initial supply
|
|
22
|
+
/// @param owner Address that will own the token and receive initial supply
|
|
23
|
+
/// @param amount Initial supply amount (visible on-chain but stored encrypted)
|
|
24
|
+
/// @param name_ Token name
|
|
25
|
+
/// @param symbol_ Token symbol
|
|
26
|
+
/// @param tokenURI_ Token metadata URI
|
|
27
|
+
constructor(
|
|
28
|
+
address owner,
|
|
29
|
+
uint64 amount,
|
|
30
|
+
string memory name_,
|
|
31
|
+
string memory symbol_,
|
|
32
|
+
string memory tokenURI_
|
|
33
|
+
) ERC7984Base(name_, symbol_, tokenURI_) Ownable(owner) {
|
|
34
|
+
// 🔐 Mint initial supply as encrypted amount
|
|
35
|
+
euint64 encryptedAmount = FHE.asEuint64(amount);
|
|
36
|
+
_mint(owner, encryptedAmount);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ==================== MINTING ====================
|
|
40
|
+
|
|
41
|
+
/// @notice Mint with visible amount (owner knows the amount)
|
|
42
|
+
/// @dev Amount is visible on-chain but stored encrypted
|
|
43
|
+
function mint(address to, uint64 amount) external onlyOwner {
|
|
44
|
+
_mint(to, FHE.asEuint64(amount));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// @notice Mint with encrypted amount (full privacy)
|
|
48
|
+
/// @dev Even the contract doesn't know the minted amount
|
|
49
|
+
function confidentialMint(
|
|
50
|
+
address to,
|
|
51
|
+
externalEuint64 encryptedAmount,
|
|
52
|
+
bytes calldata inputProof
|
|
53
|
+
) external onlyOwner returns (euint64 transferred) {
|
|
54
|
+
return _mint(to, FHE.fromExternal(encryptedAmount, inputProof));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ==================== BURNING ====================
|
|
58
|
+
|
|
59
|
+
/// @notice Burn with visible amount
|
|
60
|
+
function burn(address from, uint64 amount) external onlyOwner {
|
|
61
|
+
_burn(from, FHE.asEuint64(amount));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// @notice Burn with encrypted amount (full privacy)
|
|
65
|
+
function confidentialBurn(
|
|
66
|
+
address from,
|
|
67
|
+
externalEuint64 encryptedAmount,
|
|
68
|
+
bytes calldata inputProof
|
|
69
|
+
) external onlyOwner returns (euint64 transferred) {
|
|
70
|
+
return _burn(from, FHE.fromExternal(encryptedAmount, inputProof));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ==================== INTERNAL ====================
|
|
74
|
+
|
|
75
|
+
/// @dev Grant owner access to total supply on every transfer
|
|
76
|
+
function _update(
|
|
77
|
+
address from,
|
|
78
|
+
address to,
|
|
79
|
+
euint64 amount
|
|
80
|
+
) internal virtual override returns (euint64 transferred) {
|
|
81
|
+
transferred = super._update(from, to, amount);
|
|
82
|
+
// Allow owner to decrypt total supply
|
|
83
|
+
FHE.allow(confidentialTotalSupply(), owner());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
5
|
+
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
|
6
|
+
import {
|
|
7
|
+
ERC7984ERC20Wrapper as ERC7984ERC20WrapperBase,
|
|
8
|
+
ERC7984
|
|
9
|
+
} from "@openzeppelin/confidential-contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @notice Wraps ERC20 tokens into confidential ERC7984 tokens
|
|
13
|
+
*
|
|
14
|
+
* @dev WRAP: ERC20 → ERC7984 (public → private)
|
|
15
|
+
* UNWRAP: ERC7984 → ERC20 (private → public, requires decryption)
|
|
16
|
+
*/
|
|
17
|
+
contract ERC7984ERC20WrapperExample is
|
|
18
|
+
ERC7984ERC20WrapperBase,
|
|
19
|
+
ZamaEthereumConfig
|
|
20
|
+
{
|
|
21
|
+
/// @notice Creates a new ERC20-to-ERC7984 wrapper
|
|
22
|
+
/// @param token The ERC20 token to wrap
|
|
23
|
+
/// @param name Name for the wrapped ERC7984 token
|
|
24
|
+
/// @param symbol Symbol for the wrapped ERC7984 token
|
|
25
|
+
/// @param uri Metadata URI for the wrapped token
|
|
26
|
+
constructor(
|
|
27
|
+
IERC20 token,
|
|
28
|
+
string memory name,
|
|
29
|
+
string memory symbol,
|
|
30
|
+
string memory uri
|
|
31
|
+
) ERC7984ERC20WrapperBase(token) ERC7984(name, symbol, uri) {}
|
|
32
|
+
|
|
33
|
+
// 📦 Inherited from ERC7984ERC20Wrapper:
|
|
34
|
+
//
|
|
35
|
+
// wrap(address to, uint256 amount)
|
|
36
|
+
// - User approves this contract for ERC20
|
|
37
|
+
// - ERC20 is escrowed, ERC7984 is minted
|
|
38
|
+
//
|
|
39
|
+
// unwrap(address from, address to, euint64 amount)
|
|
40
|
+
// - ERC7984 is burned
|
|
41
|
+
// - Decryption is requested
|
|
42
|
+
// - After proof, ERC20 is released
|
|
43
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
|
|
6
|
+
import {
|
|
7
|
+
SafeERC20
|
|
8
|
+
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
9
|
+
import {
|
|
10
|
+
IERC7984
|
|
11
|
+
} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
|
|
12
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @notice Swap confidential ERC7984 tokens to regular ERC20 tokens
|
|
16
|
+
*
|
|
17
|
+
* @dev Uses FHEVM v0.9 decryption flow:
|
|
18
|
+
* FHE.makePubliclyDecryptable() + FHE.checkSignatures()
|
|
19
|
+
*/
|
|
20
|
+
contract SwapERC7984ToERC20Example is ZamaEthereumConfig {
|
|
21
|
+
using SafeERC20 for IERC20;
|
|
22
|
+
|
|
23
|
+
error InvalidSwap(euint64 encryptedAmount);
|
|
24
|
+
|
|
25
|
+
struct PendingSwap {
|
|
26
|
+
address receiver;
|
|
27
|
+
bool pending;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
mapping(euint64 => PendingSwap) private _pendingSwaps;
|
|
31
|
+
IERC7984 private _fromToken;
|
|
32
|
+
IERC20 private _toToken;
|
|
33
|
+
|
|
34
|
+
event SwapInitiated(
|
|
35
|
+
euint64 indexed encryptedAmount,
|
|
36
|
+
address indexed receiver
|
|
37
|
+
);
|
|
38
|
+
event SwapFinalized(address indexed receiver, uint64 amount);
|
|
39
|
+
|
|
40
|
+
constructor(IERC7984 fromToken, IERC20 toToken) {
|
|
41
|
+
_fromToken = fromToken;
|
|
42
|
+
_toToken = toToken;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ==================== STEP 1: INITIATE ====================
|
|
46
|
+
|
|
47
|
+
/// @notice Start the swap - transfers ERC7984 and requests decryption
|
|
48
|
+
function initiateSwap(
|
|
49
|
+
externalEuint64 encryptedInput,
|
|
50
|
+
bytes calldata inputProof
|
|
51
|
+
) public {
|
|
52
|
+
euint64 amount = FHE.fromExternal(encryptedInput, inputProof);
|
|
53
|
+
|
|
54
|
+
// Transfer ERC7984 from user to this contract
|
|
55
|
+
FHE.allowTransient(amount, address(_fromToken));
|
|
56
|
+
euint64 amountTransferred = _fromToken.confidentialTransferFrom(
|
|
57
|
+
msg.sender,
|
|
58
|
+
address(this),
|
|
59
|
+
amount
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// 🔓 FHEVM v0.9: Request public decryption
|
|
63
|
+
// KMS will provide proof that this value decrypts to X
|
|
64
|
+
FHE.makePubliclyDecryptable(amountTransferred);
|
|
65
|
+
FHE.allowThis(amountTransferred);
|
|
66
|
+
|
|
67
|
+
// Register pending swap
|
|
68
|
+
_pendingSwaps[amountTransferred] = PendingSwap({
|
|
69
|
+
receiver: msg.sender,
|
|
70
|
+
pending: true
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
emit SwapInitiated(amountTransferred, msg.sender);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ==================== STEP 2: FINALIZE ====================
|
|
77
|
+
|
|
78
|
+
/// @notice Complete the swap with decryption proof from KMS
|
|
79
|
+
/// @dev encryptedAmount: The handle from initiateSwap
|
|
80
|
+
/// cleartextAmount: The decrypted value
|
|
81
|
+
/// decryptionProof: Proof from KMS that decryption is valid
|
|
82
|
+
function finalizeSwap(
|
|
83
|
+
euint64 encryptedAmount,
|
|
84
|
+
uint64 cleartextAmount,
|
|
85
|
+
bytes calldata decryptionProof
|
|
86
|
+
) public {
|
|
87
|
+
PendingSwap storage pending = _pendingSwaps[encryptedAmount];
|
|
88
|
+
require(pending.pending, InvalidSwap(encryptedAmount));
|
|
89
|
+
|
|
90
|
+
// 🔐 FHEVM v0.9: Verify decryption proof
|
|
91
|
+
// This ensures cleartextAmount is the TRUE decryption of encryptedAmount
|
|
92
|
+
bytes32[] memory handles = new bytes32[](1);
|
|
93
|
+
handles[0] = euint64.unwrap(encryptedAmount);
|
|
94
|
+
FHE.checkSignatures(
|
|
95
|
+
handles,
|
|
96
|
+
abi.encode(cleartextAmount),
|
|
97
|
+
decryptionProof
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
address receiver = pending.receiver;
|
|
101
|
+
delete _pendingSwaps[encryptedAmount];
|
|
102
|
+
|
|
103
|
+
// Release ERC20 to user
|
|
104
|
+
if (cleartextAmount != 0) {
|
|
105
|
+
_toToken.safeTransfer(receiver, cleartextAmount);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
emit SwapFinalized(receiver, cleartextAmount);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {
|
|
6
|
+
IERC7984
|
|
7
|
+
} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
|
|
8
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @notice Fully confidential swap between two ERC7984 tokens
|
|
12
|
+
*
|
|
13
|
+
* @dev Both input and output amounts remain encrypted throughout the swap.
|
|
14
|
+
*/
|
|
15
|
+
contract SwapERC7984ToERC7984Example is ZamaEthereumConfig {
|
|
16
|
+
/// @notice Swap confidential token for another confidential token
|
|
17
|
+
/// @dev fromToken: The token to swap from
|
|
18
|
+
/// toToken: The token to receive
|
|
19
|
+
/// amountInput: Encrypted amount to swap
|
|
20
|
+
/// inputProof: Proof for the encrypted input
|
|
21
|
+
function swapConfidentialForConfidential(
|
|
22
|
+
IERC7984 fromToken,
|
|
23
|
+
IERC7984 toToken,
|
|
24
|
+
externalEuint64 amountInput,
|
|
25
|
+
bytes calldata inputProof
|
|
26
|
+
) public virtual {
|
|
27
|
+
// 🔍 Check caller is operator for this contract
|
|
28
|
+
require(
|
|
29
|
+
fromToken.isOperator(msg.sender, address(this)),
|
|
30
|
+
"Not authorized operator"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
euint64 amount = FHE.fromExternal(amountInput, inputProof);
|
|
34
|
+
|
|
35
|
+
// 📥 Transfer fromToken: user → this contract
|
|
36
|
+
FHE.allowTransient(amount, address(fromToken));
|
|
37
|
+
euint64 amountTransferred = fromToken.confidentialTransferFrom(
|
|
38
|
+
msg.sender,
|
|
39
|
+
address(this),
|
|
40
|
+
amount
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// 📤 Transfer toToken: this contract → user
|
|
44
|
+
// Amount stays encrypted throughout!
|
|
45
|
+
FHE.allowTransient(amountTransferred, address(toToken));
|
|
46
|
+
toToken.confidentialTransfer(msg.sender, amountTransferred);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {FHE, ebool, euint64, euint128} from "@fhevm/solidity/lib/FHE.sol";
|
|
5
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
6
|
+
import {
|
|
7
|
+
ReentrancyGuardTransient
|
|
8
|
+
} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
|
|
9
|
+
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
|
|
10
|
+
import {
|
|
11
|
+
IERC7984
|
|
12
|
+
} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @notice Linear vesting wallet for ERC7984 tokens - amounts stay encrypted!
|
|
16
|
+
*
|
|
17
|
+
* @dev Timeline: |--START--|---VESTING---|--END--|
|
|
18
|
+
* 0% linear 100%
|
|
19
|
+
*
|
|
20
|
+
* All vesting calculations are performed on encrypted values using FHE operations.
|
|
21
|
+
*/
|
|
22
|
+
contract VestingWalletExample is
|
|
23
|
+
Ownable,
|
|
24
|
+
ReentrancyGuardTransient,
|
|
25
|
+
ZamaEthereumConfig
|
|
26
|
+
{
|
|
27
|
+
// 🔐 Track released amounts per token (encrypted)
|
|
28
|
+
mapping(address token => euint128) private _tokenReleased;
|
|
29
|
+
uint64 private _start;
|
|
30
|
+
uint64 private _duration;
|
|
31
|
+
|
|
32
|
+
/// @notice Emitted when vested tokens are released to beneficiary
|
|
33
|
+
/// @param token The ERC7984 token address
|
|
34
|
+
/// @param amount The encrypted amount released
|
|
35
|
+
event VestingWalletConfidentialTokenReleased(
|
|
36
|
+
address indexed token,
|
|
37
|
+
euint64 amount
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/// @notice Creates a new vesting wallet for a beneficiary
|
|
41
|
+
/// @param beneficiary Address that will receive vested tokens
|
|
42
|
+
/// @param startTimestamp Unix timestamp when vesting begins
|
|
43
|
+
/// @param durationSeconds Duration of the vesting period in seconds
|
|
44
|
+
constructor(
|
|
45
|
+
address beneficiary,
|
|
46
|
+
uint48 startTimestamp,
|
|
47
|
+
uint48 durationSeconds
|
|
48
|
+
) Ownable(beneficiary) {
|
|
49
|
+
_start = startTimestamp;
|
|
50
|
+
_duration = durationSeconds;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ==================== VIEW FUNCTIONS ====================
|
|
54
|
+
|
|
55
|
+
function start() public view virtual returns (uint64) {
|
|
56
|
+
return _start;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function duration() public view virtual returns (uint64) {
|
|
60
|
+
return _duration;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function end() public view virtual returns (uint64) {
|
|
64
|
+
return start() + duration();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// @notice Encrypted amount already released for token
|
|
68
|
+
function released(address token) public view virtual returns (euint128) {
|
|
69
|
+
return _tokenReleased[token];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ==================== CORE LOGIC ====================
|
|
73
|
+
|
|
74
|
+
/// @notice Calculate how much can be released now
|
|
75
|
+
/// @dev Returns encrypted amount - no one knows the actual value
|
|
76
|
+
function releasable(address token) public virtual returns (euint64) {
|
|
77
|
+
euint128 vestedAmount_ = vestedAmount(token, uint48(block.timestamp));
|
|
78
|
+
euint128 releasedAmount = released(token);
|
|
79
|
+
|
|
80
|
+
// 🔀 FHE.select: encrypted if-else
|
|
81
|
+
// If vested >= released: return (vested - released)
|
|
82
|
+
// Else: return 0
|
|
83
|
+
ebool canRelease = FHE.ge(vestedAmount_, releasedAmount);
|
|
84
|
+
return
|
|
85
|
+
FHE.select(
|
|
86
|
+
canRelease,
|
|
87
|
+
FHE.asEuint64(FHE.sub(vestedAmount_, releasedAmount)),
|
|
88
|
+
FHE.asEuint64(0)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// @notice Release vested tokens to beneficiary
|
|
93
|
+
function release(address token) public virtual nonReentrant {
|
|
94
|
+
euint64 amount = releasable(token);
|
|
95
|
+
|
|
96
|
+
// Transfer encrypted amount to owner
|
|
97
|
+
FHE.allowTransient(amount, token);
|
|
98
|
+
euint64 amountSent = IERC7984(token).confidentialTransfer(
|
|
99
|
+
owner(),
|
|
100
|
+
amount
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Update released amount (encrypted)
|
|
104
|
+
euint128 newReleasedAmount = FHE.add(released(token), amountSent);
|
|
105
|
+
FHE.allow(newReleasedAmount, owner());
|
|
106
|
+
FHE.allowThis(newReleasedAmount);
|
|
107
|
+
_tokenReleased[token] = newReleasedAmount;
|
|
108
|
+
|
|
109
|
+
emit VestingWalletConfidentialTokenReleased(token, amountSent);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// @notice Calculate vested amount at timestamp
|
|
113
|
+
function vestedAmount(
|
|
114
|
+
address token,
|
|
115
|
+
uint48 timestamp
|
|
116
|
+
) public virtual returns (euint128) {
|
|
117
|
+
// Total = released + current balance
|
|
118
|
+
euint128 totalAllocation = FHE.add(
|
|
119
|
+
released(token),
|
|
120
|
+
IERC7984(token).confidentialBalanceOf(address(this))
|
|
121
|
+
);
|
|
122
|
+
return _vestingSchedule(totalAllocation, timestamp);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ==================== INTERNAL ====================
|
|
126
|
+
|
|
127
|
+
/// @dev Linear vesting: (total * elapsed) / duration
|
|
128
|
+
function _vestingSchedule(
|
|
129
|
+
euint128 totalAllocation,
|
|
130
|
+
uint48 timestamp
|
|
131
|
+
) internal virtual returns (euint128) {
|
|
132
|
+
if (timestamp < start()) {
|
|
133
|
+
// Before start: 0% vested
|
|
134
|
+
return euint128.wrap(0);
|
|
135
|
+
} else if (timestamp >= end()) {
|
|
136
|
+
// After end: 100% vested
|
|
137
|
+
return totalAllocation;
|
|
138
|
+
} else {
|
|
139
|
+
// During vesting: linear unlock
|
|
140
|
+
return
|
|
141
|
+
FHE.div(
|
|
142
|
+
FHE.mul(totalAllocation, (timestamp - start())),
|
|
143
|
+
duration()
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @notice Simple ERC20 mock for testing purposes
|
|
8
|
+
*/
|
|
9
|
+
contract ERC20Mock is ERC20 {
|
|
10
|
+
uint8 private _decimals;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
string memory name_,
|
|
14
|
+
string memory symbol_,
|
|
15
|
+
uint8 decimals_
|
|
16
|
+
) ERC20(name_, symbol_) {
|
|
17
|
+
_decimals = decimals_;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function decimals() public view override returns (uint8) {
|
|
21
|
+
return _decimals;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mint(address to, uint256 amount) external {
|
|
25
|
+
_mint(to, amount);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function burn(address from, uint256 amount) external {
|
|
29
|
+
_burn(from, amount);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +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"}
|