diff --git a/contracts/test/utils/TestEncryptedErrors.sol b/contracts/test/utils/TestEncryptedErrors.sol new file mode 100644 index 0000000..95be14f --- /dev/null +++ b/contracts/test/utils/TestEncryptedErrors.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.24; + +import "fhevm/lib/TFHE.sol"; +import { EncryptedErrors } from "../../utils/EncryptedErrors.sol"; +import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; + +contract TestEncryptedErrors is MockZamaFHEVMConfig, EncryptedErrors { + constructor(uint8 totalNumberErrorCodes_) EncryptedErrors(totalNumberErrorCodes_) { + for (uint8 i; i <= totalNumberErrorCodes_; i++) { + /// @dev It is not possible to access the _errorCodeDefinitions since it is private. + TFHE.allow(TFHE.asEuint8(i), msg.sender); + } + } + + function errorChangeIf( + einput encryptedCondition, + einput encryptedErrorCode, + bytes calldata inputProof, + uint8 indexCode + ) external returns (euint8 newErrorCode) { + ebool condition = TFHE.asEbool(encryptedCondition, inputProof); + euint8 errorCode = TFHE.asEuint8(encryptedErrorCode, inputProof); + newErrorCode = _errorChangeIf(condition, indexCode, errorCode); + _errorSave(newErrorCode); + TFHE.allow(newErrorCode, msg.sender); + } + + function errorChangeIfNot( + einput encryptedCondition, + einput encryptedErrorCode, + bytes calldata inputProof, + uint8 indexCode + ) external returns (euint8 newErrorCode) { + ebool condition = TFHE.asEbool(encryptedCondition, inputProof); + euint8 errorCode = TFHE.asEuint8(encryptedErrorCode, inputProof); + newErrorCode = _errorChangeIfNot(condition, indexCode, errorCode); + _errorSave(newErrorCode); + TFHE.allow(newErrorCode, msg.sender); + } + + function errorDefineIf( + einput encryptedCondition, + bytes calldata inputProof, + uint8 indexCode + ) external returns (euint8 errorCode) { + ebool condition = TFHE.asEbool(encryptedCondition, inputProof); + errorCode = _errorDefineIf(condition, indexCode); + _errorSave(errorCode); + TFHE.allow(errorCode, msg.sender); + } + + function errorDefineIfNot( + einput encryptedCondition, + bytes calldata inputProof, + uint8 indexCode + ) external returns (euint8 errorCode) { + ebool condition = TFHE.asEbool(encryptedCondition, inputProof); + errorCode = _errorDefineIfNot(condition, indexCode); + _errorSave(errorCode); + TFHE.allow(errorCode, msg.sender); + } + + function errorGetCodeDefinition(uint8 indexCodeDefinition) external view returns (euint8 errorCode) { + errorCode = _errorGetCodeDefinition(indexCodeDefinition); + } + + function errorGetCodeEmitted(uint256 errorId) external view returns (euint8 errorCode) { + errorCode = _errorGetCodeEmitted(errorId); + } + + function errorGetCounter() external view returns (uint256 countErrors) { + countErrors = _errorGetCounter(); + } + + function errorGetNumCodesDefined() external view returns (uint8 totalNumberErrorCodes) { + totalNumberErrorCodes = _errorGetNumCodesDefined(); + } +} diff --git a/test/utils/EncryptedErrors.fixture.ts b/test/utils/EncryptedErrors.fixture.ts new file mode 100644 index 0000000..ba8295e --- /dev/null +++ b/test/utils/EncryptedErrors.fixture.ts @@ -0,0 +1,11 @@ +import { ethers } from "hardhat"; + +import type { TestEncryptedErrors } from "../../types"; +import { Signers } from "../signers"; + +export async function deployEncryptedErrors(signers: Signers, numberErrors: number): Promise { + const contractFactory = await ethers.getContractFactory("TestEncryptedErrors"); + const contract = await contractFactory.connect(signers.alice).deploy(numberErrors); + await contract.waitForDeployment(); + return contract; +} diff --git a/test/utils/EncryptedErrors.test.ts b/test/utils/EncryptedErrors.test.ts new file mode 100644 index 0000000..d6edea6 --- /dev/null +++ b/test/utils/EncryptedErrors.test.ts @@ -0,0 +1,190 @@ +import { expect } from "chai"; + +import { createInstances } from "../instance"; +import { reencryptEuint8 } from "../reencrypt"; +import { getSigners, initSigners } from "../signers"; +import { deployEncryptedErrors } from "./EncryptedErrors.fixture"; + +describe("EncryptedErrors", function () { + const NO_ERROR_CODE = BigInt(0); + + before(async function () { + await initSigners(3); + this.signers = await getSigners(); + this.instances = await createInstances(this.signers); + }); + + beforeEach(async function () { + const contract = await deployEncryptedErrors(this.signers, 3); + this.encryptedErrorsAddress = await contract.getAddress(); + this.encryptedErrors = contract; + }); + + it("post-deployment", async function () { + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("0")); + expect(await this.encryptedErrors.errorGetNumCodesDefined()).to.be.eq(BigInt("3")); + + for (let i = 0; i < 3; i++) { + const handle = await this.encryptedErrors.errorGetCodeDefinition(i); + expect( + await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress), + ).to.be.eq(i); + } + }); + + it("errorDefineIf --> true", async function () { + // True --> errorId=0 has errorCode=2 + const condition = true; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorDefineIf(encryptedData.handles[0], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + targetErrorCode, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorDefineIf --> false", async function () { + // False --> errorId=1 has errorCode=0 + const condition = false; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorDefineIf(encryptedData.handles[0], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + NO_ERROR_CODE, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorDefineIfNot --> true", async function () { + // True --> errorId=0 has errorCode=0 + const condition = true; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorDefineIfNot(encryptedData.handles[0], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + NO_ERROR_CODE, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorDefineIf --> false", async function () { + // False --> errorId=1 has errorCode=2 + const condition = false; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorDefineIfNot(encryptedData.handles[0], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + targetErrorCode, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorChangeIf --> true --> change error code", async function () { + // True --> change errorCode + const condition = true; + const errorCode = 1; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).add8(errorCode).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorChangeIf(encryptedData.handles[0], encryptedData.handles[1], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + targetErrorCode, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorChangeIf --> false --> no change for error code", async function () { + // False --> no change in errorCode + const condition = false; + const errorCode = 1; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).add8(errorCode).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorChangeIf(encryptedData.handles[0], encryptedData.handles[1], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + errorCode, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorChangeIfNot --> true --> no change for error code", async function () { + // True --> no change errorCode + const condition = true; + const errorCode = 1; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).add8(errorCode).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorChangeIfNot(encryptedData.handles[0], encryptedData.handles[1], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + errorCode, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); + + it("errorChangeIfNot --> false --> change error code", async function () { + // False --> change in errorCode + const condition = false; + const errorCode = 1; + const targetErrorCode = 2; + + const input = this.instances.alice.createEncryptedInput(this.encryptedErrorsAddress, this.signers.alice.address); + const encryptedData = await input.addBool(condition).add8(errorCode).encrypt(); + + await this.encryptedErrors + .connect(this.signers.alice) + .errorChangeIfNot(encryptedData.handles[0], encryptedData.handles[1], encryptedData.inputProof, targetErrorCode); + + const handle = await this.encryptedErrors.connect(this.signers.alice).errorGetCodeEmitted(0); + expect(await reencryptEuint8(this.signers, this.instances, "alice", handle, this.encryptedErrorsAddress)).to.be.eq( + targetErrorCode, + ); + expect(await this.encryptedErrors.errorGetCounter()).to.be.eq(BigInt("1")); + }); +});