@crisp-e3/contracts 0.5.10 → 0.5.12
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.
|
@@ -49,13 +49,11 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
49
49
|
HonkVerifier private immutable honkVerifier;
|
|
50
50
|
|
|
51
51
|
// Mappings
|
|
52
|
-
mapping(address => bool) public authorizedContracts;
|
|
53
52
|
mapping(uint256 e3Id => RoundData) e3Data;
|
|
54
53
|
|
|
55
54
|
// Errors
|
|
56
55
|
error CallerNotAuthorized();
|
|
57
56
|
error E3AlreadyInitialized();
|
|
58
|
-
error E3DoesNotExist();
|
|
59
57
|
error EnclaveAddressZero();
|
|
60
58
|
error Risc0VerifierAddressZero();
|
|
61
59
|
error InvalidHonkVerifier();
|
|
@@ -67,6 +65,9 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
67
65
|
error SlotIsEmpty();
|
|
68
66
|
error MerkleRootNotSet();
|
|
69
67
|
error InvalidNumOptions();
|
|
68
|
+
error InputDeadlinePassed(uint256 e3Id, uint256 deadline);
|
|
69
|
+
error KeyNotPublished(uint256 e3Id);
|
|
70
|
+
error E3NotAcceptingInputs(uint256 e3Id);
|
|
70
71
|
|
|
71
72
|
// Events
|
|
72
73
|
event InputPublished(uint256 indexed e3Id, bytes encryptedVote, uint256 index);
|
|
@@ -84,7 +85,6 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
84
85
|
enclave = _enclave;
|
|
85
86
|
risc0Verifier = _risc0Verifier;
|
|
86
87
|
honkVerifier = _honkVerifier;
|
|
87
|
-
authorizedContracts[address(_enclave)] = true;
|
|
88
88
|
imageId = _imageId;
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -126,7 +126,7 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
126
126
|
bytes calldata,
|
|
127
127
|
bytes calldata customParams
|
|
128
128
|
) external returns (bytes32) {
|
|
129
|
-
if (
|
|
129
|
+
if (msg.sender != address(enclave) && msg.sender != owner()) revert CallerNotAuthorized();
|
|
130
130
|
if (e3Data[e3Id].paramsHash != bytes32(0)) revert E3AlreadyInitialized();
|
|
131
131
|
|
|
132
132
|
// decode custom params to get the number of options
|
|
@@ -135,7 +135,7 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
135
135
|
|
|
136
136
|
// we need to know the number of options for decoding the tally
|
|
137
137
|
e3Data[e3Id].numOptions = numOptions;
|
|
138
|
-
// we want to save the credit
|
|
138
|
+
// we want to save the credit mode so it can be verified on chain by everyone
|
|
139
139
|
e3Data[e3Id].creditMode = creditMode;
|
|
140
140
|
|
|
141
141
|
e3Data[e3Id].paramsHash = keccak256(e3ProgramParams);
|
|
@@ -147,9 +147,24 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/// @inheritdoc IE3Program
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
function publishInput(uint256 e3Id, bytes memory data) external {
|
|
151
|
+
E3 memory e3 = enclave.getE3(e3Id);
|
|
152
|
+
|
|
153
|
+
// check that we are in the correct stage
|
|
154
|
+
IEnclave.E3Stage stage = enclave.getE3Stage(e3Id);
|
|
155
|
+
if (stage != IEnclave.E3Stage.KeyPublished) {
|
|
156
|
+
revert KeyNotPublished(e3Id);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// check that we are not past the input deadline
|
|
160
|
+
if (block.timestamp > e3.inputWindow[1]) {
|
|
161
|
+
revert InputDeadlinePassed(e3Id, e3.inputWindow[1]);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// check that we are within the input window
|
|
165
|
+
if (block.timestamp < e3.inputWindow[0]) {
|
|
166
|
+
revert E3NotAcceptingInputs(e3Id);
|
|
167
|
+
}
|
|
153
168
|
|
|
154
169
|
// We need to ensure that the CRISP admin set the merkle root of the census.
|
|
155
170
|
if (e3Data[e3Id].merkleRoot == 0) revert MerkleRootNotSet();
|
|
@@ -163,9 +178,6 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
163
178
|
|
|
164
179
|
(uint40 voteIndex, bytes32 previousEncryptedVoteCommitment) = _processVote(e3Id, slotAddress, encryptedVoteCommitment);
|
|
165
180
|
|
|
166
|
-
// Fetch E3 to get committee public key
|
|
167
|
-
E3 memory e3 = enclave.getE3(e3Id);
|
|
168
|
-
|
|
169
181
|
// Set the public inputs for the proof. Order must match Noir circuit.
|
|
170
182
|
bytes32[] memory noirPublicInputs = new bytes32[](7);
|
|
171
183
|
noirPublicInputs[0] = previousEncryptedVoteCommitment;
|
|
@@ -244,12 +256,13 @@ contract CRISPProgram is IE3Program, Ownable {
|
|
|
244
256
|
|
|
245
257
|
/// @inheritdoc IE3Program
|
|
246
258
|
function verify(uint256 e3Id, bytes32 ciphertextOutputHash, bytes memory proof) external view override returns (bool) {
|
|
247
|
-
|
|
259
|
+
bytes32 paramsHash = getParamsHash(e3Id);
|
|
260
|
+
|
|
248
261
|
bytes32 inputRoot = bytes32(e3Data[e3Id].votes._root(TREE_DEPTH));
|
|
249
262
|
bytes memory journal = new bytes(396); // (32 + 1) * 4 * 3
|
|
250
263
|
|
|
251
264
|
_encodeLengthPrefixAndHash(journal, 0, ciphertextOutputHash);
|
|
252
|
-
_encodeLengthPrefixAndHash(journal, 132,
|
|
265
|
+
_encodeLengthPrefixAndHash(journal, 132, paramsHash);
|
|
253
266
|
_encodeLengthPrefixAndHash(journal, 264, inputRoot);
|
|
254
267
|
|
|
255
268
|
risc0Verifier.verify(proof, imageId, sha256(journal));
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
// SPDX-License-Identifier:
|
|
2
|
-
//
|
|
1
|
+
// SPDX-License-Identifier: LGPL-3.0-only
|
|
2
|
+
//
|
|
3
|
+
// This file is provided WITHOUT ANY WARRANTY;
|
|
4
|
+
// without even the implied warranty of MERCHANTABILITY
|
|
5
|
+
// or FITNESS FOR A PARTICULAR PURPOSE.
|
|
3
6
|
pragma solidity >=0.8.21;
|
|
4
7
|
|
|
5
8
|
uint256 constant N = 524288;
|
|
6
9
|
uint256 constant LOG_N = 19;
|
|
7
10
|
uint256 constant NUMBER_OF_PUBLIC_INPUTS = 23;
|
|
8
|
-
uint256 constant VK_HASH =
|
|
11
|
+
uint256 constant VK_HASH = 0x0b9c1b2dd5f380cd949f20a3a3f09c947df53c467285680777412ea4868a8d18;
|
|
9
12
|
library HonkVerificationKey {
|
|
10
13
|
function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) {
|
|
11
14
|
Honk.VerificationKey memory vk = Honk.VerificationKey({
|
|
@@ -13,76 +16,76 @@ library HonkVerificationKey {
|
|
|
13
16
|
logCircuitSize: uint256(19),
|
|
14
17
|
publicInputsSize: uint256(23),
|
|
15
18
|
ql: Honk.G1Point({
|
|
16
|
-
x: uint256(
|
|
17
|
-
y: uint256(
|
|
19
|
+
x: uint256(0x240f688502a6acfbcaeaaa0846cd10fca763cc54deb740784eb4d79ed50a478e),
|
|
20
|
+
y: uint256(0x1f085373708e94340f4f4741a11126be4f87589358cebb953ee6dddd1dca1172)
|
|
18
21
|
}),
|
|
19
22
|
qr: Honk.G1Point({
|
|
20
|
-
x: uint256(
|
|
21
|
-
y: uint256(
|
|
23
|
+
x: uint256(0x03ca30044543a7c02e08505b4ed256125807d7bda68712c65bd674e7be18907e),
|
|
24
|
+
y: uint256(0x223b9b4b0bfd2328aa898531ee94dde61fcaa9133773dc33184e167bb33fb343)
|
|
22
25
|
}),
|
|
23
26
|
qo: Honk.G1Point({
|
|
24
|
-
x: uint256(
|
|
25
|
-
y: uint256(
|
|
27
|
+
x: uint256(0x01df0faac1b219c2932d044ff18952ccfdb0da9f3ca8d6d98d19d3b5dcaa75a8),
|
|
28
|
+
y: uint256(0x2676231938eab7bf9e8b609d039dc7f0e9c28c8f94cf0ffcc8ce0ee2c40966e5)
|
|
26
29
|
}),
|
|
27
30
|
q4: Honk.G1Point({
|
|
28
|
-
x: uint256(
|
|
29
|
-
y: uint256(
|
|
31
|
+
x: uint256(0x05150a77def0a11380c5a171f15aac571dc0737a55aeddde2fdb147742cfccd3),
|
|
32
|
+
y: uint256(0x1fab3e3345368910a19c18ce2c9649a27cc721d406fdec42e02204ac277161cb)
|
|
30
33
|
}),
|
|
31
34
|
qm: Honk.G1Point({
|
|
32
|
-
x: uint256(
|
|
33
|
-
y: uint256(
|
|
35
|
+
x: uint256(0x264abf2ea58ab66dc9310c0d6360bd1fe8d7408259cedfbbeb119d6004583d53),
|
|
36
|
+
y: uint256(0x089c345678f0ed3d16009853498f4189927c1c49eaab388e535b4ed5abc179ba)
|
|
34
37
|
}),
|
|
35
38
|
qc: Honk.G1Point({
|
|
36
|
-
x: uint256(
|
|
37
|
-
y: uint256(
|
|
39
|
+
x: uint256(0x24bfc2a0618dd709363c0f726d12b55f556749f83ce5b7ccdfe214d2761cca2e),
|
|
40
|
+
y: uint256(0x237c95ed74cf4ba0eedec3e8172cc943b4ea2b171806c3a528d1f13f83816180)
|
|
38
41
|
}),
|
|
39
42
|
qLookup: Honk.G1Point({
|
|
40
43
|
x: uint256(0x136236e1bfc2af648ac078e134c1b4b9114b11937ebafcdd87f8ca7660715ebb),
|
|
41
44
|
y: uint256(0x02293c705250462935a653b7b993e13e2e8bc6480c45c84976d526cbdbd071df)
|
|
42
45
|
}),
|
|
43
46
|
qArith: Honk.G1Point({
|
|
44
|
-
x: uint256(
|
|
45
|
-
y: uint256(
|
|
47
|
+
x: uint256(0x1f15325c29f480d9fa82d2c7ed4994844919e3d613ba98aca98155abbe8af1d0),
|
|
48
|
+
y: uint256(0x1eaf5badfb1f6604278dbe5b8cc935bc0cf19eaef7b77c7ca2f231a65ca94c83)
|
|
46
49
|
}),
|
|
47
50
|
qDeltaRange: Honk.G1Point({
|
|
48
|
-
x: uint256(
|
|
49
|
-
y: uint256(
|
|
51
|
+
x: uint256(0x01319d2844fd5491e4140d6f0c43f4b708e1192c5bbdd2c0a53a7da62983efd8),
|
|
52
|
+
y: uint256(0x164d72be753ed477363f4af7c5e0110d46c83ec7ebf4b753eb228e4f42b37ac8)
|
|
50
53
|
}),
|
|
51
54
|
qElliptic: Honk.G1Point({
|
|
52
|
-
x: uint256(
|
|
53
|
-
y: uint256(
|
|
55
|
+
x: uint256(0x0672a9cbd7c1f9768e8d307bdd295145b3547b2783065b521a23487b927c0cfb),
|
|
56
|
+
y: uint256(0x1252d1d2d8b7841d1e92ae3167104d924eabd7607bb5a655ad42cc3fedcd25f3)
|
|
54
57
|
}),
|
|
55
58
|
qMemory: Honk.G1Point({
|
|
56
|
-
x: uint256(
|
|
57
|
-
y: uint256(
|
|
59
|
+
x: uint256(0x23d2fc90f4b3243cf25c79e052fddb5489397999ed49771b59da5ac27677647b),
|
|
60
|
+
y: uint256(0x0523ed03b53e8f80c7588938b2e5487f6cc4a63918d3f1263cfab7a7ad61737d)
|
|
58
61
|
}),
|
|
59
62
|
qNnf: Honk.G1Point({
|
|
60
|
-
x: uint256(
|
|
61
|
-
y: uint256(
|
|
63
|
+
x: uint256(0x0b3512e728f8ace54f9a6d83334f4f35e073c641b843a6ac52d4d99917b02349),
|
|
64
|
+
y: uint256(0x1c89ed6a83ea8bdce1e560ab0204ee0939e1784dffc427cc11e321b61bed74c6)
|
|
62
65
|
}),
|
|
63
66
|
qPoseidon2External: Honk.G1Point({
|
|
64
|
-
x: uint256(
|
|
65
|
-
y: uint256(
|
|
67
|
+
x: uint256(0x0b6006bc7b12f44177d3b59d197fe11d2fc41599f6f1fde44c7f268dd32d337e),
|
|
68
|
+
y: uint256(0x0c45abdec36bd5b61f2edca097daccb7a81455a2d88496a6d1a72aef33b8b7b0)
|
|
66
69
|
}),
|
|
67
70
|
qPoseidon2Internal: Honk.G1Point({
|
|
68
|
-
x: uint256(
|
|
69
|
-
y: uint256(
|
|
71
|
+
x: uint256(0x0be8cb27fcd19968dfa54fd667491a0e95b7a92518a9a82b506dfdd9d8bb0323),
|
|
72
|
+
y: uint256(0x01c52d04c4d69fcaecdacb0b9dba750320704ae6375f8ef985a9fc2cb555ac48)
|
|
70
73
|
}),
|
|
71
74
|
s1: Honk.G1Point({
|
|
72
|
-
x: uint256(
|
|
73
|
-
y: uint256(
|
|
75
|
+
x: uint256(0x1a7257ea10514736801482f89408f5925618409f43bf217880f9ba8564708b3c),
|
|
76
|
+
y: uint256(0x21bdeab9c20c3e3d0727429b2a6efdbd7473e1b239ef02d9581de8499444a82c)
|
|
74
77
|
}),
|
|
75
78
|
s2: Honk.G1Point({
|
|
76
|
-
x: uint256(
|
|
77
|
-
y: uint256(
|
|
79
|
+
x: uint256(0x1cdd60998e69b08a3178183583434d5d1747fe2b59d53198cee0189184ef4137),
|
|
80
|
+
y: uint256(0x0e1ec93697a66ada78411a4343a73a94162688bc96ceec69e06b6b292ad6ae15)
|
|
78
81
|
}),
|
|
79
82
|
s3: Honk.G1Point({
|
|
80
|
-
x: uint256(
|
|
81
|
-
y: uint256(
|
|
83
|
+
x: uint256(0x2e3739fca228a028616288bded555bda201546582e0fbfb15ba1d79ecedb99ff),
|
|
84
|
+
y: uint256(0x2d3fa85df312d814ce05e6424c0789aabd29780e2751d06191b5c19385c495e8)
|
|
82
85
|
}),
|
|
83
86
|
s4: Honk.G1Point({
|
|
84
|
-
x: uint256(
|
|
85
|
-
y: uint256(
|
|
87
|
+
x: uint256(0x2cc67ab5542d9dcb11e2f47c61991d3b8c1530be0fb65ae78bb579575472be37),
|
|
88
|
+
y: uint256(0x1a9453a6379fcd5d9357de3c3c92f09a37a566d01452ccbc053a79cbf5d3062b)
|
|
86
89
|
}),
|
|
87
90
|
t1: Honk.G1Point({
|
|
88
91
|
x: uint256(0x1f16b037f0b4c96ea2a30a118a44e139881c0db8a4d6c9fde7db5c1c1738e61f),
|
|
@@ -101,28 +104,28 @@ library HonkVerificationKey {
|
|
|
101
104
|
y: uint256(0x2d7e8c1ecb92e2490049b50efc811df63f1ca97e58d5e82852dbec0c29715d71)
|
|
102
105
|
}),
|
|
103
106
|
id1: Honk.G1Point({
|
|
104
|
-
x: uint256(
|
|
105
|
-
y: uint256(
|
|
107
|
+
x: uint256(0x242bbe50c5583ccc277f71ef51aa243eda251285787baac14b27a1f4cc411edf),
|
|
108
|
+
y: uint256(0x154f6eb3c9511184ca082c61b12292332f5a4d3a20f6612c8b337b988df6441e)
|
|
106
109
|
}),
|
|
107
110
|
id2: Honk.G1Point({
|
|
108
|
-
x: uint256(
|
|
109
|
-
y: uint256(
|
|
111
|
+
x: uint256(0x28516035c0e7f925849fc922c1cd3cc13d6da9c37e723131dbff7c7bb672d762),
|
|
112
|
+
y: uint256(0x25a236e395d2e5b5b6bfa4c732982c53aeb626cd4f84624ced88724a2b271820)
|
|
110
113
|
}),
|
|
111
114
|
id3: Honk.G1Point({
|
|
112
|
-
x: uint256(
|
|
113
|
-
y: uint256(
|
|
115
|
+
x: uint256(0x296663882374f237a8fc9915de61b3a2f558a731cb10204dfa257273d90f6b93),
|
|
116
|
+
y: uint256(0x2b20baa201e9ddcbbf5eba8a2d5cff0cb1b5e0ba96be4bbd3b0da781cca7b47d)
|
|
114
117
|
}),
|
|
115
118
|
id4: Honk.G1Point({
|
|
116
|
-
x: uint256(
|
|
117
|
-
y: uint256(
|
|
119
|
+
x: uint256(0x1438d3dc0bd7d25324cfddbbdbe88df7782c33920c2276cafa2b5e316639721b),
|
|
120
|
+
y: uint256(0x0bc0958c27e76138920ac5aa682f2b724456ebf2bd169a863244b9cc153639db)
|
|
118
121
|
}),
|
|
119
122
|
lagrangeFirst: Honk.G1Point({
|
|
120
123
|
x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001),
|
|
121
124
|
y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002)
|
|
122
125
|
}),
|
|
123
126
|
lagrangeLast: Honk.G1Point({
|
|
124
|
-
x: uint256(
|
|
125
|
-
y: uint256(
|
|
127
|
+
x: uint256(0x0ffb0f958447e12cda902c71cb70f85d2ebfcd4b93f50569c2f00eff7e60368e),
|
|
128
|
+
y: uint256(0x1230b28595aee83bc86d1c7fb3d0fc726604d0e86455cf648b9ddaa89c173ce4)
|
|
126
129
|
})
|
|
127
130
|
});
|
|
128
131
|
return vk;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
pragma solidity >=0.8.27;
|
|
7
7
|
|
|
8
8
|
import { E3 } from "@enclave-e3/contracts/contracts/interfaces/IE3.sol";
|
|
9
|
+
import { IEnclave } from "@enclave-e3/contracts/contracts/interfaces/IEnclave.sol";
|
|
9
10
|
import { IE3Program } from "@enclave-e3/contracts/contracts/interfaces/IE3Program.sol";
|
|
10
11
|
import { IDecryptionVerifier } from "@enclave-e3/contracts/contracts/interfaces/IDecryptionVerifier.sol";
|
|
11
12
|
|
|
@@ -22,11 +23,9 @@ contract MockEnclave {
|
|
|
22
23
|
seed: 0,
|
|
23
24
|
threshold: [uint32(1), uint32(2)],
|
|
24
25
|
requestBlock: 0,
|
|
25
|
-
|
|
26
|
-
duration: 0,
|
|
27
|
-
expiration: 0,
|
|
26
|
+
inputWindow: [uint256(0), uint256(0)],
|
|
28
27
|
encryptionSchemeId: bytes32(0),
|
|
29
|
-
e3Program: IE3Program(
|
|
28
|
+
e3Program: IE3Program(address(0)),
|
|
30
29
|
e3ProgramParams: bytes(""),
|
|
31
30
|
customParams: abi.encode(address(0), nextE3Id, 2, 0, 0),
|
|
32
31
|
decryptionVerifier: IDecryptionVerifier(address(0)),
|
|
@@ -36,7 +35,7 @@ contract MockEnclave {
|
|
|
36
35
|
requester: address(0)
|
|
37
36
|
});
|
|
38
37
|
|
|
39
|
-
IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2));
|
|
38
|
+
IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2, 0, 0));
|
|
40
39
|
|
|
41
40
|
nextE3Id++;
|
|
42
41
|
}
|
|
@@ -49,15 +48,17 @@ contract MockEnclave {
|
|
|
49
48
|
committeePublicKey = publicKeyHash;
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
function getE3Stage(uint256) external view returns (IEnclave.E3Stage) {
|
|
52
|
+
return IEnclave.E3Stage.KeyPublished;
|
|
53
|
+
}
|
|
54
|
+
|
|
52
55
|
function getE3(uint256) external view returns (E3 memory) {
|
|
53
56
|
return
|
|
54
57
|
E3({
|
|
55
58
|
seed: 0,
|
|
56
59
|
threshold: [uint32(1), uint32(2)],
|
|
57
60
|
requestBlock: 0,
|
|
58
|
-
|
|
59
|
-
duration: 0,
|
|
60
|
-
expiration: 0,
|
|
61
|
+
inputWindow: [uint256(0), block.timestamp + 100],
|
|
61
62
|
encryptionSchemeId: bytes32(0),
|
|
62
63
|
e3Program: IE3Program(address(0)),
|
|
63
64
|
e3ProgramParams: bytes(""),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crisp-e3/contracts",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"contracts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@zk-kit/lazy-imt.sol": "2.0.0-beta.12",
|
|
32
32
|
"poseidon-solidity": "^0.0.5",
|
|
33
33
|
"solady": "^0.1.13",
|
|
34
|
-
"@enclave-e3/contracts": "0.1.
|
|
34
|
+
"@enclave-e3/contracts": "0.1.14"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@nomicfoundation/hardhat-ethers": "4",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"typechain": "^8.3.0",
|
|
60
60
|
"typescript": "5.8.3",
|
|
61
61
|
"viem": "2.30.6",
|
|
62
|
-
"@crisp-e3/zk-inputs": "^0.5.
|
|
63
|
-
"@crisp-e3/sdk": "^0.5.
|
|
62
|
+
"@crisp-e3/zk-inputs": "^0.5.12",
|
|
63
|
+
"@crisp-e3/sdk": "^0.5.12"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"compile": "hardhat compile",
|