Skip to content

Commit 7c5dc38

Browse files
authored
refactor(test-suite): refactor negative ACL e2e tests into consistent pattern (#2101)
* refactor(test-suite): extract negative ACL tests in userDecryption.ts * refactor(test-suite): extract negative ACL test in EncryptedERC20.ts * refactor(test-suite): group negative ACL test in httpPublicDecrypt.ts * refactor(test-suite): group negative ACL tests in delegatedUserDecryption.ts * fix(test-suite): fix stale expect.fail message in userDecryption negative test * feat(test-suite): add negative-acl test type to fhevm-cli * ci(test-suite): add negative-acl step to e2e workflow * fix(test-suite): fix broken negative acl test for public decryption * refactor(test-suite): rename negative acl blocks to be more precise in grep * chore(test-suite): update TEST_SUITE_VERSION * chore(test-suite): pin fhevm-cli TEST_SUITE_VERSION after rebase
1 parent cc70478 commit 7c5dc38

File tree

6 files changed

+765
-730
lines changed

6 files changed

+765
-730
lines changed

.github/workflows/test-suite-e2e-tests.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ jobs:
223223
run: |
224224
./fhevm-cli test public-decrypt-http-mixed
225225
226+
- name: Negative ACL tests
227+
working-directory: test-suite/fhevm
228+
run: |
229+
./fhevm-cli test negative-acl
230+
226231
- name: Random operators test (subset)
227232
working-directory: test-suite/fhevm
228233
run: |

test-suite/e2e/test/delegatedUserDecryption/delegatedUserDecryption.ts

Lines changed: 144 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -200,147 +200,149 @@ describe('Delegated user decryption', function () {
200200
expect(Number(decryptedBalanceBefore) - Number(decryptedBalanceAfter)).to.equal(Number(transferAmount));
201201
});
202202

203-
it('test delegated user decryption - smartWallet revokes the delegation of user decryption to an EOA', async function () {
204-
// First, ensure Bob has delegation.
205-
const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
206-
const delegateTx = await this.smartWallet
207-
.connect(this.signers.bob)
208-
.delegateUserDecryption(
209-
this.signers.bob.address,
210-
this.tokenAddress,
211-
expirationTimestamp,
212-
);
213-
await delegateTx.wait();
214-
215-
// Wait for 15 blocks to ensure delegation is propagated by the coprocessor.
216-
const currentBlock1 = await ethers.provider.getBlockNumber();
217-
await waitForBlock(currentBlock1 + 15);
218-
219-
// Revoke the delegation for Bob's EOA.
220-
const revokeTx = await this.smartWallet
221-
.connect(this.signers.bob)
222-
.revokeUserDecryptionDelegation(
223-
this.signers.bob.address,
224-
this.tokenAddress,
225-
);
226-
await revokeTx.wait();
227-
228-
// Wait for 15 blocks to ensure revocation is propagated by the coprocessor.
229-
const currentBlock2 = await ethers.provider.getBlockNumber();
230-
await waitForBlock(currentBlock2 + 15);
231-
232-
// Try to decrypt the smartWallet balance with Bob's EOA, which should now fail.
233-
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
234-
const { publicKey, privateKey } = this.instances.bob.generateKeypair();
235-
236-
try {
237-
await delegatedUserDecryptSingleHandle(
238-
this.instances.bob,
239-
balanceHandle,
240-
this.tokenAddress,
241-
this.smartWalletAddress,
242-
this.signers.bob.address,
243-
this.signers.bob,
244-
privateKey,
245-
publicKey,
246-
);
247-
expect.fail('Expected delegated user decrypt to be rejected after revocation');
248-
} catch (err: any) {
249-
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
250-
}
251-
});
252-
253-
it('test delegated user decryption should fail when no delegation exists', async function () {
254-
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
255-
const { publicKey, privateKey } = this.instances.dave.generateKeypair();
256-
257-
try {
258-
await delegatedUserDecryptSingleHandle(
259-
this.instances.dave,
260-
balanceHandle,
261-
this.tokenAddress,
262-
this.smartWalletAddress,
263-
this.signers.dave.address,
264-
this.signers.dave,
265-
privateKey,
266-
publicKey,
267-
);
268-
expect.fail('Expected delegated user decrypt to be rejected without delegation');
269-
} catch (err: any) {
270-
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
271-
}
272-
});
273-
274-
it('test delegated user decryption should fail when delegation is for wrong contract', async function () {
275-
const dummyFactory = await ethers.getContractFactory('UserDecrypt');
276-
const dummy = await dummyFactory.connect(this.signers.alice).deploy();
277-
await dummy.waitForDeployment();
278-
const wrongAddress = await dummy.getAddress();
279-
280-
const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
281-
const tx = await this.smartWallet
282-
.connect(this.signers.bob)
283-
.delegateUserDecryption(this.signers.eve.address, wrongAddress, expirationTimestamp);
284-
await tx.wait();
285-
const currentBlock = await ethers.provider.getBlockNumber();
286-
await waitForBlock(currentBlock + 15);
287-
288-
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
289-
const { publicKey, privateKey } = this.instances.eve.generateKeypair();
290-
291-
try {
292-
await delegatedUserDecryptSingleHandle(
293-
this.instances.eve,
294-
balanceHandle,
295-
this.tokenAddress,
296-
this.smartWalletAddress,
297-
this.signers.eve.address,
298-
this.signers.eve,
299-
privateKey,
300-
publicKey,
301-
);
302-
expect.fail('Expected delegated user decrypt to be rejected for wrong contract');
303-
} catch (err: any) {
304-
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
305-
}
306-
});
307-
308-
it('test delegated user decryption should fail when delegation has expired', async function () {
309-
// Expiration must be >1h from chain time (FHE library constraint).
310-
// Use block timestamp, not Date.now(), since evm_increaseTime shifts chain clock.
311-
const oneHour = 3600;
312-
const buffer = 60;
313-
const latestBlock = await ethers.provider.getBlock('latest');
314-
const expirationTimestamp = latestBlock!.timestamp + oneHour + buffer;
315-
const tx = await this.smartWallet
316-
.connect(this.signers.bob)
317-
.delegateUserDecryption(this.signers.eve.address, this.tokenAddress, expirationTimestamp);
318-
await tx.wait();
319-
320-
// Fast-forward time past the expiration.
321-
await ethers.provider.send('evm_increaseTime', [oneHour + buffer + 1]);
322-
await ethers.provider.send('evm_mine', []);
323-
324-
const currentBlock = await ethers.provider.getBlockNumber();
325-
await waitForBlock(currentBlock + 15);
326-
327-
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
328-
const { publicKey, privateKey } = this.instances.eve.generateKeypair();
329-
330-
try {
331-
await delegatedUserDecryptSingleHandle(
332-
this.instances.eve,
333-
balanceHandle,
334-
this.tokenAddress,
335-
this.smartWalletAddress,
336-
this.signers.eve.address,
337-
this.signers.eve,
338-
privateKey,
339-
publicKey,
340-
);
341-
expect.fail('Expected delegated user decrypt to be rejected for expired delegation');
342-
} catch (err: any) {
343-
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
344-
}
203+
describe('negative-acl', function () {
204+
it('should reject when delegation has been revoked', async function () {
205+
// First, ensure Bob has delegation.
206+
const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
207+
const delegateTx = await this.smartWallet
208+
.connect(this.signers.bob)
209+
.delegateUserDecryption(
210+
this.signers.bob.address,
211+
this.tokenAddress,
212+
expirationTimestamp,
213+
);
214+
await delegateTx.wait();
215+
216+
// Wait for 15 blocks to ensure delegation is propagated by the coprocessor.
217+
const currentBlock1 = await ethers.provider.getBlockNumber();
218+
await waitForBlock(currentBlock1 + 15);
219+
220+
// Revoke the delegation for Bob's EOA.
221+
const revokeTx = await this.smartWallet
222+
.connect(this.signers.bob)
223+
.revokeUserDecryptionDelegation(
224+
this.signers.bob.address,
225+
this.tokenAddress,
226+
);
227+
await revokeTx.wait();
228+
229+
// Wait for 15 blocks to ensure revocation is propagated by the coprocessor.
230+
const currentBlock2 = await ethers.provider.getBlockNumber();
231+
await waitForBlock(currentBlock2 + 15);
232+
233+
// Try to decrypt the smartWallet balance with Bob's EOA, which should now fail.
234+
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
235+
const { publicKey, privateKey } = this.instances.bob.generateKeypair();
236+
237+
try {
238+
await delegatedUserDecryptSingleHandle(
239+
this.instances.bob,
240+
balanceHandle,
241+
this.tokenAddress,
242+
this.smartWalletAddress,
243+
this.signers.bob.address,
244+
this.signers.bob,
245+
privateKey,
246+
publicKey,
247+
);
248+
expect.fail('Expected delegated user decrypt to be rejected after revocation');
249+
} catch (err: any) {
250+
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
251+
}
252+
});
253+
254+
it('should reject when no delegation exists', async function () {
255+
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
256+
const { publicKey, privateKey } = this.instances.dave.generateKeypair();
257+
258+
try {
259+
await delegatedUserDecryptSingleHandle(
260+
this.instances.dave,
261+
balanceHandle,
262+
this.tokenAddress,
263+
this.smartWalletAddress,
264+
this.signers.dave.address,
265+
this.signers.dave,
266+
privateKey,
267+
publicKey,
268+
);
269+
expect.fail('Expected delegated user decrypt to be rejected without delegation');
270+
} catch (err: any) {
271+
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
272+
}
273+
});
274+
275+
it('should reject when delegation is for wrong contract', async function () {
276+
const dummyFactory = await ethers.getContractFactory('UserDecrypt');
277+
const dummy = await dummyFactory.connect(this.signers.alice).deploy();
278+
await dummy.waitForDeployment();
279+
const wrongAddress = await dummy.getAddress();
280+
281+
const expirationTimestamp = Math.floor(Date.now() / 1000) + 86400;
282+
const tx = await this.smartWallet
283+
.connect(this.signers.bob)
284+
.delegateUserDecryption(this.signers.eve.address, wrongAddress, expirationTimestamp);
285+
await tx.wait();
286+
const currentBlock = await ethers.provider.getBlockNumber();
287+
await waitForBlock(currentBlock + 15);
288+
289+
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
290+
const { publicKey, privateKey } = this.instances.eve.generateKeypair();
291+
292+
try {
293+
await delegatedUserDecryptSingleHandle(
294+
this.instances.eve,
295+
balanceHandle,
296+
this.tokenAddress,
297+
this.smartWalletAddress,
298+
this.signers.eve.address,
299+
this.signers.eve,
300+
privateKey,
301+
publicKey,
302+
);
303+
expect.fail('Expected delegated user decrypt to be rejected for wrong contract');
304+
} catch (err: any) {
305+
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
306+
}
307+
});
308+
309+
it('should reject when delegation has expired', async function () {
310+
// Expiration must be >1h from chain time (FHE library constraint).
311+
// Use block timestamp, not Date.now(), since evm_increaseTime shifts chain clock.
312+
const oneHour = 3600;
313+
const buffer = 60;
314+
const latestBlock = await ethers.provider.getBlock('latest');
315+
const expirationTimestamp = latestBlock!.timestamp + oneHour + buffer;
316+
const tx = await this.smartWallet
317+
.connect(this.signers.bob)
318+
.delegateUserDecryption(this.signers.eve.address, this.tokenAddress, expirationTimestamp);
319+
await tx.wait();
320+
321+
// Fast-forward time past the expiration.
322+
await ethers.provider.send('evm_increaseTime', [oneHour + buffer + 1]);
323+
await ethers.provider.send('evm_mine', []);
324+
325+
const currentBlock = await ethers.provider.getBlockNumber();
326+
await waitForBlock(currentBlock + 15);
327+
328+
const balanceHandle = await this.token.balanceOf(this.smartWalletAddress);
329+
const { publicKey, privateKey } = this.instances.eve.generateKeypair();
330+
331+
try {
332+
await delegatedUserDecryptSingleHandle(
333+
this.instances.eve,
334+
balanceHandle,
335+
this.tokenAddress,
336+
this.smartWalletAddress,
337+
this.signers.eve.address,
338+
this.signers.eve,
339+
privateKey,
340+
publicKey,
341+
);
342+
expect.fail('Expected delegated user decrypt to be rejected for expired delegation');
343+
} catch (err: any) {
344+
expect(err.relayerApiError?.label).to.equal(NOT_ALLOWED_ON_HOST_ACL);
345+
}
346+
});
345347
});
346348
});

test-suite/e2e/test/encryptedERC20/EncryptedERC20.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -101,23 +101,6 @@ describe('EncryptedERC20', function () {
101101
);
102102

103103
expect(balanceBob).to.equal(1337);
104-
105-
// on the other hand, Bob should be unable to read Alice's balance
106-
try {
107-
await userDecryptSingleHandle(
108-
balanceHandleAlice,
109-
this.contractAddress,
110-
this.instances.bob,
111-
this.signers.bob,
112-
privateKeyBob,
113-
publicKeyBob,
114-
);
115-
expect.fail('Expected an error to be thrown - Bob should not be able to user decrypt Alice balance');
116-
} catch (error) {
117-
expect(error.message).to.equal(
118-
`User address ${this.signers.bob.address} is not authorized to user decrypt handle ${balanceHandleAlice}!`,
119-
);
120-
}
121104
});
122105

123106
it('should not transfer tokens between two users', async function () {
@@ -251,4 +234,40 @@ describe('EncryptedERC20', function () {
251234
);
252235
expect(balanceBob2).to.equal(1337); // check that transfer did happen this time
253236
});
237+
238+
describe('negative-acl', function () {
239+
it('should reject when user is not allowed for handle', async function () {
240+
const transaction = await this.erc20.mint(10000);
241+
await transaction.wait();
242+
243+
const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address);
244+
input.add64(1337);
245+
const encryptedTransferAmount = await input.encrypt();
246+
const tx = await this.erc20['transfer(address,bytes32,bytes)'](
247+
this.signers.bob.address,
248+
encryptedTransferAmount.handles[0],
249+
encryptedTransferAmount.inputProof,
250+
);
251+
await tx.wait();
252+
253+
const balanceHandleAlice = await this.erc20.balanceOf(this.signers.alice);
254+
const { publicKey: publicKeyBob, privateKey: privateKeyBob } = this.instances.bob.generateKeypair();
255+
256+
try {
257+
await userDecryptSingleHandle(
258+
balanceHandleAlice,
259+
this.contractAddress,
260+
this.instances.bob,
261+
this.signers.bob,
262+
privateKeyBob,
263+
publicKeyBob,
264+
);
265+
expect.fail('Expected an error to be thrown - Bob should not be able to user decrypt Alice balance');
266+
} catch (error) {
267+
expect(error.message).to.equal(
268+
`User address ${this.signers.bob.address} is not authorized to user decrypt handle ${balanceHandleAlice}!`,
269+
);
270+
}
271+
});
272+
});
254273
});

test-suite/e2e/test/httpPublicDecrypt/httpPublicDecrypt.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,19 @@ describe('HTTPPublicDecrypt', function () {
3939
assert.deepEqual(res.clearValues, expectedRes);
4040
});
4141

42-
it('test public decrypt should fail for non-publicly-decryptable handle', async function () {
43-
const factory = await ethers.getContractFactory('UserDecrypt');
44-
const contract = await factory.connect(this.signers.alice).deploy();
45-
await contract.waitForDeployment();
46-
const handle = await contract.xBool();
42+
describe('negative-acl', function () {
43+
it('should reject when handle is not publicly decryptable', async function () {
44+
const factory = await ethers.getContractFactory('UserDecrypt');
45+
const contract = await factory.connect(this.signers.alice).deploy();
46+
await contract.waitForDeployment();
47+
const handle = await contract.xUint8();
4748

48-
try {
49-
await this.instances.alice.publicDecrypt([handle]);
50-
expect.fail('Expected an error - handle is not publicly decryptable');
51-
} catch (error) {
52-
expect(error.message).to.include('not allowed for public decryption');
53-
}
49+
try {
50+
await this.instances.alice.publicDecrypt([handle]);
51+
expect.fail('Expected an error - handle is not publicly decryptable');
52+
} catch (error) {
53+
expect(error.message).to.include('not allowed for public decryption');
54+
}
55+
});
5456
});
5557
});

0 commit comments

Comments
 (0)