From 0e60f435b041e080b255185d80306fcf10c6101e Mon Sep 17 00:00:00 2001 From: PacificYield <173040337+PacificYield@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:03:24 +0100 Subject: [PATCH] test: add unit tests --- contracts/token/ERC20/EncryptedWETH.sol | 13 +- .../EncryptedERC20Wrapped.fixture.ts | 43 +++ .../EncryptedERC20Wrapped.test.ts | 333 ++++++++++++++++++ test/encryptedERC20/EncryptedWETH.fixture.ts | 12 + test/encryptedERC20/EncryptedWETH.test.ts | 217 ++++++++++++ 5 files changed, 612 insertions(+), 6 deletions(-) create mode 100644 test/encryptedERC20/EncryptedERC20Wrapped.fixture.ts create mode 100644 test/encryptedERC20/EncryptedERC20Wrapped.test.ts create mode 100644 test/encryptedERC20/EncryptedWETH.fixture.ts create mode 100644 test/encryptedERC20/EncryptedWETH.test.ts diff --git a/contracts/token/ERC20/EncryptedWETH.sol b/contracts/token/ERC20/EncryptedWETH.sol index c011d67..a99ce75 100644 --- a/contracts/token/ERC20/EncryptedWETH.sol +++ b/contracts/token/ERC20/EncryptedWETH.sol @@ -6,8 +6,6 @@ import { EncryptedERC20 } from "./EncryptedERC20.sol"; import "fhevm/lib/TFHE.sol"; import "fhevm/gateway/GatewayCaller.sol"; -import "hardhat/console.sol"; - /** * @title EncryptedWETH * @notice This contract allows users to wrap/unwrap trustlessly @@ -63,6 +61,13 @@ abstract contract EncryptedWETH is EncryptedERC20, GatewayCaller { wrap(); } + /** + * @notice Receive function calls wrap(). + */ + receive() external payable { + wrap(); + } + /** * @notice Unwrap EncryptedERC20 tokens to ether. * @param amount Amount to unwrap. @@ -115,8 +120,6 @@ abstract contract EncryptedWETH is EncryptedERC20, GatewayCaller { UnwrapRequest memory unwrapRequest = unwrapRequests[requestId]; delete unwrapRequests[requestId]; - console.log(canUnwrap); - if (canUnwrap) { _unsafeBurn(unwrapRequest.account, TFHE.asEuint64(unwrapRequest.amount)); _totalSupply -= unwrapRequest.amount; @@ -133,10 +136,8 @@ abstract contract EncryptedWETH is EncryptedERC20, GatewayCaller { } emit Unwrap(unwrapRequest.account, unwrapRequest.amount); - console.log("YES"); } else { emit UnwrapFail(unwrapRequest.account, unwrapRequest.amount); - console.log("FAIL"); } delete isAccountRestricted[unwrapRequest.account]; diff --git a/test/encryptedERC20/EncryptedERC20Wrapped.fixture.ts b/test/encryptedERC20/EncryptedERC20Wrapped.fixture.ts new file mode 100644 index 0000000..fd957f7 --- /dev/null +++ b/test/encryptedERC20/EncryptedERC20Wrapped.fixture.ts @@ -0,0 +1,43 @@ +import { ethers } from "hardhat"; + +import type { ERC20Mintable, EncryptedERC20Wrapped, TestEncryptedERC20Wrapped } from "../../types"; +import { Signers } from "../signers"; + +export async function deployERC20AndEncryptedERC20WrappedFixture( + signers: Signers, + name: string, + symbol: string, + decimals: number, +): Promise<[ERC20Mintable, TestEncryptedERC20Wrapped]> { + const contractFactoryERC20Mintable = await ethers.getContractFactory("ERC20Mintable"); + const contractERC20 = await contractFactoryERC20Mintable + .connect(signers.alice) + .deploy(name, symbol, decimals, signers.alice.address); + await contractERC20.waitForDeployment(); + + const contractFactoryEncryptedERC20Wrapped = await ethers.getContractFactory("TestEncryptedERC20Wrapped"); + const contractEncryptedERC20Wrapped = await contractFactoryEncryptedERC20Wrapped + .connect(signers.alice) + .deploy(contractERC20.getAddress()); + await contractEncryptedERC20Wrapped.waitForDeployment(); + + return [contractERC20, contractEncryptedERC20Wrapped]; +} + +export async function mintAndWrap( + signers: Signers, + user: string, + plainToken: ERC20Mintable, + token: EncryptedERC20Wrapped, + tokenAddress: string, + amount: bigint, +): Promise { + let tx = await plainToken.connect(signers[user as keyof Signers]).mint(amount); + await tx.wait(); + + tx = await plainToken.connect(signers[user as keyof Signers]).approve(tokenAddress, amount); + await tx.wait(); + + tx = await token.connect(signers[user as keyof Signers]).wrap(amount); + await tx.wait(); +} diff --git a/test/encryptedERC20/EncryptedERC20Wrapped.test.ts b/test/encryptedERC20/EncryptedERC20Wrapped.test.ts new file mode 100644 index 0000000..1e25270 --- /dev/null +++ b/test/encryptedERC20/EncryptedERC20Wrapped.test.ts @@ -0,0 +1,333 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { awaitAllDecryptionResults } from "../asyncDecrypt"; +import { createInstances } from "../instance"; +import { getSigners, initSigners } from "../signers"; +import { reencryptBalance } from "./EncryptedERC20.fixture"; +import { deployERC20AndEncryptedERC20WrappedFixture } from "./EncryptedERC20Wrapped.fixture"; + +describe("EncryptedERC20Wrapped using ERC20 with 6 decimals", function () { + before(async function () { + await initSigners(2); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + const [erc20, encryptedERC20] = await deployERC20AndEncryptedERC20WrappedFixture( + this.signers, + "Naraggara", + "NARA", + 6, + ); + + this.erc20 = erc20; + this.encryptedERC20 = encryptedERC20; + this.erc20ContractAddress = await erc20.getAddress(); + this.encryptedERC20Address = await encryptedERC20.getAddress(); + this.instances = await createInstances(this.signers); + }); + + it("name/symbol are automatically set", async function () { + expect(await this.encryptedERC20.name()).to.eq("Encrypted Naraggara"); + expect(await this.encryptedERC20.symbol()).to.eq("eNARA"); + }); + + it("can wrap", async function () { + const amountToWrap = ethers.parseUnits("100000", 6); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + + // Check balance/totalSupply + expect(await this.erc20.balanceOf(this.signers.alice)).to.equal(amountToWrap); + expect(await this.erc20.totalSupply()).to.equal(amountToWrap); + + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.wrap(amountToWrap); + await tx.wait(); + + // Check encrypted balance + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedERC20, this.encryptedERC20Address), + ).to.equal(amountToWrap); + }); + + it("can unwrap", async function () { + const amountToWrap = ethers.parseUnits("10000", 6); + const amountToUnwrap = ethers.parseUnits("5000", 6); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap); + await tx.wait(); + + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(amountToUnwrap); + await tx.wait(); + + await awaitAllDecryptionResults(); + + expect(await this.erc20.balanceOf(this.signers.alice)).to.equal(amountToUnwrap); + expect(await this.erc20.totalSupply()).to.equal(amountToWrap); + + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedERC20, this.encryptedERC20Address), + ).to.equal(amountToWrap - amountToUnwrap); + }); + + it("cannot transfer after unwrap has been called but decryption has not occurred", async function () { + const amountToWrap = ethers.parseUnits("10000", 6); + const amountToUnwrap = ethers.parseUnits("5000", 6); + const transferAmount = ethers.parseUnits("3000", 6); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap); + await tx.wait(); + + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(amountToUnwrap); + await tx.wait(); + + const input = this.instances.alice.createEncryptedInput(this.encryptedERC20Address, this.signers.alice.address); + input.add64(transferAmount); + const encryptedTransferAmount = await input.encrypt(); + + await expect( + this.encryptedERC20 + .connect(this.signers.alice) + [ + "transfer(address,bytes32,bytes)" + ](this.signers.bob.address, encryptedTransferAmount.handles[0], encryptedTransferAmount.inputProof), + ).to.be.revertedWithCustomError(this.encryptedERC20, "CannotTransferOrUnwrap"); + }); + + it("cannot call twice unwrap before decryption", async function () { + const amountToWrap = ethers.parseUnits("10000", 6); + const amountToUnwrap = ethers.parseUnits("5000", 6); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap); + await tx.wait(); + + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(amountToUnwrap); + await tx.wait(); + + await expect(this.encryptedERC20.connect(this.signers.alice).unwrap(amountToUnwrap)).to.be.revertedWithCustomError( + this.encryptedERC20, + "CannotTransferOrUnwrap", + ); + }); + + it("cannot unwrap more than balance", async function () { + const amountToWrap = ethers.parseUnits("10000", 6); + const amountToUnwrap = amountToWrap + BigInt("1"); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(amountToUnwrap); + await tx.wait(); + + await awaitAllDecryptionResults(); + + // Verify the balances have not changed + expect(await this.erc20.balanceOf(this.encryptedERC20Address)).to.equal(amountToWrap); + expect(await this.encryptedERC20.totalSupply()).to.equal(amountToWrap); + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedERC20, this.encryptedERC20Address), + ).to.equal(amountToWrap); + }); + + it("transfers work outside of decryption period", async function () { + const amountToWrap = ethers.parseUnits("10000", 6); + const amountToUnwrap = ethers.parseUnits("2000", 6); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap); + await tx.wait(); + + let transferAmount = ethers.parseUnits("3000", 6); + let input = this.instances.alice.createEncryptedInput(this.encryptedERC20Address, this.signers.alice.address); + input.add64(transferAmount); + let encryptedTransferAmount = await input.encrypt(); + + await this.encryptedERC20 + .connect(this.signers.alice) + [ + "transfer(address,bytes32,bytes)" + ](this.signers.bob.address, encryptedTransferAmount.handles[0], encryptedTransferAmount.inputProof); + + tx = await this.encryptedERC20.connect(this.signers.bob).unwrap(amountToUnwrap); + await tx.wait(); + + await awaitAllDecryptionResults(); + + transferAmount = ethers.parseUnits("1000", 6); + input = this.instances.bob.createEncryptedInput(this.encryptedERC20Address, this.signers.bob.address); + input.add64(transferAmount); + encryptedTransferAmount = await input.encrypt(); + + await this.encryptedERC20 + .connect(this.signers.bob) + [ + "transfer(address,bytes32,bytes)" + ](this.signers.alice.address, encryptedTransferAmount.handles[0], encryptedTransferAmount.inputProof); + }); + + it("amount > 2**64 cannot be wrapped", async function () { + const amountToWrap = BigInt(2 ** 64); + + // @dev Verify 2**64 - 1 is fine. + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap - BigInt(1)); + await tx.wait(); + + // Unwrap all + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(amountToWrap - BigInt(1)); + await tx.wait(); + await awaitAllDecryptionResults(); + + // @dev Verify 2**64 is not fine + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + await expect(this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap)).to.be.revertedWithCustomError( + this.encryptedERC20, + "AmountTooHigh", + ); + }); + + it("only gateway can call callback functions", async function () { + await expect(this.encryptedERC20.connect(this.signers.alice).callbackUnwrap(1, false)).to.be.reverted; + }); +}); + +describe("EncryptedERC20Wrapped using ERC20 with 18 decimals", function () { + before(async function () { + await initSigners(2); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + const [erc20, encryptedERC20] = await deployERC20AndEncryptedERC20WrappedFixture( + this.signers, + "Naraggara", + "NARA", + 18, + ); + this.erc20 = erc20; + this.encryptedERC20 = encryptedERC20; + this.erc20ContractAddress = await erc20.getAddress(); + this.encryptedERC20Address = await encryptedERC20.getAddress(); + this.instances = await createInstances(this.signers); + }); + + it("can wrap", async function () { + const amountToWrap = "100000"; + const amountToWrap6Decimals = ethers.parseUnits(amountToWrap, 6); + const amountToWrap18Decimals = ethers.parseUnits(amountToWrap, 18); + + let tx = await this.erc20.mint(amountToWrap18Decimals); + await tx.wait(); + + // Check balance/totalSupply + expect(await this.erc20.balanceOf(this.signers.alice)).to.equal(amountToWrap18Decimals); + expect(await this.erc20.totalSupply()).to.equal(amountToWrap18Decimals); + + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap18Decimals); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap18Decimals); + await tx.wait(); + + // Check encrypted balance + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedERC20, this.encryptedERC20Address), + ).to.equal(amountToWrap6Decimals); + }); + + it("can unwrap", async function () { + const amountToWrap = "100000"; + const amountToWrap6Decimals = ethers.parseUnits(amountToWrap, 6); + const amountToWrap18Decimals = ethers.parseUnits(amountToWrap, 18); + const amountToUnwrap = "5000"; + const amountToUnwrap6Decimals = ethers.parseUnits(amountToUnwrap, 6); + const amountToUnwrap18Decimals = ethers.parseUnits(amountToUnwrap, 18); + + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap18Decimals); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap18Decimals); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap18Decimals); + await tx.wait(); + + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(amountToUnwrap6Decimals); + await tx.wait(); + + await awaitAllDecryptionResults(); + + expect(await this.erc20.balanceOf(this.signers.alice)).to.equal(amountToUnwrap18Decimals); + expect(await this.erc20.totalSupply()).to.equal(amountToWrap18Decimals); + + // Check encrypted balance + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedERC20, this.encryptedERC20Address), + ).to.equal(amountToWrap6Decimals - amountToUnwrap6Decimals); + + // Unwrap all + tx = await this.encryptedERC20.unwrap(amountToWrap6Decimals - amountToUnwrap6Decimals); + await tx.wait(); + + await awaitAllDecryptionResults(); + + expect(await this.erc20.balanceOf(this.signers.alice)).to.equal(amountToWrap18Decimals); + }); + + it("amount > 2**64 cannot be wrapped", async function () { + const amountToWrap = BigInt(2 ** 64) * ethers.parseUnits("1", 12); + + // @dev Verify 2**64 - 1 is fine. + let tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + tx = await this.encryptedERC20.connect(this.signers.alice).wrap(amountToWrap - BigInt(1)); + await tx.wait(); + + const totalSupply = await this.encryptedERC20.totalSupply(); + + // Unwrap all + tx = await this.encryptedERC20.connect(this.signers.alice).unwrap(totalSupply); + await tx.wait(); + await awaitAllDecryptionResults(); + + // @dev Verify 2**64 is not fine + // @dev There is a bit of loss due to precision issue when the unwrap operation took place. + tx = await this.erc20.connect(this.signers.alice).mint(amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.alice).transfer(this.signers.bob.address, amountToWrap); + await tx.wait(); + tx = await this.erc20.connect(this.signers.bob).approve(this.encryptedERC20Address, amountToWrap); + await tx.wait(); + + await expect(this.encryptedERC20.connect(this.signers.bob).wrap(amountToWrap)).to.be.revertedWithCustomError( + this.encryptedERC20, + "AmountTooHigh", + ); + }); +}); diff --git a/test/encryptedERC20/EncryptedWETH.fixture.ts b/test/encryptedERC20/EncryptedWETH.fixture.ts new file mode 100644 index 0000000..33092aa --- /dev/null +++ b/test/encryptedERC20/EncryptedWETH.fixture.ts @@ -0,0 +1,12 @@ +import { ethers } from "hardhat"; + +import type { TestEncryptedWETH } from "../../types"; +import { Signers } from "../signers"; + +export async function deployEncryptedWETHFixture(signers: Signers): Promise { + const contractFactoryEncryptedWETH = await ethers.getContractFactory("TestEncryptedWETH"); + const encryptedWETH = await contractFactoryEncryptedWETH.connect(signers.alice).deploy(); + await encryptedWETH.waitForDeployment(); + + return encryptedWETH; +} diff --git a/test/encryptedERC20/EncryptedWETH.test.ts b/test/encryptedERC20/EncryptedWETH.test.ts new file mode 100644 index 0000000..2c14ca9 --- /dev/null +++ b/test/encryptedERC20/EncryptedWETH.test.ts @@ -0,0 +1,217 @@ +import { expect } from "chai"; +import { parseUnits } from "ethers"; +import { ethers } from "hardhat"; + +import { awaitAllDecryptionResults } from "../asyncDecrypt"; +import { createInstances } from "../instance"; +import { getSigners, initSigners } from "../signers"; +import { reencryptBalance } from "./EncryptedERC20.fixture"; +import { deployEncryptedWETHFixture } from "./EncryptedWETH.fixture"; + +describe("EncryptedWETH", function () { + before(async function () { + await initSigners(3); + this.signers = await getSigners(); + this.instances = await createInstances(this.signers); + }); + + beforeEach(async function () { + const encryptedWETH = await deployEncryptedWETHFixture(this.signers); + this.encryptedWETH = encryptedWETH; + this.encryptedWETHAddress = await encryptedWETH.getAddress(); + }); + + it("name/symbol are automatically set, totalSupply = 0", async function () { + expect(await this.encryptedWETH.name()).to.eq("Encrypted Wrapped Ether"); + expect(await this.encryptedWETH.symbol()).to.eq("eWETH"); + expect(await this.encryptedWETH.totalSupply()).to.eq("0"); + }); + + it("can wrap", async function () { + const amountToWrap = "200"; + const amountToWrap6Decimals = ethers.parseUnits(amountToWrap, 6); + const amountToWrap18Decimals = ethers.parseUnits(amountToWrap, 18); + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap18Decimals + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + const tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap18Decimals }); + await tx.wait(); + + // Check encrypted balance + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedWETH, this.encryptedWETHAddress), + ).to.equal(amountToWrap6Decimals); + }); + + it("can unwrap", async function () { + const amountToWrap = "100000"; + const amountToWrap6Decimals = ethers.parseUnits(amountToWrap, 6); + const amountToWrap18Decimals = ethers.parseUnits(amountToWrap, 18); + const amountToUnwrap = "5000"; + const amountToUnwrap6Decimals = ethers.parseUnits(amountToUnwrap, 6); + + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap18Decimals + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + let tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap18Decimals }); + await tx.wait(); + + tx = await this.encryptedWETH.connect(this.signers.alice).unwrap(amountToUnwrap6Decimals); + await tx.wait(); + await awaitAllDecryptionResults(); + + // Check encrypted balance + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedWETH, this.encryptedWETHAddress), + ).to.equal(amountToWrap6Decimals - amountToUnwrap6Decimals); + + // Unwrap all + tx = await this.encryptedWETH.unwrap(amountToWrap6Decimals - amountToUnwrap6Decimals); + await tx.wait(); + await awaitAllDecryptionResults(); + + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedWETH, this.encryptedWETHAddress), + ).to.equal(BigInt("0")); + }); + + it("amount > 2**64 cannot be wrapped", async function () { + const amountToWrap = BigInt(2 ** 64) * ethers.parseUnits("1", 12); + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + // @dev Verify 2**64 - 1 is fine. + let tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap - BigInt(1) }); + await tx.wait(); + + const totalSupply = await this.encryptedWETH.totalSupply(); + + // Unwrap all + tx = await this.encryptedWETH.connect(this.signers.alice).unwrap(totalSupply); + await tx.wait(); + await awaitAllDecryptionResults(); + + // @dev Verify 2**64 is not fine + // @dev There is a bit of loss due to precision issue when the unwrap operation took place. + await ethers.provider.send("hardhat_setBalance", [this.signers.bob.address, "0x" + amountToMint.toString(16)]); + + await expect( + this.encryptedWETH.connect(this.signers.bob).wrap({ value: amountToWrap }), + ).to.be.revertedWithCustomError(this.encryptedWETH, "AmountTooHigh"); + }); + + it("cannot transfer after unwrap has been called but decryption has not occurred", async function () { + const amountToWrap = ethers.parseUnits("10000", 18); + const amountToUnwrap = ethers.parseUnits("5000", 6); + const transferAmount = ethers.parseUnits("3000", 6); + + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + let tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap }); + await tx.wait(); + + tx = await this.encryptedWETH.connect(this.signers.alice).unwrap(amountToUnwrap); + await tx.wait(); + + const input = this.instances.alice.createEncryptedInput(this.encryptedWETHAddress, this.signers.alice.address); + input.add64(transferAmount); + const encryptedTransferAmount = await input.encrypt(); + + await expect( + this.encryptedWETH + .connect(this.signers.alice) + [ + "transfer(address,bytes32,bytes)" + ](this.signers.bob.address, encryptedTransferAmount.handles[0], encryptedTransferAmount.inputProof), + ).to.be.revertedWithCustomError(this.encryptedWETH, "CannotTransferOrUnwrap"); + }); + + it("cannot call twice unwrap before decryption", async function () { + const amountToWrap = ethers.parseUnits("10000", 18); + const amountToUnwrap = ethers.parseUnits("5000", 6); + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + let tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap }); + await tx.wait(); + + tx = await this.encryptedWETH.connect(this.signers.alice).unwrap(amountToUnwrap); + await tx.wait(); + + await expect(this.encryptedWETH.connect(this.signers.alice).unwrap(amountToUnwrap)).to.be.revertedWithCustomError( + this.encryptedWETH, + "CannotTransferOrUnwrap", + ); + }); + + it("cannot unwrap more than balance", async function () { + const amountToWrap = "100000"; + const amountToWrap6Decimals = ethers.parseUnits(amountToWrap, 6); + const amountToWrap18Decimals = ethers.parseUnits(amountToWrap, 18); + const amountToUnwrap6Decimals = amountToWrap6Decimals + BigInt(1); + + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap18Decimals + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + let tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap18Decimals }); + await tx.wait(); + tx = await this.encryptedWETH.connect(this.signers.alice).unwrap(amountToUnwrap6Decimals); + await tx.wait(); + await awaitAllDecryptionResults(); + + // Verify the balances have not changed + expect(await ethers.provider.getBalance(this.encryptedWETHAddress)).to.equal(amountToWrap18Decimals); + expect(await this.encryptedWETH.totalSupply()).to.equal(amountToWrap6Decimals); + expect( + await reencryptBalance(this.signers, this.instances, "alice", this.encryptedWETH, this.encryptedWETHAddress), + ).to.equal(amountToWrap6Decimals); + }); + + it("transfers work outside of decryption period", async function () { + const amountToWrap = ethers.parseUnits("10000", 18); + const amountToUnwrap = ethers.parseUnits("2000", 6); + // @dev The amount to mint is greater than amountToWrap since each tx costs gas + const amountToMint = amountToWrap + ethers.parseUnits("1", 18); + await ethers.provider.send("hardhat_setBalance", [this.signers.alice.address, "0x" + amountToMint.toString(16)]); + + let tx = await this.encryptedWETH.connect(this.signers.alice).wrap({ value: amountToWrap }); + await tx.wait(); + + let transferAmount = ethers.parseUnits("3000", 6); + let input = this.instances.alice.createEncryptedInput(this.encryptedWETHAddress, this.signers.alice.address); + input.add64(transferAmount); + let encryptedTransferAmount = await input.encrypt(); + + await this.encryptedWETH + .connect(this.signers.alice) + [ + "transfer(address,bytes32,bytes)" + ](this.signers.bob.address, encryptedTransferAmount.handles[0], encryptedTransferAmount.inputProof); + + tx = await this.encryptedWETH.connect(this.signers.bob).unwrap(amountToUnwrap); + await tx.wait(); + await awaitAllDecryptionResults(); + + transferAmount = ethers.parseUnits("1000", 6); + input = this.instances.bob.createEncryptedInput(this.encryptedWETHAddress, this.signers.bob.address); + input.add64(transferAmount); + encryptedTransferAmount = await input.encrypt(); + + await this.encryptedWETH + .connect(this.signers.bob) + [ + "transfer(address,bytes32,bytes)" + ](this.signers.alice.address, encryptedTransferAmount.handles[0], encryptedTransferAmount.inputProof); + }); + + it("only gateway can call callback functions", async function () { + await expect(this.encryptedWETH.connect(this.signers.alice).callbackUnwrap(1, false)).to.be.reverted; + }); +});