@@ -105,14 +105,6 @@ describe('EncryptedERC20:HCU', function () {
105105 } ) ;
106106
107107 describe ( 'block cap scenarios' , function ( ) {
108- const TIGHT_DEPTH_PER_TX = 400_000 ;
109- const TIGHT_MAX_PER_TX = 600_000 ;
110- const TIGHT_PER_BLOCK = 600_000 ;
111-
112- let savedHCUPerBlock : bigint ;
113- let savedMaxHCUPerTx : bigint ;
114- let savedMaxHCUDepthPerTx : bigint ;
115-
116108 // eslint-disable-next-line @typescript-eslint/no-explicit-any
117109 async function sendEncryptedTransfer ( ctx : any , sender : string , recipient : string , amount : number , overrides ?: any ) {
118110 const erc20 = ctx . erc20 . connect ( ctx . signers [ sender ] ) ;
@@ -122,13 +114,12 @@ describe('EncryptedERC20:HCU', function () {
122114 return erc20 [ 'transfer(address,bytes32,bytes)' ] ( recipient , enc . handles [ 0 ] , enc . inputProof , overrides ?? { } ) ;
123115 }
124116
125- // Narrowest-first when lowering to satisfy: hcuPerBlock >= maxHCUPerTx >= maxHCUDepthPerTx
126117 // eslint-disable-next-line @typescript-eslint/no-explicit-any
127- async function lowerHCULimits ( ctx : any ) {
128- const ownerHcuLimit = ctx . hcuLimit . connect ( ctx . deployer ) ;
129- await ownerHcuLimit . setMaxHCUDepthPerTx ( TIGHT_DEPTH_PER_TX ) ;
130- await ownerHcuLimit . setMaxHCUPerTx ( TIGHT_MAX_PER_TX ) ;
131- await ownerHcuLimit . setHCUPerBlock ( TIGHT_PER_BLOCK ) ;
118+ async function mintAndDistribute ( ctx : any ) {
119+ const mintTx = await ctx . erc20 . mint ( 10000 ) ;
120+ await mintTx . wait ( ) ;
121+ const setupTx = await sendEncryptedTransfer ( ctx , 'alice' , ctx . signers . bob . address , 5000 ) ;
122+ await setupTx . wait ( ) ;
132123 }
133124
134125 before ( async function ( ) {
@@ -145,31 +136,12 @@ describe('EncryptedERC20:HCU', function () {
145136 this . deployer = new ethers . Wallet ( deployerKey , ethers . provider ) ;
146137 } ) ;
147138
148- beforeEach ( async function ( ) {
149- savedHCUPerBlock = await this . hcuLimit . getGlobalHCUCapPerBlock ( ) ;
150- savedMaxHCUPerTx = await this . hcuLimit . getMaxHCUPerTx ( ) ;
151- savedMaxHCUDepthPerTx = await this . hcuLimit . getMaxHCUDepthPerTx ( ) ;
152- } ) ;
153-
154139 afterEach ( async function ( ) {
155140 await ethers . provider . send ( 'evm_setAutomine' , [ true ] ) ;
156- // Widest-first when restoring to satisfy: hcuPerBlock >= maxHCUPerTx >= maxHCUDepthPerTx
157- const ownerHcuLimit = this . hcuLimit . connect ( this . deployer ) ;
158- await ownerHcuLimit . setHCUPerBlock ( savedHCUPerBlock ) ;
159- await ownerHcuLimit . setMaxHCUPerTx ( savedMaxHCUPerTx ) ;
160- await ownerHcuLimit . setMaxHCUDepthPerTx ( savedMaxHCUDepthPerTx ) ;
161-
162- if ( await this . hcuLimit . isBlockHCUWhitelisted ( this . contractAddress ) ) {
163- await ownerHcuLimit . removeFromBlockHCUWhitelist ( this . contractAddress ) ;
164- }
165141 } ) ;
166142
167143 it ( 'should accumulate HCU from multiple users in the same block' , async function ( ) {
168- const mintTx = await this . erc20 . mint ( 10000 ) ;
169- await mintTx . wait ( ) ;
170-
171- const setupTx = await sendEncryptedTransfer ( this , 'alice' , this . signers . bob . address , 5000 ) ;
172- await setupTx . wait ( ) ;
144+ await mintAndDistribute ( this ) ;
173145
174146 await mineNBlocks ( 1 ) ;
175147 await ethers . provider . send ( 'evm_setAutomine' , [ false ] ) ;
@@ -204,69 +176,89 @@ describe('EncryptedERC20:HCU', function () {
204176 expect ( usedHCU ) . to . be . greaterThan ( BigInt ( hcuBob ) , 'Block meter should exceed bob HCU alone' ) ;
205177 } ) ;
206178
207- it ( 'should revert when block HCU cap is exhausted' , async function ( ) {
208- await lowerHCULimits ( this ) ;
209-
210- const mintTx = await this . erc20 . mint ( 10000 ) ;
211- await mintTx . wait ( ) ;
212-
213- const setupTx = await sendEncryptedTransfer ( this , 'alice' , this . signers . bob . address , 5000 ) ;
214- await setupTx . wait ( ) ;
215-
216- await mineNBlocks ( 1 ) ;
217- await ethers . provider . send ( 'evm_setAutomine' , [ false ] ) ;
218-
219- // Alice fills the cap, Bob would push block total over — use fixed gasLimit
220- // to bypass estimateGas (which reverts against pending state)
221- const tx1 = await sendEncryptedTransfer ( this , 'alice' , this . signers . carol . address , 100 ) ;
222- const tx2 = await sendEncryptedTransfer ( this , 'bob' , this . signers . carol . address , 100 , { gasLimit : 1_000_000 } ) ;
223-
224- await ethers . provider . send ( 'evm_mine' ) ;
225- await ethers . provider . send ( 'evm_setAutomine' , [ true ] ) ;
226-
227- const receipt1 = await tx1 . wait ( ) ;
228- expect ( receipt1 ?. status ) . to . eq ( 1 , 'First transfer should succeed' ) ;
229-
230- // Use getTransactionReceipt to avoid ethers throwing on reverted tx
231- const receipt2 = await ethers . provider . getTransactionReceipt ( tx2 . hash ) ;
232- expect ( receipt2 ?. status ) . to . eq ( 0 , 'Second transfer should revert (block cap exceeded)' ) ;
233- expect ( receipt1 ?. blockNumber ) . to . eq ( receipt2 ?. blockNumber ) ;
234- } ) ;
235-
236- it ( 'should allow previously blocked caller to succeed after block rollover' , async function ( ) {
237- await lowerHCULimits ( this ) ;
238-
239- const mintTx = await this . erc20 . mint ( 10000 ) ;
240- await mintTx . wait ( ) ;
241-
242- const setupTx = await sendEncryptedTransfer ( this , 'alice' , this . signers . bob . address , 5000 ) ;
243- await setupTx . wait ( ) ;
244-
245- // Block N: alice fills the cap, bob gets blocked
246- await mineNBlocks ( 1 ) ;
247- await ethers . provider . send ( 'evm_setAutomine' , [ false ] ) ;
248-
249- const txAlice = await sendEncryptedTransfer ( this , 'alice' , this . signers . carol . address , 100 ) ;
250- const txBob = await sendEncryptedTransfer ( this , 'bob' , this . signers . carol . address , 100 , { gasLimit : 1_000_000 } ) ;
251-
252- await ethers . provider . send ( 'evm_mine' ) ;
253- await ethers . provider . send ( 'evm_setAutomine' , [ true ] ) ;
254-
255- const receiptAlice = await txAlice . wait ( ) ;
256- expect ( receiptAlice ?. status ) . to . eq ( 1 , 'Alice should succeed' ) ;
257-
258- const receiptBob = await ethers . provider . getTransactionReceipt ( txBob . hash ) ;
259- expect ( receiptBob ?. status ) . to . eq ( 0 , 'Bob should be blocked in block N' ) ;
260-
261- // Block N+1: meter resets, bob retries and succeeds
262- await mineNBlocks ( 1 ) ;
263-
264- const [ , usedHCUAfterReset ] = await this . hcuLimit . getBlockMeter ( ) ;
265- expect ( usedHCUAfterReset ) . to . eq ( 0n , 'Meter should reset after new block' ) ;
266-
267- const retryBob = await sendEncryptedTransfer ( this , 'bob' , this . signers . carol . address , 100 ) ;
268- const receiptRetry = await retryBob . wait ( ) ;
269- expect ( receiptRetry ?. status ) . to . eq ( 1 , 'Bob should succeed after rollover' ) ;
179+ describe ( 'with lowered limits' , function ( ) {
180+ const TIGHT_DEPTH_PER_TX = 400_000 ;
181+ const TIGHT_MAX_PER_TX = 600_000 ;
182+ const TIGHT_PER_BLOCK = 600_000 ;
183+
184+ let savedHCUPerBlock : bigint ;
185+ let savedMaxHCUPerTx : bigint ;
186+ let savedMaxHCUDepthPerTx : bigint ;
187+
188+ beforeEach ( async function ( ) {
189+ [ savedHCUPerBlock , savedMaxHCUPerTx , savedMaxHCUDepthPerTx ] = await Promise . all ( [
190+ this . hcuLimit . getGlobalHCUCapPerBlock ( ) ,
191+ this . hcuLimit . getMaxHCUPerTx ( ) ,
192+ this . hcuLimit . getMaxHCUDepthPerTx ( ) ,
193+ ] ) ;
194+
195+ // Narrowest-first when lowering: hcuPerBlock >= maxHCUPerTx >= maxHCUDepthPerTx
196+ const ownerHcuLimit = this . hcuLimit . connect ( this . deployer ) ;
197+ await ownerHcuLimit . setMaxHCUDepthPerTx ( TIGHT_DEPTH_PER_TX ) ;
198+ await ownerHcuLimit . setMaxHCUPerTx ( TIGHT_MAX_PER_TX ) ;
199+ await ownerHcuLimit . setHCUPerBlock ( TIGHT_PER_BLOCK ) ;
200+ } ) ;
201+
202+ afterEach ( async function ( ) {
203+ // Widest-first when restoring: hcuPerBlock >= maxHCUPerTx >= maxHCUDepthPerTx
204+ const ownerHcuLimit = this . hcuLimit . connect ( this . deployer ) ;
205+ await ownerHcuLimit . setHCUPerBlock ( savedHCUPerBlock ) ;
206+ await ownerHcuLimit . setMaxHCUPerTx ( savedMaxHCUPerTx ) ;
207+ await ownerHcuLimit . setMaxHCUDepthPerTx ( savedMaxHCUDepthPerTx ) ;
208+ } ) ;
209+
210+ it ( 'should revert when block HCU cap is exhausted' , async function ( ) {
211+ await mintAndDistribute ( this ) ;
212+
213+ await mineNBlocks ( 1 ) ;
214+ await ethers . provider . send ( 'evm_setAutomine' , [ false ] ) ;
215+
216+ // Alice fills the cap, Bob would push block total over — use fixed gasLimit
217+ // to bypass estimateGas (which reverts against pending state)
218+ const tx1 = await sendEncryptedTransfer ( this , 'alice' , this . signers . carol . address , 100 ) ;
219+ const tx2 = await sendEncryptedTransfer ( this , 'bob' , this . signers . carol . address , 100 , { gasLimit : 1_000_000 } ) ;
220+
221+ await ethers . provider . send ( 'evm_mine' ) ;
222+ await ethers . provider . send ( 'evm_setAutomine' , [ true ] ) ;
223+
224+ const receipt1 = await tx1 . wait ( ) ;
225+ expect ( receipt1 ?. status ) . to . eq ( 1 , 'First transfer should succeed' ) ;
226+
227+ // Use getTransactionReceipt to avoid ethers throwing on reverted tx
228+ const receipt2 = await ethers . provider . getTransactionReceipt ( tx2 . hash ) ;
229+ expect ( receipt2 ?. status ) . to . eq ( 0 , 'Second transfer should revert (block cap exceeded)' ) ;
230+ expect ( receipt1 ?. blockNumber ) . to . eq ( receipt2 ?. blockNumber ) ;
231+ } ) ;
232+
233+ it ( 'should allow previously blocked caller to succeed after block rollover' , async function ( ) {
234+ await mintAndDistribute ( this ) ;
235+
236+ // Block N: alice fills the cap, bob gets blocked
237+ await mineNBlocks ( 1 ) ;
238+ await ethers . provider . send ( 'evm_setAutomine' , [ false ] ) ;
239+
240+ const txAlice = await sendEncryptedTransfer ( this , 'alice' , this . signers . carol . address , 100 ) ;
241+ const txBob = await sendEncryptedTransfer ( this , 'bob' , this . signers . carol . address , 100 , { gasLimit : 1_000_000 } ) ;
242+
243+ await ethers . provider . send ( 'evm_mine' ) ;
244+ await ethers . provider . send ( 'evm_setAutomine' , [ true ] ) ;
245+
246+ const receiptAlice = await txAlice . wait ( ) ;
247+ expect ( receiptAlice ?. status ) . to . eq ( 1 , 'Alice should succeed' ) ;
248+
249+ const receiptBob = await ethers . provider . getTransactionReceipt ( txBob . hash ) ;
250+ expect ( receiptBob ?. status ) . to . eq ( 0 , 'Bob should be blocked in block N' ) ;
251+
252+ // Block N+1: meter resets, bob retries and succeeds
253+ await mineNBlocks ( 1 ) ;
254+
255+ const [ , usedHCUAfterReset ] = await this . hcuLimit . getBlockMeter ( ) ;
256+ expect ( usedHCUAfterReset ) . to . eq ( 0n , 'Meter should reset after new block' ) ;
257+
258+ const retryBob = await sendEncryptedTransfer ( this , 'bob' , this . signers . carol . address , 100 ) ;
259+ const receiptRetry = await retryBob . wait ( ) ;
260+ expect ( receiptRetry ?. status ) . to . eq ( 1 , 'Bob should succeed after rollover' ) ;
261+ } ) ;
270262 } ) ;
271263
272264 it ( 'should count HCU after whitelist removal' , async function ( ) {
0 commit comments