1
+ import { expect } from "chai" ;
2
+ import { ethers , upgrades } from "hardhat" ;
3
+ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers" ;
4
+ import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" ;
5
+ import { TokenVault , XXXToken } from "../typechain-types" ;
6
+
7
+ describe ( "TokenVault" , function ( ) {
8
+ // Role identifiers
9
+ const ALLOCATOR_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "ALLOCATOR_ROLE" ) ) ;
10
+ const AIRDROP_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "AIRDROP_ROLE" ) ) ;
11
+ const UPGRADER_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "UPGRADER_ROLE" ) ) ;
12
+
13
+ // Test accounts
14
+ let owner : SignerWithAddress ;
15
+ let allocator : SignerWithAddress ;
16
+ let airdropper : SignerWithAddress ;
17
+ let vestingManager : SignerWithAddress ;
18
+ let beneficiary1 : SignerWithAddress ;
19
+ let beneficiary2 : SignerWithAddress ;
20
+ let user : SignerWithAddress ;
21
+
22
+ // Contract instances
23
+ let token : XXXToken ;
24
+ let vault : TokenVault ;
25
+
26
+ async function deployVaultFixture ( ) {
27
+ [ owner , allocator , airdropper , vestingManager , beneficiary1 , beneficiary2 , user ] = await ethers . getSigners ( ) ;
28
+
29
+ // Deploy token
30
+ const XXXToken = await ethers . getContractFactory ( "XXXToken" ) ;
31
+ const token = await upgrades . deployProxy ( XXXToken , [ ] , { initializer : 'initialize' } ) ;
32
+ await token . waitForDeployment ( ) ;
33
+
34
+ // Deploy vault
35
+ const TokenVault = await ethers . getContractFactory ( "TokenVault" ) ;
36
+ const vault = await upgrades . deployProxy ( TokenVault , [ await token . getAddress ( ) ] , { initializer : 'initialize' } ) ;
37
+ await vault . waitForDeployment ( ) ;
38
+
39
+ // Grant all roles to the owner (admin)
40
+ const MINTER_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "MINTER_ROLE" ) ) ;
41
+ const ALLOCATOR_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "ALLOCATOR_ROLE" ) ) ;
42
+ const AIRDROP_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "AIRDROP_ROLE" ) ) ;
43
+ const UPGRADER_ROLE = ethers . keccak256 ( ethers . toUtf8Bytes ( "UPGRADER_ROLE" ) ) ;
44
+ const DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000" ;
45
+
46
+ // Grant MINTER_ROLE to the vault contract
47
+ await token . grantRole ( MINTER_ROLE , await vault . getAddress ( ) ) ;
48
+
49
+ // Grant all other roles to the owner
50
+ await vault . grantRole ( DEFAULT_ADMIN_ROLE , owner . address ) ;
51
+ await vault . grantRole ( ALLOCATOR_ROLE , owner . address ) ;
52
+ await vault . grantRole ( AIRDROP_ROLE , owner . address ) ;
53
+ await vault . grantRole ( UPGRADER_ROLE , owner . address ) ;
54
+
55
+ return { token, vault, owner, allocator, airdropper, vestingManager, beneficiary1, beneficiary2, user } ;
56
+ }
57
+
58
+ beforeEach ( async function ( ) {
59
+ ( { token, vault, owner, allocator, airdropper, vestingManager, beneficiary1, beneficiary2, user } =
60
+ await loadFixture ( deployVaultFixture ) ) ;
61
+ } ) ;
62
+
63
+ describe ( "Deployment" , function ( ) {
64
+ it ( "Should set the correct token address" , async function ( ) {
65
+ expect ( await vault . ttnToken ( ) ) . to . equal ( await token . getAddress ( ) ) ;
66
+ } ) ;
67
+
68
+ it ( "Should assign the correct roles" , async function ( ) {
69
+ expect ( await vault . hasRole ( ALLOCATOR_ROLE , owner . address ) ) . to . be . true ;
70
+ expect ( await vault . hasRole ( AIRDROP_ROLE , owner . address ) ) . to . be . true ;
71
+ expect ( await vault . hasRole ( UPGRADER_ROLE , owner . address ) ) . to . be . true ;
72
+ } ) ;
73
+
74
+ it ( "Should initialize counters to zero" , async function ( ) {
75
+ expect ( await vault . getAllocationsForBeneficiary ( beneficiary1 . address ) ) . to . be . empty ;
76
+ } ) ;
77
+ } ) ;
78
+
79
+ describe ( "Vesting Manager" , function ( ) {
80
+ it ( "Should allow admin to set vesting manager" , async function ( ) {
81
+ await vault . setVestingManager ( vestingManager . address ) ;
82
+ expect ( await vault . vestingManager ( ) ) . to . equal ( vestingManager . address ) ;
83
+ } ) ;
84
+
85
+ it ( "Should not allow non-admin to set vesting manager" , async function ( ) {
86
+ await expect (
87
+ vault . connect ( user ) . setVestingManager ( vestingManager . address )
88
+ ) . to . be . revertedWithCustomError ( vault , "AccessControlUnauthorizedAccount" ) ;
89
+ } ) ;
90
+
91
+ it ( "Should not allow setting zero address as vesting manager" , async function ( ) {
92
+ await expect (
93
+ vault . setVestingManager ( ethers . ZeroAddress )
94
+ ) . to . be . revertedWithCustomError ( vault , "ZeroAddress" ) ;
95
+ } ) ;
96
+ } ) ;
97
+
98
+ describe ( "Allocations" , function ( ) {
99
+ it ( "Should allow allocator to create allocation" , async function ( ) {
100
+ const amount = ethers . parseEther ( "1000" ) ;
101
+ await vault . connect ( owner ) . createAllocation ( beneficiary1 . address , amount ) ;
102
+
103
+ const allocations = await vault . getAllocationsForBeneficiary ( beneficiary1 . address ) ;
104
+ expect ( allocations . length ) . to . equal ( 1 ) ;
105
+
106
+ const allocation = await vault . allocations ( allocations [ 0 ] ) ;
107
+ expect ( allocation . amount ) . to . equal ( amount ) ;
108
+ expect ( allocation . beneficiary ) . to . equal ( beneficiary1 . address ) ;
109
+ expect ( allocation . revoked ) . to . be . false ;
110
+ } ) ;
111
+
112
+ it ( "Should not allow non-allocator to create allocation" , async function ( ) {
113
+ await expect (
114
+ vault . connect ( user ) . createAllocation ( beneficiary1 . address , ethers . parseEther ( "1000" ) )
115
+ ) . to . be . revertedWithCustomError ( vault , "AccessControlUnauthorizedAccount" ) ;
116
+ } ) ;
117
+
118
+ it ( "Should not allow creating allocation with zero amount" , async function ( ) {
119
+ await expect (
120
+ vault . connect ( owner ) . createAllocation ( beneficiary1 . address , 0 )
121
+ ) . to . be . revertedWithCustomError ( vault , "InvalidAmount" ) ;
122
+ } ) ;
123
+
124
+ it ( "Should not allow creating allocation for zero address" , async function ( ) {
125
+ await expect (
126
+ vault . connect ( owner ) . createAllocation ( ethers . ZeroAddress , ethers . parseEther ( "1000" ) )
127
+ ) . to . be . revertedWithCustomError ( vault , "InvalidBeneficiary" ) ;
128
+ } ) ;
129
+
130
+ it ( "Should allow allocator to revoke allocation" , async function ( ) {
131
+ const amount = ethers . parseEther ( "1000" ) ;
132
+ await vault . connect ( owner ) . createAllocation ( beneficiary1 . address , amount ) ;
133
+
134
+ const allocations = await vault . getAllocationsForBeneficiary ( beneficiary1 . address ) ;
135
+ await vault . connect ( owner ) . revokeAllocation ( allocations [ 0 ] ) ;
136
+
137
+ const allocation = await vault . allocations ( allocations [ 0 ] ) ;
138
+ expect ( allocation . revoked ) . to . be . true ;
139
+ } ) ;
140
+
141
+ it ( "Should not allow revoking non-existent allocation" , async function ( ) {
142
+ await expect (
143
+ vault . connect ( owner ) . revokeAllocation ( 999 )
144
+ ) . to . be . revertedWithCustomError ( vault , "InvalidAllocationId" ) ;
145
+ } ) ;
146
+
147
+ it ( "Should not allow revoking already revoked allocation" , async function ( ) {
148
+ const amount = ethers . parseEther ( "1000" ) ;
149
+ await vault . connect ( owner ) . createAllocation ( beneficiary1 . address , amount ) ;
150
+
151
+ const allocations = await vault . getAllocationsForBeneficiary ( beneficiary1 . address ) ;
152
+ await vault . connect ( owner ) . revokeAllocation ( allocations [ 0 ] ) ;
153
+
154
+ await expect (
155
+ vault . connect ( owner ) . revokeAllocation ( allocations [ 0 ] )
156
+ ) . to . be . revertedWithCustomError ( vault , "AllocationAlreadyRevoked" ) ;
157
+ } ) ;
158
+ } ) ;
159
+
160
+ describe ( "Airdrops" , function ( ) {
161
+ it ( "Should allow airdropper to execute airdrop" , async function ( ) {
162
+ const beneficiaries = [ beneficiary1 . address , beneficiary2 . address ] ;
163
+ const amounts = [ ethers . parseEther ( "1000" ) , ethers . parseEther ( "2000" ) ] ;
164
+
165
+ await vault . connect ( owner ) . executeAirdrop ( beneficiaries , amounts ) ;
166
+
167
+ const allocations1 = await vault . getAllocationsForBeneficiary ( beneficiary1 . address ) ;
168
+ const allocations2 = await vault . getAllocationsForBeneficiary ( beneficiary2 . address ) ;
169
+
170
+ expect ( allocations1 . length ) . to . equal ( 1 ) ;
171
+ expect ( allocations2 . length ) . to . equal ( 1 ) ;
172
+
173
+ const allocation1 = await vault . allocations ( allocations1 [ 0 ] ) ;
174
+ const allocation2 = await vault . allocations ( allocations2 [ 0 ] ) ;
175
+
176
+ expect ( allocation1 . amount ) . to . equal ( amounts [ 0 ] ) ;
177
+ expect ( allocation2 . amount ) . to . equal ( amounts [ 1 ] ) ;
178
+ } ) ;
179
+
180
+ it ( "Should not allow non-airdropper to execute airdrop" , async function ( ) {
181
+ const beneficiaries = [ beneficiary1 . address ] ;
182
+ const amounts = [ ethers . parseEther ( "1000" ) ] ;
183
+
184
+ await expect (
185
+ vault . connect ( user ) . executeAirdrop ( beneficiaries , amounts )
186
+ ) . to . be . revertedWithCustomError ( vault , "AccessControlUnauthorizedAccount" ) ;
187
+ } ) ;
188
+
189
+ it ( "Should not allow airdrop with empty beneficiaries list" , async function ( ) {
190
+ await expect (
191
+ vault . connect ( owner ) . executeAirdrop ( [ ] , [ ] )
192
+ ) . to . be . revertedWithCustomError ( vault , "EmptyBeneficiariesList" ) ;
193
+ } ) ;
194
+
195
+ it ( "Should not allow airdrop with mismatched arrays" , async function ( ) {
196
+ const beneficiaries = [ beneficiary1 . address , beneficiary2 . address ] ;
197
+ const amounts = [ ethers . parseEther ( "1000" ) ] ;
198
+
199
+ await expect (
200
+ vault . connect ( owner ) . executeAirdrop ( beneficiaries , amounts )
201
+ ) . to . be . revertedWithCustomError ( vault , "ArraysLengthMismatch" ) ;
202
+ } ) ;
203
+ } ) ;
204
+
205
+ describe ( "Pausing" , function ( ) {
206
+ it ( "Should allow admin to pause and unpause" , async function ( ) {
207
+ await vault . pause ( ) ;
208
+ expect ( await vault . paused ( ) ) . to . be . true ;
209
+
210
+ await vault . unpause ( ) ;
211
+ expect ( await vault . paused ( ) ) . to . be . false ;
212
+ } ) ;
213
+
214
+ it ( "Should not allow non-admin to pause" , async function ( ) {
215
+ await expect (
216
+ vault . connect ( user ) . pause ( )
217
+ ) . to . be . revertedWithCustomError ( vault , "AccessControlUnauthorizedAccount" ) ;
218
+ } ) ;
219
+
220
+ it ( "Should not allow allocations when paused" , async function ( ) {
221
+ await vault . pause ( ) ;
222
+
223
+ await expect (
224
+ vault . connect ( owner ) . createAllocation ( beneficiary1 . address , ethers . parseEther ( "1000" ) )
225
+ ) . to . be . revertedWithCustomError ( vault , "EnforcedPause" ) ;
226
+ } ) ;
227
+
228
+ it ( "Should not allow airdrops when paused" , async function ( ) {
229
+ await vault . pause ( ) ;
230
+
231
+ const beneficiaries = [ beneficiary1 . address ] ;
232
+ const amounts = [ ethers . parseEther ( "1000" ) ] ;
233
+
234
+ await expect (
235
+ vault . connect ( owner ) . executeAirdrop ( beneficiaries , amounts )
236
+ ) . to . be . revertedWithCustomError ( vault , "EnforcedPause" ) ;
237
+ } ) ;
238
+ } ) ;
239
+ } ) ;
0 commit comments