hardhat#waffle TypeScript Examples

The following examples show how to use hardhat#waffle. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: 04_Guard.spec.ts    From zodiac with GNU Lesser General Public License v3.0 6 votes vote down vote up
describe("Guardable", async () => {
  const [user1, user2] = waffle.provider.getWallets();

  const setupTests = deployments.createFixture(async ({ deployments }) => {
    await deployments.fixture();
    const Avatar = await hre.ethers.getContractFactory("TestAvatar");
    const avatar = await Avatar.deploy();
    const iAvatar = await hre.ethers.getContractAt("IAvatar", avatar.address);
    const Module = await hre.ethers.getContractFactory("TestModule");
    const module = await Module.deploy(iAvatar.address, iAvatar.address);
    await avatar.enableModule(module.address);
    const Guard = await hre.ethers.getContractFactory("TestGuard");
    const guard = await Guard.deploy(module.address);
    return {
      iAvatar,
      guard,
      module,
    };
  });

  describe("setGuard", async () => {
    it("reverts if reverts if caller is not the owner", async () => {
      const { module } = await setupTests();
      await expect(
        module.connect(user2).setGuard(user2.address)
      ).to.be.revertedWith("Ownable: caller is not the owner");
    });

    it("reverts if guard does not implement ERC165", async () => {
      const { module } = await setupTests();
      await expect(module.setGuard(module.address)).to.be.reverted;
    });

    it("sets module and emits event", async () => {
      const { module, guard } = await setupTests();
      await expect(module.setGuard(guard.address))
        .to.emit(module, "ChangedGuard")
        .withArgs(guard.address);
    });
  });

  describe("getGuard", async () => {
    it("returns guard address", async () => {
      const { module } = await setupTests();
      await expect(await module.getGuard()).to.be.equals(AddressZero);
    });
  });
});
Example #2
Source File: deposit_withdraw.test.ts    From hypervisor with The Unlicense 5 votes vote down vote up
createFixtureLoader = waffle.createFixtureLoader
Example #3
Source File: TestUtils.ts    From eth with GNU General Public License v3.0 5 votes vote down vote up
fixtureLoader = waffle.createFixtureLoader()
Example #4
Source File: 01_IAvatar.spec.ts    From zodiac with GNU Lesser General Public License v3.0 5 votes vote down vote up
describe("IAvatar", async () => {
  const [user1, user2] = waffle.provider.getWallets();

  const setupTests = deployments.createFixture(async ({ deployments }) => {
    await deployments.fixture();
    const Avatar = await hre.ethers.getContractFactory("TestAvatar");
    const avatar = await Avatar.deploy();
    const iAvatar = await hre.ethers.getContractAt("IAvatar", avatar.address);
    const tx = {
      to: avatar.address,

      value: 0,
      data: "0x",
      operation: 0,
      avatarTxGas: 0,
      baseGas: 0,
      gasPrice: 0,
      gasToken: AddressZero,
      refundReceiver: AddressZero,
      signatures: "0x",
    };
    return {
      iAvatar,
      tx,
    };
  });

  describe("enableModule", async () => {
    it("allow to enable a module", async () => {
      const { iAvatar } = await setupTests();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        false
      );
      let transaction = await iAvatar.enableModule(user1.address);
      let receipt = await transaction.wait();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        true
      );
    });
  });

  describe("disableModule", async () => {
    it("allow to disable a module", async () => {
      const { iAvatar } = await setupTests();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        false
      );
      let transaction = await iAvatar.enableModule(user1.address);
      let receipt = await transaction.wait();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        true
      );
      transaction = await iAvatar.disableModule(AddressZero, user1.address);
      receipt = await transaction.wait();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        false
      );
    });
  });

  describe("execTransactionFromModule", async () => {
    it("revert if module is not enabled", async () => {
      const { iAvatar, tx } = await setupTests();
      await expect(
        iAvatar.execTransactionFromModule(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).to.be.revertedWith("Not authorized");
    });

    it("allow to execute module transaction", async () => {
      const { iAvatar, tx } = await setupTests();
      await iAvatar.enableModule(user1.address);
      await expect(
        iAvatar.execTransactionFromModule(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      );
    });
  });

  describe("execTransactionFromModuleReturnData", async () => {
    it("revert if module is not enabled", async () => {
      const { iAvatar, tx } = await setupTests();
      await expect(
        iAvatar.execTransactionFromModuleReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).to.be.revertedWith("Not authorized");
    });

    it("allow to execute module transaction and return data", async () => {
      const { iAvatar, tx } = await setupTests();
      await iAvatar.enableModule(user1.address);
      await expect(
        iAvatar.execTransactionFromModuleReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      );
    });
  });

  describe("isModuleEnabled", async () => {
    it("returns false if module has not been enabled", async () => {
      const { iAvatar } = await setupTests();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        false
      );
    });

    it("returns true if module has been enabled", async () => {
      const { iAvatar } = await setupTests();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        false
      );
      let transaction = await iAvatar.enableModule(user1.address);
      let receipt = await transaction.wait();
      await expect(await iAvatar.isModuleEnabled(user1.address)).to.be.equals(
        true
      );
    });
  });

  describe("getModulesPaginated", async () => {
    it("returns array of enabled modules", async () => {
      const { iAvatar } = await setupTests();
      let transaction = await iAvatar.enableModule(user1.address);
      let array, next;
      [array, next] = await iAvatar.getModulesPaginated(user1.address, 1);
      await expect(array.toString()).to.be.equals([user1.address].toString());
      await expect(next).to.be.equals(user1.address);
    });
  });
});
Example #5
Source File: 02_Module.spec.ts    From zodiac with GNU Lesser General Public License v3.0 5 votes vote down vote up
describe("Module", async () => {
  const [user1, user2] = waffle.provider.getWallets();

  const setupTests = deployments.createFixture(async ({ deployments }) => {
    await deployments.fixture();
    const Avatar = await hre.ethers.getContractFactory("TestAvatar");
    const avatar = await Avatar.deploy();
    const iAvatar = await hre.ethers.getContractAt("IAvatar", avatar.address);
    const Module = await hre.ethers.getContractFactory("TestModule");
    const module = await Module.deploy(iAvatar.address, iAvatar.address);
    await avatar.enableModule(module.address);
    const Guard = await hre.ethers.getContractFactory("TestGuard");
    const guard = await Guard.deploy(module.address);
    const tx = {
      to: avatar.address,
      value: 0,
      data: "0x",
      operation: 0,
      avatarTxGas: 0,
      baseGas: 0,
      gasPrice: 0,
      gasToken: AddressZero,
      refundReceiver: AddressZero,
      signatures: "0x",
    };
    return {
      iAvatar,
      guard,
      module,
      tx,
    };
  });

  describe("setAvatar", async () => {
    it("reverts if caller is not the owner", async () => {
      const { iAvatar, module } = await setupTests();
      await module.transferOwnership(user2.address);
      await expect(module.setAvatar(iAvatar.address)).to.be.revertedWith(
        "Ownable: caller is not the owner"
      );
    });

    it("allows owner to set avatar", async () => {
      const { iAvatar, module } = await setupTests();
      await expect(module.setAvatar(iAvatar.address));
    });

    it("emits previous owner and new owner", async () => {
      const { iAvatar, module } = await setupTests();
      await expect(module.setAvatar(user2.address))
        .to.emit(module, "AvatarSet")
        .withArgs(iAvatar.address, user2.address);
    });
  });

  describe("setTarget", async () => {
    it("reverts if caller is not the owner", async () => {
      const { iAvatar, module } = await setupTests();
      await module.transferOwnership(user2.address);
      await expect(module.setTarget(iAvatar.address)).to.be.revertedWith(
        "Ownable: caller is not the owner"
      );
    });

    it("allows owner to set avatar", async () => {
      const { iAvatar, module } = await setupTests();
      await expect(module.setTarget(iAvatar.address));
    });

    it("emits previous owner and new owner", async () => {
      const { iAvatar, module } = await setupTests();
      await expect(module.setTarget(user2.address))
        .to.emit(module, "TargetSet")
        .withArgs(iAvatar.address, user2.address);
    });
  });

  describe("exec", async () => {
    it("skips guard pre-check if no guard is set", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await expect(
        module.executeTransaction(tx.to, tx.value, tx.data, tx.operation)
      );
    });

    it("pre-checks transaction if guard is set", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await module.setGuard(guard.address);
      await expect(
        module.executeTransaction(tx.to, tx.value, tx.data, tx.operation)
      )
        .to.emit(guard, "PreChecked")
        .withArgs(true);
    });

    it("executes a transaction", async () => {
      const { iAvatar, module, tx } = await setupTests();
      await expect(
        module.executeTransaction(tx.to, tx.value, tx.data, tx.operation)
      );
    });

    it("skips post-check if no guard is enabled", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await expect(
        module.executeTransaction(tx.to, tx.value, tx.data, tx.operation)
      ).not.to.emit(guard, "PostChecked");
    });

    it("post-checks transaction if guard is set", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await module.setGuard(guard.address);
      await expect(
        module.executeTransaction(tx.to, tx.value, tx.data, tx.operation)
      )
        .to.emit(guard, "PostChecked")
        .withArgs(true);
    });
  });

  describe("execAndReturnData", async () => {
    it("skips guard pre-check if no guard is set", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await expect(
        module.executeTransactionReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      );
    });

    it("pre-checks transaction if guard is set", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await module.setGuard(guard.address);
      await expect(
        module.executeTransactionReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      )
        .to.emit(guard, "PreChecked")
        .withArgs(true);
    });

    it("executes a transaction", async () => {
      const { iAvatar, module, tx } = await setupTests();
      await expect(
        module.executeTransactionReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      );
    });

    it("skips post-check if no guard is enabled", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await expect(
        module.executeTransactionReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).not.to.emit(guard, "PostChecked");
    });

    it("post-checks transaction if guard is set", async () => {
      const { iAvatar, guard, module, tx } = await setupTests();
      await module.setGuard(guard.address);
      await expect(
        module.executeTransactionReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      )
        .to.emit(guard, "PostChecked")
        .withArgs(true);
    });
  });
});
Example #6
Source File: 03_Modifier.spec.ts    From zodiac with GNU Lesser General Public License v3.0 5 votes vote down vote up
describe("Modifier", async () => {
  const [user1, user2] = waffle.provider.getWallets();
  const SENTINEL_MODULES = "0x0000000000000000000000000000000000000001";

  const setupTests = deployments.createFixture(async ({ deployments }) => {
    await deployments.fixture();
    const Avatar = await hre.ethers.getContractFactory("TestAvatar");
    const avatar = await Avatar.deploy();
    const iAvatar = await hre.ethers.getContractAt("IAvatar", avatar.address);
    const Modifier = await hre.ethers.getContractFactory("TestModifier");
    const modifier = await Modifier.deploy(iAvatar.address, iAvatar.address);
    await iAvatar.enableModule(modifier.address);
    // await modifier.enableModule(user1.address);
    const tx = {
      to: avatar.address,
      value: 0,
      data: "0x",
      operation: 0,
      avatarTxGas: 0,
      baseGas: 0,
      gasPrice: 0,
      gasToken: AddressZero,
      refundReceiver: AddressZero,
      signatures: "0x",
    };
    return {
      iAvatar,
      modifier,
      tx,
    };
  });

  describe("enableModule", async () => {
    it("reverts if caller is not the owner", async () => {
      const { modifier } = await setupTests();
      await expect(
        modifier.connect(user2).enableModule(user2.address)
      ).to.be.revertedWith("Ownable: caller is not the owner");
    });

    it("reverts if module is zero address", async () => {
      const { modifier } = await setupTests();
      await expect(modifier.enableModule(AddressZero)).to.be.revertedWith(
        "reverted with custom error 'InvalidModule(\"0x0000000000000000000000000000000000000000\")'"
      );
    });

    it("reverts if module is SENTINEL_MODULES", async () => {
      const { iAvatar, modifier } = await setupTests();
      await expect(modifier.enableModule(SENTINEL_MODULES)).to.be.revertedWith(
        "reverted with custom error 'InvalidModule(\"0x0000000000000000000000000000000000000001\")'"
      );
    });

    it("reverts if module is already enabled", async () => {
      const { modifier } = await setupTests();
      await expect(modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      await expect(modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      await expect(modifier.enableModule(user1.address)).to.be.revertedWith(
        "reverted with custom error 'AlreadyEnabledModule(\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\")'"
      );
    });

    it("enables a module", async () => {
      const { modifier } = await setupTests();
      await expect(modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
    });
  });

  describe("disableModule", async () => {
    it("reverts if caller is not the owner", async () => {
      const { modifier } = await setupTests();
      await expect(
        modifier.connect(user2).disableModule(SENTINEL_MODULES, user2.address)
      ).to.be.revertedWith("Ownable: caller is not the owner");
    });

    it("reverts if module is zero address", async () => {
      const { modifier } = await setupTests();
      await expect(
        modifier.disableModule(SENTINEL_MODULES, AddressZero)
      ).to.be.revertedWith("reverted with custom error 'InvalidModule(\"0x0000000000000000000000000000000000000000\")'");
    });

    it("reverts if module is SENTINEL_MODULES", async () => {
      const { modifier } = await setupTests();
      await expect(
        modifier.disableModule(SENTINEL_MODULES, SENTINEL_MODULES)
      ).to.be.revertedWith("reverted with custom error 'InvalidModule(\"0x0000000000000000000000000000000000000001\")'");
    });

    it("reverts if module is already disabled", async () => {
      const { modifier } = await setupTests();
      await expect(modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      await expect(modifier.disableModule(SENTINEL_MODULES, user1.address))
        .to.emit(modifier, "DisabledModule")
        .withArgs(user1.address);
      await expect(
        modifier.disableModule(SENTINEL_MODULES, user1.address)
      ).to.be.revertedWith("reverted with custom error 'AlreadyDisabledModule(\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\")'");
    });

    it("disables a module", async () => {
      const { modifier } = await setupTests();
      await expect(modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      await expect(modifier.disableModule(SENTINEL_MODULES, user1.address))
        .to.emit(modifier, "DisabledModule")
        .withArgs(user1.address);
    });
  });

  describe("isModuleEnabled", async () => {
    it("returns false if SENTINEL_MODULES is provided", async () => {
      const { modifier } = await setupTests();
      await expect(
        await modifier.isModuleEnabled(SENTINEL_MODULES)
      ).to.be.equals(false);
    });

    it("returns false if AddressZero is provided", async () => {
      const { modifier } = await setupTests();
      await expect(await modifier.isModuleEnabled(AddressZero)).to.be.equals(
        false
      );
    });

    it("returns false if module is not enabled", async () => {
      const { modifier } = await setupTests();
      await expect(await modifier.isModuleEnabled(user1.address)).to.be.equals(
        false
      );
    });

    it("returns true if module is enabled", async () => {
      const { modifier } = await setupTests();
      // delete once you figure out why you need to do this twice
      await expect(await modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);

      await expect(await modifier.enableModule(user2.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user2.address);
      await expect(await modifier.isModuleEnabled(user2.address)).to.be.equals(
        true
      );
    });
  });

  describe("getModulesPaginated", async () => {
    it("returns empty array if no modules are enabled.", async () => {
      const { modifier } = await setupTests();
      let tx = await modifier.getModulesPaginated(SENTINEL_MODULES, 3);
      tx = tx.toString();
      await expect(tx).to.be.equals(
        [[], "0x0000000000000000000000000000000000000000"].toString()
      );
    });

    it("returns one module if one module is enabled", async () => {
      const { modifier } = await setupTests();
      await modifier.enableModule(user1.address);
      let tx = await modifier.getModulesPaginated(SENTINEL_MODULES, 3);
      tx = tx.toString();
      await expect(tx).to.be.equals(
        [
          [user1.address],
          "0x0000000000000000000000000000000000000000",
        ].toString()
      );
    });

    it("returns two modules if two modules are enabled", async () => {
      const { modifier } = await setupTests();
      await expect(modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      // delete once you figure out why you need to do this twice
      await expect(modifier.enableModule(user2.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user2.address);
      let tx = await modifier.getModulesPaginated(SENTINEL_MODULES, 3);
      tx = tx.toString();
      await expect(tx).to.be.equals(
        [
          user2.address,
          user1.address,
          "0x0000000000000000000000000000000000000000",
        ].toString()
      );
    });
  });

  describe("execTransactionFromModule", async () => {
    it("reverts if module is not enabled", async () => {
      const { iAvatar, modifier, tx } = await setupTests();
      await expect(
        modifier.execTransactionFromModule(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).to.be.revertedWith("reverted with custom error 'NotAuthorized(\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\")'");
    });

    it("execute a transaction.", async () => {
      const { iAvatar, modifier, tx } = await setupTests();
      await expect(await modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      // delete once you figure out why you need to do this twice
      await expect(await modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);

      await expect(
        modifier.execTransactionFromModule(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).to.emit(modifier, "executed");
    });
  });

  describe("execTransactionFromModuleReturnData", async () => {
    it("reverts if module is not enabled", async () => {
      const { iAvatar, modifier, tx } = await setupTests();
      await expect(
        modifier.execTransactionFromModuleReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).to.be.revertedWith("reverted with custom error 'NotAuthorized(\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\")'");
    });

    it("execute a transaction.", async () => {
      const { iAvatar, modifier, tx } = await setupTests();
      await expect(await modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);
      // delete once you figure out why you need to do this twice
      await expect(await modifier.enableModule(user1.address))
        .to.emit(modifier, "EnabledModule")
        .withArgs(user1.address);

      await expect(
        modifier.execTransactionFromModuleReturnData(
          tx.to,
          tx.value,
          tx.data,
          tx.operation
        )
      ).to.emit(modifier, "executedAndReturnedData");
    });
  });
});
Example #7
Source File: 04_Guard.spec.ts    From zodiac with GNU Lesser General Public License v3.0 5 votes vote down vote up
describe("BaseGuard", async () => {
  const [user1, user2] = waffle.provider.getWallets();
  const txHash =
    "0x0000000000000000000000000000000000000000000000000000000000000001";

  const setupTests = deployments.createFixture(async ({ deployments }) => {
    await deployments.fixture();
    const Avatar = await hre.ethers.getContractFactory("TestAvatar");
    const avatar = await Avatar.deploy();
    const iAvatar = await hre.ethers.getContractAt("IAvatar", avatar.address);
    const Module = await hre.ethers.getContractFactory("TestModule");
    const module = await Module.deploy(iAvatar.address, iAvatar.address);
    await avatar.enableModule(module.address);
    const Guard = await hre.ethers.getContractFactory("TestGuard");
    const guard = await Guard.deploy(module.address);
    const tx = {
      to: avatar.address,
      value: 0,
      data: "0x",
      operation: 0,
      avatarTxGas: 0,
      baseGas: 0,
      gasPrice: 0,
      gasToken: AddressZero,
      refundReceiver: AddressZero,
      signatures: "0x",
    };
    return {
      iAvatar,
      guard,
      module,
      tx,
    };
  });

  describe("checkTransaction", async () => {
    it("reverts if test fails", async () => {
      const { guard, tx } = await setupTests();
      await expect(
        guard.checkTransaction(
          tx.to,
          1337,
          tx.data,
          tx.operation,
          tx.avatarTxGas,
          tx.baseGas,
          tx.gasPrice,
          tx.gasToken,
          tx.refundReceiver,
          tx.signatures,
          user1.address
        )
      ).to.be.revertedWith("Cannot send 1337");
    });
    it("checks transaction", async () => {
      const { guard, tx } = await setupTests();
      await expect(
        guard.checkTransaction(
          tx.to,
          tx.value,
          tx.data,
          tx.operation,
          tx.avatarTxGas,
          tx.baseGas,
          tx.gasPrice,
          tx.gasToken,
          tx.refundReceiver,
          tx.signatures,
          user1.address
        )
      )
        .to.emit(guard, "PreChecked")
        .withArgs(true);
    });
  });

  describe("checkAfterExecution", async () => {
    it("reverts if test fails", async () => {
      const { module, guard, tx } = await setupTests();
      await expect(guard.checkAfterExecution(txHash, true)).to.be.revertedWith(
        "Module cannot remove its own guard."
      );
    });
    it("checks state after execution", async () => {
      const { module, guard, tx } = await setupTests();
      await expect(module.setGuard(guard.address))
        .to.emit(module, "ChangedGuard")
        .withArgs(guard.address);
      await expect(guard.checkAfterExecution(txHash, true))
        .to.emit(guard, "PostChecked")
        .withArgs(true);
    });
  });
});
Example #8
Source File: main.ts    From pawnft with GNU General Public License v3.0 4 votes vote down vote up
describe("PawnBank", () => {
  // Pre-setup
  beforeEach(async () => {
    // Reset hardhat forknet
    await network.provider.request({
      method: "hardhat_reset",
      params: [
        {
          forking: {
            jsonRpcUrl: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
            blockNumber: 12864983,
          },
        },
      ],
    });

    // Deploy contract
    await deploy();

    // Scaffold initial loan
    await scaffoldLoan();
  });

  describe("Loan creation", () => {
    it("Should allow creating a loan", async () => {
      // Collect details
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
      const SquiggleOGOwner: string = (await PawnBankContract.pawnLoans(1)).tokenOwner;

      // Verify that contract holds NFT
      expect(SquiggleOwner).to.equal(PawnBankContractAddress);
      // Verify that PawnLoan is created with correct owner
      expect(SquiggleOGOwner).to.equal(ADDRESSES.SNOWFRO);
    });

    it("Should prevent creating a loan in the past", async () => {
      const { SnowfroBank } = await impersonateBanks();

      // Force delete scaffold loan
      await SnowfroBank.cancelLoan(1);

      // Approve NFT for transfer
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      await SquigglesContract.approve(PawnBankContractAddress, SQUIGGLE_0);

      // Create loan w/ Chrome Squiggle #0 in past
      const tx: Transaction = SnowfroBank.createLoan(
        // Token address
        ADDRESSES.SQUIGGLE,
        // Token ID
        SQUIGGLE_0,
        // Interest rate
        5,
        // Max loan amount
        ethers.utils.parseEther("10"),
        // Loan completion time (5 years ago)
        Math.floor(Date.now() / 1000) - 157680000
      );

      // Expect tx to revert because loan completion time < current time
      await expect(tx).revertedWith(ERROR_MESSAGES.CREATE.NO_EXPIRED_LOAN);
    });
  });

  describe("Loan underwriting", () => {
    it("Should allow underwriting a new loan", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Collect details
      const PawnBankBalance: BigNumber = await waffle.provider.getBalance(PawnBankContractAddress);
      const PawnLoanDetails = await LenderOneBank.pawnLoans(1);

      // Verify contract balance is increased
      expect(PawnBankBalance.toString()).to.equal("1000000000000000000");
      // Verify loan has 1 ether available to draw
      expect(PawnLoanDetails.loanAmount.toString()).to.equal("1000000000000000000");
      // Verify loan has 0 capital drawn
      expect(PawnLoanDetails.loanAmountDrawn.toString()).to.equal("0");
      // Verify new lender is Lender One
      expect(PawnLoanDetails.lender).to.equal(ADDRESSES.LENDER_ONE);
    });

    it("Should prevent underwriting a new loan with 0 Ether", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Back loan with 0 ETH
      const tx: Transaction = LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("0.0"),
      });

      // Expect tx to fail
      await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.NO_0_UNDERWRITE);
    });

    it("Should prevent underwriting a repaid loan", async () => {
      const { SnowfroBank, LenderOneBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Draw and repay loan
      await SnowfroBank.drawLoan(1);
      const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
      await SnowfroBank.repayLoan(1, { value: repaymentAmount.mul(101).div(100) });

      // Attempt to re-underwrite loan
      const tx: Transaction = LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.1"),
      });

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.ALREADY_REPAID);
    });

    it("Should prevent underwriting an expired loan", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Fast forward >1h
      await network.provider.send("evm_increaseTime", [3601]);
      await network.provider.send("evm_mine");

      // Back loan with 1 ETH
      const tx: Transaction = LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.EXPIRED);
    });

    it("Should prevent underwriting a loan under the current top bid", async () => {
      const { LenderOneBank, LenderTwoBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Back loan with <1 ETH
      const tx: Transaction = LenderTwoBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("0.5"),
      });

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.INSUFFICIENT_BID);
    });

    it("Should prevent underwriting a loan over the maxLoanAmount", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Back loan with 11 ETH
      const tx: Transaction = LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("11.0"),
      });

      // Expect tx to fail
      await expect(tx).revertedWith(ERROR_MESSAGES.UNDERWRITE.OVER_MAX_UNDERWRITE);
    });

    it("Should allow underwriting a loan with existing bid", async () => {
      const { LenderOneBank, LenderTwoBank } = await impersonateBanks();

      // First lender balance
      const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);

      // Back loan with 5 ETH
      const tx = await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });
      const receipt = await waffle.provider.getTransactionReceipt(tx.hash);

      // Back loan with 6 ETH
      const totalInterest: BigNumber = await LenderTwoBank.calculateTotalInterest(1, 1);
      const higherLoan: BigNumber = ethers.utils.parseEther("6.0").add(totalInterest);
      await LenderTwoBank.underwriteLoan(1, { value: higherLoan });

      // Collect details
      const NewTopLender: string = (await LenderTwoBank.pawnLoans(1)).lender;
      const expectedLenderOneBalance: BigNumber = previousBalance
        .sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
        .add(totalInterest);
      const acutalLenderOneBalance: BigNumber = await waffle.provider.getBalance(
        ADDRESSES.LENDER_ONE
      );

      // Verify first lender received principle + interest
      expect(acutalLenderOneBalance).to.be.gte(expectedLenderOneBalance);
      // Verify second lender is now top bidder
      expect(NewTopLender).to.equal(ADDRESSES.LENDER_TWO);
    });
  });

  describe("Loan drawing", () => {
    it("Should allow drawing from a loan", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 5 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Collect previous Snowfro balance
      const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);

      // Draw 5ETH
      const tx = await SnowfroBank.drawLoan(1);
      const receipt = await waffle.provider.getTransactionReceipt(tx.hash);

      // Collect after Snowfro balance
      const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
      const expectedAfterBalance: BigNumber = previousBalance
        .sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
        .add(ethers.utils.parseEther("5.0"));

      expect(afterBalance).to.equal(expectedAfterBalance);
    });

    it("Should allow drawing additional capital from a new bid", async () => {
      const { LenderOneBank, LenderTwoBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 5 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Collect previous Snowfro balance
      const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);

      // Draw 5ETH
      const tx = await SnowfroBank.drawLoan(1);
      const receipt = await waffle.provider.getTransactionReceipt(tx.hash);

      // Check for Snowfro balance increment
      const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
      const expectedAfterBalance: BigNumber = previousBalance
        .sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
        .add(ethers.utils.parseEther("5.0"));
      expect(afterBalance).to.equal(expectedAfterBalance);

      // Back loan with 6 ETH
      const totalInterest: BigNumber = await LenderTwoBank.calculateTotalInterest(1, 5);
      const higherLoan: BigNumber = ethers.utils.parseEther("6.0").add(totalInterest);
      await LenderTwoBank.underwriteLoan(1, { value: higherLoan });

      // Draw 6ETH
      const tx2 = await SnowfroBank.drawLoan(1);
      const receipt2 = await waffle.provider.getTransactionReceipt(tx2.hash);

      // Check for Snowfro balance increment
      const afterBalance2: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
      const expectedAfterBalance2: BigNumber = afterBalance
        .sub(tx2.gasPrice.mul(receipt2.cumulativeGasUsed))
        .add(ethers.utils.parseEther("1.0"));

      expect(afterBalance2).to.be.gte(expectedAfterBalance2);
    });

    it("Should prevent drawing from a loan with no bids", async () => {
      const { SnowfroBank } = await impersonateBanks();

      // Attempt to draw ETH
      const tx: Transaction = SnowfroBank.drawLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.MAX_CAPACITY);
    });

    it("Should prevent non-owners from drawing from loan", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Back loan with 5 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Attempt to collect loan as Lender
      const tx: Transaction = LenderOneBank.drawLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.NOT_OWNER);
    });

    it("Should prevent consecutive draws from a loan", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 5 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Draw 5ETH
      await SnowfroBank.drawLoan(1);
      // Attempt to redraw
      const tx: Transaction = SnowfroBank.drawLoan(1);

      await expect(tx).revertedWith(ERROR_MESSAGES.DRAW.MAX_CAPACITY);
    });

    it("Should allow drawing loan after NFT seizure", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Fast forward >1h
      await network.provider.send("evm_increaseTime", [3601]);
      await network.provider.send("evm_mine");

      // Seize NFT
      await LenderOneBank.seizeNFT(1);

      // Collect previous Snowfro balance
      const previousBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);

      // Draw 5ETH
      const tx = await SnowfroBank.drawLoan(1);
      const receipt = await waffle.provider.getTransactionReceipt(tx.hash);

      // Collect after Snowfro balance
      const afterBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.SNOWFRO);
      const expectedAfterBalance: BigNumber = previousBalance
        .sub(tx.gasPrice.mul(receipt.cumulativeGasUsed))
        .add(ethers.utils.parseEther("5.0"));

      // Expect increase in balance
      expect(afterBalance).to.equal(expectedAfterBalance);
    });
  });

  describe("Loan repayment", () => {
    it("Should allow repaying loan", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 5 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Record Lender balance
      const previousLenderBalance: BigNumber = await waffle.provider.getBalance(
        ADDRESSES.LENDER_ONE
      );

      // Fast forward <1h
      await network.provider.send("evm_increaseTime", [3500]);
      await network.provider.send("evm_mine");

      // Calculate repayment amount
      const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
      const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);

      // Repay loan
      await SnowfroBank.repayLoan(1, { value: repaymentBuffer });

      // Expect loan to be closed
      const NewLoanOwner: string = (await SnowfroBank.pawnLoans(1)).tokenOwner;
      expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);

      // Expect NFT to be owned by original owner
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
      expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);

      // Expect increase in Lender One balance by ~.243 ETH
      const afterLenderBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
      expect(afterLenderBalance).to.be.gte(previousLenderBalance.add(repaymentAmount));
    });

    it("Should allow repaying someone elses loan", async () => {
      const { LenderOneBank, LenderTwoBank } = await impersonateBanks();

      // Back loan with 5 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("5.0"),
      });

      // Record Lender balance
      const previousLenderBalance: BigNumber = await waffle.provider.getBalance(
        ADDRESSES.LENDER_ONE
      );

      // Fast forward <1h
      await network.provider.send("evm_increaseTime", [3500]);
      await network.provider.send("evm_mine");

      // Calculate repayment amount
      const repaymentAmount: BigNumber = await LenderTwoBank.calculateRequiredRepayment(1, 0);
      const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);

      // Repay loan
      await LenderTwoBank.repayLoan(1, { value: repaymentBuffer });

      // Expect loan to be closed
      const NewLoanOwner: string = (await LenderTwoBank.pawnLoans(1)).tokenOwner;
      expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);

      // Expect NFT to be owned by original owner
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
      expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);

      // Expect increase in Lender One balance by ~.243 ETH
      const afterLenderBalance: BigNumber = await waffle.provider.getBalance(ADDRESSES.LENDER_ONE);
      expect(afterLenderBalance).to.be.gte(previousLenderBalance.add(repaymentAmount));
    });

    it("Should prevent repaying loan w/ 0 bids", async () => {
      const { SnowfroBank } = await impersonateBanks();

      // Attempt to repay loan
      const tx: Transaction = SnowfroBank.repayLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.NO_BIDS);
    });

    it("Should prevent repaying expired loan", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Fast forward >1h
      await network.provider.send("evm_increaseTime", [3601]);
      await network.provider.send("evm_mine");

      // Attempt to repay loan
      const tx: Transaction = SnowfroBank.repayLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.EXPIRED);
    });

    it("Should prevent repaying paid loan", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Fast forward <1h
      await network.provider.send("evm_increaseTime", [3500]);
      await network.provider.send("evm_mine");

      // Calculate repayment amount
      const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
      const repaymentBuffer: BigNumber = repaymentAmount.mul(101).div(100);

      // Repay loan
      await SnowfroBank.repayLoan(1, { value: repaymentBuffer });

      // Attempt to re-repay loan
      const tx: Transaction = SnowfroBank.repayLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.REPAY.ALREADY_REPAID);
    });
  });

  describe("Loan cancellation", () => {
    it("Should allow cancelling loan with 0 bids", async () => {
      const { SnowfroBank } = await impersonateBanks();

      // Cancel loan
      await SnowfroBank.cancelLoan(1);

      // Collect details
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);
      const NewLoanOwner: string = (await SnowfroBank.pawnLoans(1)).tokenOwner;

      // Verify that Snowfro holds NFT
      expect(SquiggleOwner).to.equal(ADDRESSES.SNOWFRO);
      // Verify that PawnLoan is nullifed w/ 0 address
      expect(NewLoanOwner).to.equal(ADDRESSES.ZERO);
    });

    it("Should prevent cancelling loan with >0 bids", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Attempt to cancel loan with 1.0 bid existing
      const tx: Transaction = SnowfroBank.cancelLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.CANCEL.NON_ZERO_BIDS);
    });

    it("Should prevent cancelling loan if not owner", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Attempt to cancel loan as Lender One
      const tx: Transaction = LenderOneBank.cancelLoan(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.CANCEL.NOT_OWNER);
    });
  });

  describe("Loan seizing", () => {
    it("Should allow lender to seize NFT", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Fast forward >1h
      await network.provider.send("evm_increaseTime", [3601]);
      await network.provider.send("evm_mine");

      // Seize NFT
      await LenderOneBank.seizeNFT(1);

      // Collect details
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);

      // Verify that Lender One holds NFT
      expect(SquiggleOwner).to.equal(ADDRESSES.LENDER_ONE);
    });

    it("Should allow anyone to seize NFT on behalf of lender", async () => {
      const { LenderOneBank, LenderTwoBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Fast forward >1h
      await network.provider.send("evm_increaseTime", [3601]);
      await network.provider.send("evm_mine");

      // Seize NFT
      await LenderTwoBank.seizeNFT(1);

      // Collect details
      const SquigglesContract: Contract = await getSquigglesContract(ADDRESSES.SNOWFRO);
      const SquiggleOwner: string = await SquigglesContract.ownerOf(SQUIGGLE_0);

      // Verify that Lender One holds NFT
      expect(SquiggleOwner).to.equal(ADDRESSES.LENDER_ONE);
    });

    it("Should prevent seizing NFT before loan expiration", async () => {
      const { LenderOneBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Seize NFT
      const tx: Transaction = LenderOneBank.seizeNFT(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.SEIZE.NOT_EXPIRED);
    });

    it("Should prevent seizing repaid loan", async () => {
      const { LenderOneBank, SnowfroBank } = await impersonateBanks();

      // Back loan with 1 ETH
      await LenderOneBank.underwriteLoan(1, {
        value: ethers.utils.parseEther("1.0"),
      });

      // Draw and repay loan
      await SnowfroBank.drawLoan(1);
      const repaymentAmount: BigNumber = await SnowfroBank.calculateRequiredRepayment(1, 0);
      await SnowfroBank.repayLoan(1, { value: repaymentAmount.mul(101).div(100) });

      // Seize NFT
      const tx: Transaction = LenderOneBank.seizeNFT(1);

      // Expect revert
      await expect(tx).revertedWith(ERROR_MESSAGES.SEIZE.ALREADY_REPAID);
    });
  });
});
Example #9
Source File: deposit_withdraw.test.ts    From hypervisor with The Unlicense 4 votes vote down vote up
describe('Hypervisor', () => {
    const [wallet, alice, bob, carol, other,
           user0, user1, user2, user3, user4] = waffle.provider.getWallets()

    let factory: IUniswapV3Factory
    let router: ISwapRouter
    let token0: TestERC20
    let token1: TestERC20
    let token2: TestERC20
    let uniswapPool: IUniswapV3Pool
    let hypervisorFactory: HypervisorFactory
    let hypervisor: Hypervisor

    let loadFixture: ReturnType<typeof createFixtureLoader>
    before('create fixture loader', async () => {
        loadFixture = createFixtureLoader([wallet, other])
    })

    beforeEach('deploy contracts', async () => {
        ({ token0, token1, token2, factory, router, hypervisorFactory } = await loadFixture(hypervisorTestFixture))
        await hypervisorFactory.createHypervisor(token0.address, token1.address, FeeAmount.MEDIUM,"Test Visor", "TVR");
        const hypervisorAddress = await hypervisorFactory.getHypervisor(token0.address, token1.address, FeeAmount.MEDIUM)
        hypervisor = (await ethers.getContractAt('Hypervisor', hypervisorAddress)) as Hypervisor

        const poolAddress = await factory.getPool(token0.address, token1.address, FeeAmount.MEDIUM)
        uniswapPool = (await ethers.getContractAt('IUniswapV3Pool', poolAddress)) as IUniswapV3Pool
        await uniswapPool.initialize(encodePriceSqrt('1', '1'))
        await hypervisor.setDepositMax(ethers.utils.parseEther('100000'), ethers.utils.parseEther('100000'))

        // adding extra liquidity into pool to make sure there's always
        // someone to swap with
        await token0.mint(carol.address, ethers.utils.parseEther('1000000000000'))
        await token1.mint(carol.address, ethers.utils.parseEther('1000000000000'))
    })

  /*
   * Tests arent accommodating deposit functions with differing signatures. 
   * UniProxy straddles deposit(uint,uint,address) and deposit(uint,uint,address,address)
   */
    it('deposit with an incorrect proportion will revert', async () => {
        let uniProxyFactory = await ethers.getContractFactory('UniProxy')
        let uniProxy = (await uniProxyFactory.deploy())
        let owner = await uniProxy.owner();
        expect(owner).to.equal(wallet.address);
        await uniProxy.connect(wallet).addPosition(hypervisor.address, 3);

        // SETTING FREE DEPOSIT
        //await uniProxy.connect(wallet).toggleDepositFree();
        let depState = await uniProxy.freeDeposit();
        expect(depState).to.equal(false);

        await token0.mint(alice.address, ethers.utils.parseEther('1000000'))
        await token1.mint(alice.address, ethers.utils.parseEther('1000000'))

        await token0.connect(alice).approve(hypervisor.address, ethers.utils.parseEther('1000000'))
        await token1.connect(alice).approve(hypervisor.address, ethers.utils.parseEther('1000000'))
        await token0.connect(alice).approve(uniProxy.address, ethers.utils.parseEther('1000000'))
        await token1.connect(alice).approve(uniProxy.address, ethers.utils.parseEther('1000000'))

        // alice should start with 0 hypervisor tokens
        let alice_liq_balance = await hypervisor.balanceOf(alice.address)
        expect(alice_liq_balance).to.equal(0)
        // establishing 1:1 ratio in hypervisor
        await uniProxy.connect(alice).deposit(ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'), alice.address, hypervisor.address)
        await hypervisor.rebalance(-1800, 1800, 0, 600, bob.address, 0)
        // attempting 2 unbalanced deposits & expecting failure
        await expect(uniProxy.connect(alice).deposit(ethers.utils.parseEther('20000'), 0, alice.address, hypervisor.address)).to.be.revertedWith("Improper ratio")
        await expect(uniProxy.connect(alice).deposit(0, ethers.utils.parseEther('20000'), alice.address, hypervisor.address)).to.be.revertedWith("Improper ratio")
        // attempting balanced deposit & expecting success
        await uniProxy.connect(alice).deposit(ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'), alice.address, hypervisor.address)
        // nearly balanced deposits are excepted
        await uniProxy.connect(alice).deposit(ethers.utils.parseEther('1000'), ethers.utils.parseEther('998'), alice.address, hypervisor.address)
    });

    it('calculates fees properly & rebalances to limit-only after large swap, realize pending fees after compound', async () => {
        await token0.mint(alice.address, ethers.utils.parseEther('1000000'))
        await token1.mint(alice.address, ethers.utils.parseEther('1000000'))

        await token0.connect(alice).approve(hypervisor.address, ethers.utils.parseEther('1000000'))
        await token1.connect(alice).approve(hypervisor.address, ethers.utils.parseEther('1000000'))

        // alice should start with 0 hypervisor tokens
        let alice_liq_balance = await hypervisor.balanceOf(alice.address)
        expect(alice_liq_balance).to.equal(0)

        await hypervisor.connect(alice).deposit(ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'), alice.address, alice.address)

        let token0hypervisor = await token0.balanceOf(hypervisor.address)
        let token1hypervisor = await token1.balanceOf(hypervisor.address)
        expect(token0hypervisor).to.equal(ethers.utils.parseEther('1000'))
        expect(token1hypervisor).to.equal(ethers.utils.parseEther('1000'))
        alice_liq_balance = await hypervisor.balanceOf(alice.address)
        console.log("alice liq balance: " + ethers.utils.formatEther(alice_liq_balance))
        // check that alice has been awarded liquidity tokens equal the
        // quantity of tokens deposited since their price is the same
        expect(alice_liq_balance).to.equal(ethers.utils.parseEther('2000'))

        // liquidity positions will only be created once rebalance is called
        await hypervisor.rebalance(-120, 120, -60, 0, bob.address, 0)
        token0hypervisor = await token0.balanceOf(hypervisor.address)
        token1hypervisor = await token1.balanceOf(hypervisor.address)
        expect(token0hypervisor).to.equal(0)
        expect(token1hypervisor).to.equal(0)

        let basePosition = await hypervisor.getBasePosition()
        let limitPosition = await hypervisor.getLimitPosition()
        expect(basePosition[0]).to.be.gt(0)
        expect(limitPosition[0]).to.be.equal(0)
        console.log("basePosition[0]: " + ethers.utils.formatEther(basePosition[0]));
        console.log("basePosition[1]: " + ethers.utils.formatEther(basePosition[1]));
        console.log("basePosition[2]: " + ethers.utils.formatEther(basePosition[2]));
        console.log("limitPosition[0]: " + ethers.utils.formatEther(limitPosition[0]));
        console.log("limitPosition[2]: " + ethers.utils.formatEther(limitPosition[1]));
        console.log("limitPosition[2]: " + ethers.utils.formatEther(limitPosition[2]));

        let tokenAmounts = await hypervisor.getTotalAmounts()
        expect(tokenAmounts[0] === tokenAmounts[1])
        console.log("totalAmounts[0]: " + ethers.utils.formatEther(tokenAmounts[0]))
        console.log("totalAmounts[1]: " + ethers.utils.formatEther(tokenAmounts[1]))

        // do a test swap
        await token0.connect(carol).approve(router.address, ethers.utils.parseEther('10000000000'))
        await token1.connect(carol).approve(router.address, ethers.utils.parseEther('10000000000'))
        await router.connect(carol).exactInputSingle({
            tokenIn: token0.address,
            tokenOut: token1.address,
            fee: FeeAmount.MEDIUM,
            recipient: carol.address,
            deadline: 2000000000, // Wed May 18 2033 03:33:20 GMT+0000
            amountIn: ethers.utils.parseEther('100000000'),
            amountOutMinimum: ethers.utils.parseEther('0'),
            sqrtPriceLimitX96: 0,
        })

        let limitUpper = 0
        let limitLower = -180
        tokenAmounts = await hypervisor.getTotalAmounts()
        console.log("tokenAmounts0: " + ethers.utils.formatEther(tokenAmounts[0]));
        console.log("tokenAmounts1: " + ethers.utils.formatEther(tokenAmounts[1]));
        expect(tokenAmounts[0] > tokenAmounts[1])
        let currentTick = await hypervisor.currentTick()
        // this is beyond the bounds of the original base position
        expect(currentTick).to.equal(-887272)

        let fees0 = await token0.balanceOf(bob.address)
        let fees1 = await token1.balanceOf(bob.address)
        expect(fees0).to.equal(0)
        expect(fees1).to.equal(0)
        await hypervisor.rebalance(-1800, 1800, limitLower, limitUpper, bob.address, 0)
        token0hypervisor = await token0.balanceOf(hypervisor.address)
        token1hypervisor = await token1.balanceOf(hypervisor.address)
        expect(token0hypervisor).to.equal(0)
        expect(token1hypervisor).to.equal(0)
        fees0 = await token0.balanceOf(bob.address)
        fees1 = await token1.balanceOf(bob.address)
        // we are expecting VISR fees of 3 bips
        expect(fees0).to.gt(ethers.utils.parseEther('0.3'))
        expect(fees0).to.lt(ethers.utils.parseEther('0.305'))
        console.log("fees: " + ethers.utils.formatEther(fees0))
        expect(fees1).to.equal(0)
        // have the positions been updated? Are the token amounts unchanged?
        basePosition = await hypervisor.getBasePosition()
        limitPosition = await hypervisor.getLimitPosition()
        // the limit position should have 0 liquidity because we are left with
        // only a single asset after carol's big swap
        console.log("limit liq:" + ethers.utils.formatEther(limitPosition[0]))
        console.log("base liq:" + ethers.utils.formatEther(basePosition[0]))
        expect(basePosition[0]).to.be.gt(0)
        expect(limitPosition[0]).to.equal(0)

        // swap everything back and check fees in the other token have
        // been earned
        await router.connect(carol).exactInputSingle({
            tokenIn: token1.address,
            tokenOut: token0.address,
            fee: FeeAmount.MEDIUM,
            recipient: carol.address,
            deadline: 2000000000, // Wed May 18 2033 03:33:20 GMT+0000
            amountIn: ethers.utils.parseEther('200000000'),
            amountOutMinimum: ethers.utils.parseEther('0'),
            sqrtPriceLimitX96: 0,
        })
        currentTick = await hypervisor.currentTick()
				let totalAmounts0 = await hypervisor.getTotalAmounts();
				await hypervisor.compound();
				let totalAmounts1 = await hypervisor.getTotalAmounts();
				// pending fees from swap should be realized after compounding
				expect(totalAmounts0.total1).to.lt(totalAmounts1.total1);	 	


        // this is beyond the bounds of the original base position
        expect(currentTick).to.equal(887271)
        limitUpper = 180
        limitLower = 0
        await hypervisor.rebalance(-1800, 1800, limitLower, limitUpper, bob.address, 0)
        token0hypervisor = await token0.balanceOf(hypervisor.address)
        token1hypervisor = await token1.balanceOf(hypervisor.address)
        expect(token0hypervisor).to.equal(0)
        expect(token1hypervisor).to.equal(0)
        fees1 = await token1.balanceOf(bob.address)
        // we are expecting fees of approximately 3 bips (10% of 30bips, which is total fees)
        expect(fees1).to.gt(ethers.utils.parseEther('0.595'))
        expect(fees1).to.lt(ethers.utils.parseEther('0.605'))
        console.log("fees: " + ethers.utils.formatEther(fees0))
        // have the positions been updated? Are the token amounts unchanged?
        basePosition = await hypervisor.getBasePosition()
        limitPosition = await hypervisor.getLimitPosition()
        // the limit position should have 0 liquidity because we are left with
        // only a single asset after carol's big swap
        console.log("limit liq:" + ethers.utils.formatEther(limitPosition[0]))
        console.log("base liq:" + ethers.utils.formatEther(basePosition[0]))
        expect(basePosition[0]).to.be.gt(0)
        expect(limitPosition[0]).to.equal(0)
    })

    it('deposit/withdrawal with many users', async () => {
        let tokenAmount = ethers.utils.parseEther('10000')

        // token mint for liquidity add
        await token0.mint(user0.address, tokenAmount)
        await token1.mint(user0.address, tokenAmount)

        await token0.mint(user1.address, tokenAmount)
        await token1.mint(user1.address, tokenAmount)

        await token0.mint(user2.address, tokenAmount)
        await token1.mint(user2.address, tokenAmount)

        await token0.mint(user3.address, tokenAmount)
        await token1.mint(user3.address, tokenAmount)

        await token0.mint(user4.address, tokenAmount)
        await token1.mint(user4.address, tokenAmount)

        await token0.mint(other.address, ethers.utils.parseEther('100000'))
        await token1.mint(other.address, ethers.utils.parseEther('100000'))

        // deposit to hypervisor contract

        await token0.connect(user0).approve(hypervisor.address, tokenAmount)
        await token1.connect(user0).approve(hypervisor.address, tokenAmount)

        await token0.connect(user1).approve(hypervisor.address, tokenAmount)
        await token1.connect(user1).approve(hypervisor.address, tokenAmount)

        await token0.connect(user2).approve(hypervisor.address, tokenAmount)
        await token1.connect(user2).approve(hypervisor.address, tokenAmount)

        await token0.connect(user3).approve(hypervisor.address, tokenAmount)
        await token1.connect(user3).approve(hypervisor.address, tokenAmount)

        await token0.connect(user4).approve(hypervisor.address, tokenAmount)
        await token1.connect(user4).approve(hypervisor.address, tokenAmount)

        await hypervisor.connect(user0).deposit(tokenAmount, tokenAmount, user0.address, user0.address)
        await hypervisor.rebalance(-1800, 1800, 0, 600, bob.address, 0)
        await hypervisor.connect(user1).deposit(tokenAmount, tokenAmount, user1.address, user1.address)
        await hypervisor.connect(user2).deposit(tokenAmount, tokenAmount, user2.address, user2.address)
        await hypervisor.connect(user3).deposit(tokenAmount, tokenAmount, user3.address, user3.address)
        await hypervisor.connect(user4).deposit(tokenAmount, tokenAmount, user4.address, user4.address)

        let user0token0Amount = await token0.balanceOf(user0.address)
        let user0token1Amount = await token1.balanceOf(user0.address)

        let user1token0Amount = await token0.balanceOf(user1.address)
        let user1token1Amount = await token1.balanceOf(user1.address)

        let user2token0Amount = await token0.balanceOf(user2.address)
        let user2token1Amount = await token1.balanceOf(user2.address)

        let user3token0Amount = await token0.balanceOf(user3.address)
        let user3token1Amount = await token1.balanceOf(user3.address)

        let user4token0Amount = await token0.balanceOf(user4.address)
        let user4token1Amount = await token1.balanceOf(user4.address)

        expect(user0token0Amount).to.equal(0)
        expect(user1token0Amount).to.equal(0)
        expect(user2token0Amount).to.equal(0)
        expect(user3token0Amount).to.equal(0)
        expect(user4token0Amount).to.equal(0)
        expect(user0token1Amount).to.equal(0)
        expect(user1token1Amount).to.equal(0)
        expect(user2token1Amount).to.equal(0)
        expect(user3token1Amount).to.equal(0)
        expect(user4token1Amount).to.equal(0)

        // withdraw
        const user0_liq_balance = await hypervisor.balanceOf(user0.address)
        const user1_liq_balance = await hypervisor.balanceOf(user1.address)
        const user2_liq_balance = await hypervisor.balanceOf(user2.address)
        const user3_liq_balance = await hypervisor.balanceOf(user3.address)
        const user4_liq_balance = await hypervisor.balanceOf(user4.address)

        await hypervisor.connect(user0).withdraw(user0_liq_balance, user0.address, user0.address)
        await hypervisor.connect(user1).withdraw(user1_liq_balance, user1.address, user1.address)
        await hypervisor.connect(user2).withdraw(user2_liq_balance, user2.address, user2.address)
        await hypervisor.connect(user3).withdraw(user3_liq_balance, user3.address, user3.address)
        await hypervisor.connect(user4).withdraw(user4_liq_balance, user4.address, user4.address)

        user0token0Amount = await token0.balanceOf(user0.address)
        user0token1Amount = await token1.balanceOf(user0.address)

        user1token0Amount = await token0.balanceOf(user1.address)
        user1token1Amount = await token1.balanceOf(user1.address)

        user2token0Amount = await token0.balanceOf(user2.address)
        user2token1Amount = await token1.balanceOf(user2.address)

        user3token0Amount = await token0.balanceOf(user3.address)
        user3token1Amount = await token1.balanceOf(user3.address)

        user4token0Amount = await token0.balanceOf(user4.address)
        user4token1Amount = await token1.balanceOf(user4.address)

        expect(user0token0Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user1token0Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user2token0Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user3token0Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user0token1Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user1token1Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user2token1Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
        expect(user3token1Amount.sub(tokenAmount).abs()).to.be.lte(ethers.utils.parseEther('1'))
    })

    it('can withdraw deposited funds without rebalance', async () => {
        await token0.mint(alice.address, ethers.utils.parseEther('1000000'))
        await token1.mint(alice.address, ethers.utils.parseEther('1000000'))

        await token0.connect(alice).approve(hypervisor.address, ethers.utils.parseEther('1000000'))
        await token1.connect(alice).approve(hypervisor.address, ethers.utils.parseEther('1000000'))

        // alice should start with 0 hypervisor tokens
        let alice_liq_balance = await hypervisor.balanceOf(alice.address)
        expect(alice_liq_balance).to.equal(0)

        await hypervisor.connect(alice).deposit(ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'), alice.address, alice.address)
        alice_liq_balance = await hypervisor.balanceOf(alice.address)
        expect(alice_liq_balance).to.equal(ethers.utils.parseEther('2000'))
        await hypervisor.connect(alice).withdraw(alice_liq_balance, alice.address, alice.address)
        let tokenAmounts = await hypervisor.getTotalAmounts()
        // verify that all liquidity has been removed from the pool
        expect(tokenAmounts[0]).to.equal(0)
        expect(tokenAmounts[1]).to.equal(0)

        await hypervisor.connect(alice).deposit(ethers.utils.parseEther('1000'), ethers.utils.parseEther('1000'), alice.address, alice.address)

        await hypervisor.rebalance(-120, 120, 0, 60, bob.address, 0)

        let tokenAmount = ethers.utils.parseEther('1000')

        await token0.mint(user0.address, tokenAmount)
        await token1.mint(user0.address, tokenAmount)
        await token0.connect(user0).approve(hypervisor.address, tokenAmount)
        await token1.connect(user0).approve(hypervisor.address, tokenAmount)
        await hypervisor.connect(user0).deposit(tokenAmount, tokenAmount, user0.address, user0.address)
        let token0Balance = await token0.balanceOf(user0.address)
        let token1Balance = await token1.balanceOf(user0.address)
        expect(token0Balance).to.equal(0)
        expect(token1Balance).to.equal(0)

        const user0_liq_balance = await hypervisor.balanceOf(user0.address)
        tokenAmounts = await hypervisor.getTotalAmounts()
        // verify that all liquidity has been removed from the pool
        expect(tokenAmounts[0]).to.be.gte(ethers.utils.parseEther('1999'))
        expect(tokenAmounts[1]).to.be.gte(ethers.utils.parseEther('1999'))
        expect(tokenAmounts[0]).to.be.lt(ethers.utils.parseEther('2001'))
        expect(tokenAmounts[1]).to.be.lt(ethers.utils.parseEther('2001'))

        await hypervisor.connect(user0).withdraw(user0_liq_balance, user0.address, user0.address)
        token0Balance = await token0.balanceOf(user0.address)
        token1Balance = await token1.balanceOf(user0.address)
        expect(token0Balance).to.equal(ethers.utils.parseEther('1000'))
        expect(token1Balance).to.equal(ethers.utils.parseEther('1000'))
    })

})
Example #10
Source File: ppn.ts    From perp-vault-templates with MIT License 4 votes vote down vote up
describe("PPN Vault", function () {
  const counterpartyWallet = ethers.Wallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/30");

  const provider = waffle.provider;

  const ethPrice = 2000 * 1e8;
  const putStrikePrice = 1800 * 1e8;

  // core components
  let proxy: CTokenProxy;
  let vault: OpynPerpVault;
  let action1: CTokenTreasury;
  let action2: LongOTokenWithCToken;

  // asset used by this action: in this case, weth
  let cusdc: MockCErc20;
  let weth: MockWETH;
  let usdc: MockERC20;

  let otoken1: MockOToken;

  let accounts: SignerWithAddress[] = [];

  let owner: SignerWithAddress;
  let depositor1: SignerWithAddress;
  let depositor2: SignerWithAddress;
  let optionSeller: SignerWithAddress;
  let feeRecipient: SignerWithAddress;

  // mock external contracts
  let swap: MockSwap;
  let controller: MockController;
  let whitelist: MockWhitelist;
  let oracle: MockOpynOracle;
  let pool: MockPool;

  this.beforeAll("Set accounts", async () => {
    accounts = await ethers.getSigners();
    const [_owner, _feeRecipient, _depositor1, _depositor2, _seller] = accounts;
    owner = _owner;
    feeRecipient = _feeRecipient;
    depositor1 = _depositor1;
    depositor2 = _depositor2;
    optionSeller = _seller;
  });

  this.beforeAll("Deploy Mock Token contracts", async () => {
    const MockWETHContract = await ethers.getContractFactory("MockWETH");
    weth = (await MockWETHContract.deploy()) as MockWETH;
    await weth.init("WETH", "WETH", 18);

    const ERC20 = await ethers.getContractFactory("MockERC20");
    usdc = (await ERC20.deploy()) as MockERC20;
    await usdc.init("USDC", "USDC", 6);

    // setup cusdc
    const MockCERC20Contract = await ethers.getContractFactory("MockCErc20");
    cusdc = (await MockCERC20Contract.deploy(usdc.address, "compound USDC", "cUSDC", 8)) as MockCErc20;

    await cusdc.setExchangeRate("240000000000000");
    await usdc.mint(cusdc.address, "1000000000000");
  });

  this.beforeAll("Deploy Mock external contracts", async () => {
    const Swap = await ethers.getContractFactory("MockSwap");
    swap = (await Swap.deploy()) as MockSwap;

    const Controller = await ethers.getContractFactory("MockController");
    controller = (await Controller.deploy()) as MockController;

    const Whitelist = await ethers.getContractFactory("MockWhitelist");
    whitelist = (await Whitelist.deploy()) as MockWhitelist;

    const MockPool = await ethers.getContractFactory("MockPool");
    pool = (await MockPool.deploy()) as MockPool;

    const MockOracle = await ethers.getContractFactory("MockOpynOracle");
    oracle = (await MockOracle.deploy()) as MockOpynOracle;

    await controller.setPool(pool.address);
    await controller.setWhitelist(whitelist.address);
    await controller.setOracle(oracle.address);

    await oracle.setAssetPrice(weth.address, ethPrice);

    await usdc.mint(pool.address, "1000000000000");
  });

  this.beforeAll("Mint USDC for participants", async () => {
    await usdc.mint(depositor1.address, 1000000 * 1e6);
    await usdc.mint(depositor2.address, 1000000 * 1e6);
  });

  this.beforeAll("Deploy vault and actions", async () => {
    const VaultContract = await ethers.getContractFactory("OpynPerpVault");
    vault = (await VaultContract.deploy()) as OpynPerpVault;

    const ProxyContract = await ethers.getContractFactory("CTokenProxy");
    proxy = (await ProxyContract.deploy(vault.address, usdc.address, cusdc.address)) as CTokenProxy;

    // deploy 2 mock actions
    const CTokenTreasuryContract = await ethers.getContractFactory("CTokenTreasury");
    action1 = (await CTokenTreasuryContract.deploy(vault.address, cusdc.address)) as CTokenTreasury;

    const LongOToken = await ethers.getContractFactory("LongOTokenWithCToken");
    action2 = (await LongOToken.deploy(
      vault.address,
      cusdc.address,
      usdc.address,
      action1.address, // treasury address
      swap.address,
      controller.address,
      true // put
    )) as LongOTokenWithCToken;
  });

  describe("init", async () => {
    it("should init the contract successfully", async () => {
      await vault
        .connect(owner)
        .init(cusdc.address, owner.address, feeRecipient.address, cusdc.address, 18, "PPN share", "sPPN", [
          action1.address,
          action2.address,
        ]);
      // init state
      expect((await vault.state()) === VaultState.Unlocked).to.be.true;
      expect((await vault.totalAsset()).isZero(), "total asset should be zero").to.be.true;
    });
  });

  describe("Round 0, vault unlocked", async () => {
    const depositAmount = "10000000000"; // 10000 USDC

    it("unlocked state checks", async () => {
      expect(await vault.state()).to.eq(VaultState.Unlocked);
      expect(await vault.round()).to.eq(0);
    });
    it("should be able to deposit cUSDC", async () => {
      await usdc.connect(depositor1).approve(cusdc.address, ethers.constants.MaxUint256);
      await cusdc.connect(depositor1).mint(depositAmount);
      const cusdcBalance = await cusdc.balanceOf(depositor1.address);
      const shares1Before = await vault.balanceOf(depositor1.address);
      const expectedShares = await vault.getSharesByDepositAmount(cusdcBalance);

      // depositor 1 deposits 10000 cUSDC directly
      await cusdc.connect(depositor1).approve(vault.address, ethers.constants.MaxUint256);
      await vault.connect(depositor1).deposit(cusdcBalance);

      const shares1After = await vault.balanceOf(depositor1.address);
      expect(shares1After.sub(shares1Before).eq(expectedShares)).to.be.true;
    });

    it("should be able to deposit USDC through Proxy", async () => {
      await usdc.connect(depositor2).approve(proxy.address, ethers.constants.MaxUint256);
      // depositor 2 deposits 10000 USDC through proxy
      await proxy.connect(depositor2).depositUnderlying(depositAmount);
      const d2Shares = await vault.balanceOf(depositor2.address);
      const d1Shares = await vault.balanceOf(depositor1.address);
      expect(d2Shares.lt(d1Shares)).to.be.true;
    });

    it("should rollover to the first round without committing otoken", async () => {
      const vaultBalanceBefore = await cusdc.balanceOf(vault.address);
      const action1BalanceBefore = await cusdc.balanceOf(action1.address);
      const action2BalanceBefore = await cusdc.balanceOf(action2.address);
      const totalValueBefore = await vault.totalAsset();

      // Distribution:
      // 100% - action1
      // 0% - action2
      await vault.connect(owner).rollOver([10000, 0]);

      const vaultBalanceAfter = await cusdc.balanceOf(vault.address);
      const action1BalanceAfter = await cusdc.balanceOf(action1.address);
      const action2BalanceAfter = await cusdc.balanceOf(action2.address);
      const totalValueAfter = await vault.totalAsset();

      expect(action1BalanceAfter.sub(action1BalanceBefore).eq(vaultBalanceBefore)).to.be.true;
      expect(action2BalanceAfter.sub(action2BalanceBefore).isZero()).to.be.true;

      expect(vaultBalanceAfter.isZero()).to.be.true;
      expect(totalValueAfter.eq(totalValueBefore), "total value should stay unaffected").to.be.true;
    });
  });
  describe("Round 0, vault Locked", async () => {
    it("increase exchange rate over time", async () => {
      const oldExchangeRate = (await cusdc.exchangeRateStored()).toNumber();
      // cusdc value increase by 1%
      await cusdc.setExchangeRate(Math.floor(oldExchangeRate * 1.01));
    });
    it("should be able to close position, once there's interest to collect ", async () => {
      const vaultBalanceBefore = await cusdc.balanceOf(vault.address);
      const action1BalanceBefore = await cusdc.balanceOf(action1.address);

      await vault.connect(owner).closePositions();

      const vaultBalanceAfter = await cusdc.balanceOf(vault.address);
      const action1BalanceAfter = await cusdc.balanceOf(action1.address);
      expect(vaultBalanceAfter.sub(vaultBalanceBefore).eq(action1BalanceBefore.sub(action1BalanceAfter))).to.be.true;

      const profit = await action1.lastRoundProfit();
      expect(profit.gt(0)).to.be.true;
    });
  });

  describe("Round 1: vault Unlocked", async () => {
    it("should be able to commit to an otoken to buy with the interest", async () => {
      const blockNumber = await provider.getBlockNumber();
      const block = await provider.getBlock(blockNumber);
      const currentTimestamp = block.timestamp;
      const expiry = currentTimestamp + 86400 * 7;

      const MockOToken = await ethers.getContractFactory("MockOToken");
      otoken1 = (await MockOToken.deploy()) as MockOToken;
      await otoken1.init("oWETHUSDP", "oWETHUSDP", 18);
      await otoken1.initMockOTokenDetail(weth.address, usdc.address, usdc.address, putStrikePrice, expiry, true);

      await action2.connect(owner).commitOToken(otoken1.address);

      // pass commit period
      const minPeriod = await action2.MIN_COMMIT_PERIOD();
      await provider.send("evm_increaseTime", [minPeriod.toNumber()]); // increase time
      await provider.send("evm_mine", []);
    });
    it("should revert when trying to rollover with incorrect percentage", async () => {
      await expect(vault.connect(owner).rollOver([9850, 150])).to.be.revertedWith("too many cTokens");
    });
    it("should be able to rollover again", async () => {
      const action1BalanceBefore = await cusdc.balanceOf(action1.address);
      const action2BalanceBefore = await cusdc.balanceOf(action2.address);
      const totalValueBefore = await vault.totalAsset();
      // Distribution:
      // 99% - action1
      // 1% - action2
      await vault.connect(owner).rollOver([9900, 100]);

      const action1BalanceAfter = await cusdc.balanceOf(action1.address);
      const action2BalanceAfter = await cusdc.balanceOf(action2.address);

      expect(action1BalanceAfter.sub(action1BalanceBefore).eq(totalValueBefore.mul(99).div(100))).to.be.true;
      expect(action2BalanceAfter.sub(action2BalanceBefore).eq(totalValueBefore.mul(1).div(100))).to.be.true;
    });
  });

  describe("Round 1: vault Locked", async () => {
    it("should be able to buy otoken", async () => {
      const premium = 12 * 1e6; // 12 USD
      const buyAmount = 0.2 * 1e8;
      const order = await getAirSwapOrder(
        action2.address,
        usdc.address,
        premium,
        counterpartyWallet.address,
        otoken1.address,
        buyAmount,
        swap.address,
        counterpartyWallet.privateKey
      );

      const cTokenBefore = await cusdc.balanceOf(action2.address);
      await action2.connect(owner).tradeAirswapOTC(order);

      const cTokenAfter = await cusdc.balanceOf(action2.address);
      expect(cTokenBefore.gt(cTokenAfter)).to.be.true;
    });

    it("increase exchange rate over time", async () => {
      const oldExchangeRate = (await cusdc.exchangeRateStored()).toNumber();
      // cusdc value increase by 1%
      await cusdc.setExchangeRate(Math.floor(oldExchangeRate * 1.01));
    });

    it("should close a round with profit in cusdc", async () => {
      const payout = 100 * 1e6;

      const expiry = (await otoken1.expiryTimestamp()).toNumber();
      await provider.send("evm_setNextBlockTimestamp", [expiry + 60]);
      await provider.send("evm_mine", []);

      const totalAssetBefore = await vault.totalAsset();

      await controller.setRedeemPayout(usdc.address, payout);
      await vault.connect(owner).closePositions();

      const totalAssetAfter = await vault.totalAsset();
      expect(totalAssetAfter.gt(totalAssetBefore)).to.be.true;
    });
  });
});
Example #11
Source File: SellAction.ts    From perp-vault-templates with MIT License 4 votes vote down vote up
describe("ShortAction", function () {
  const provider = waffle.provider;

  const counterpartyWallet = ethers.Wallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/30");

  let action: ShortOToken;
  // asset used by this action: in this case, weth
  let token: MockERC20;
  //
  let usdc: MockERC20;

  // mock external contracts
  let swap: MockSwap;
  let auction: MockEasyAuction;

  let whitelist: MockWhitelist;
  let controller: MockController;
  let oracle: MockOpynOracle;

  let accounts: SignerWithAddress[] = [];

  let owner: SignerWithAddress;
  let vault: SignerWithAddress;

  let otokenBad: MockOToken;
  let otoken1: MockOToken;
  let otoken2: MockOToken;

  const otokenBadStrikePrice = 10 * 1e8;
  const otoken1StrikePrice = 4000 * 1e8; // 4000
  const otoken2StrikePrice = 5000 * 1e8; // 5000

  let otoken1Expiry = BigNumber.from(0);
  let otoken2Expiry = BigNumber.from(0);

  // pretend to be gamma margin pool
  let pool: MockPool;

  this.beforeAll("Set accounts", async () => {
    accounts = await ethers.getSigners();
    const [_owner, _vault] = accounts;

    owner = _owner;
    vault = _vault;
  });

  this.beforeAll("Set timestamps", async () => {
    const blockNumber = await provider.getBlockNumber();
    const block = await provider.getBlock(blockNumber);
    const currentTimestamp = block.timestamp;
    // 7 days from now
    otoken1Expiry = BigNumber.from(parseInt(currentTimestamp.toString()) + 86400 * 7);
    otoken2Expiry = BigNumber.from(parseInt(currentTimestamp.toString()) + 86400 * 14);
  });

  this.beforeAll("Deploy Mock contracts", async () => {
    const ERC20 = await ethers.getContractFactory("MockERC20");
    token = (await ERC20.deploy()) as MockERC20;
    await token.init("WETH", "WETH", 18);

    usdc = (await ERC20.deploy()) as MockERC20;
    await usdc.init("USDC", "USDC", 6);

    // deploy mock swap and mock whitelist
    const Whitelist = await ethers.getContractFactory("MockWhitelist");
    whitelist = (await Whitelist.deploy()) as MockWhitelist;

    const Swap = await ethers.getContractFactory("MockSwap");
    swap = (await Swap.deploy()) as MockSwap;

    const Auction = await ethers.getContractFactory("MockEasyAuction");
    auction = (await Auction.deploy()) as MockEasyAuction;

    const MockPool = await ethers.getContractFactory("MockPool");
    pool = (await MockPool.deploy()) as MockPool;

    const MockOracle = await ethers.getContractFactory("MockOpynOracle");
    oracle = (await MockOracle.deploy()) as MockOpynOracle;

    const Controller = await ethers.getContractFactory("MockController");
    controller = (await Controller.deploy()) as MockController;

    await controller.setPool(pool.address);
    await controller.setWhitelist(whitelist.address);
    await controller.setOracle(oracle.address);
  });

  describe("deployment test", () => {
    it("deploy", async () => {
      const ShortActionContract = await ethers.getContractFactory("ShortOToken");
      action = (await ShortActionContract.deploy(
        vault.address,
        token.address,
        swap.address,
        auction.address,
        controller.address,
        0 // type 0 vault
      )) as ShortOToken;

      expect((await action.owner()) == owner.address).to.be.true;

      expect((await action.asset()) === token.address).to.be.true;

      expect(await controller.vaultOpened()).to.be.true;

      expect((await token.allowance(action.address, pool.address)).eq(ethers.constants.MaxUint256)).to.be.true;
      expect((await token.allowance(action.address, vault.address)).eq(ethers.constants.MaxUint256)).to.be.true;

      // init state should be idle
      expect((await action.state()) === ActionState.Idle).to.be.true;

      // whitelist is set
      expect((await action.opynWhitelist()) === whitelist.address).to.be.true;
    });
    it("should deploy with type 1 vault", async () => {
      const ShortActionContract = await ethers.getContractFactory("ShortOToken");
      await ShortActionContract.deploy(
        vault.address,
        token.address,
        swap.address,
        ethers.constants.AddressZero,
        controller.address,
        1 // type 0 vault
      );
      expect((await action.owner()) == owner.address).to.be.true;
      expect((await action.asset()) === token.address).to.be.true;
      expect(await controller.vaultOpened()).to.be.true;
    });
  });

  const totalDepositInAction = utils.parseUnits("100");

  describe("idle phase", () => {
    before("Mint some eth to action", async () => {
      // mint 100 weth
      await token.mint(action.address, totalDepositInAction);
    });
    before("Deploy mock otokens", async () => {
      const MockOToken = await ethers.getContractFactory("MockOToken");
      otoken1 = (await MockOToken.deploy()) as MockOToken;
      await otoken1.init("oWETHUSDC", "oWETHUSDC", 18);
      await otoken1.initMockOTokenDetail(
        token.address,
        usdc.address,
        token.address,
        otoken1StrikePrice,
        otoken1Expiry,
        false
      );

      otoken2 = (await MockOToken.deploy()) as MockOToken;
      await otoken2.init("oWETHUSDC", "oWETHUSDC", 18);
      await otoken2.initMockOTokenDetail(
        token.address,
        usdc.address,
        token.address,
        otoken2StrikePrice,
        otoken2Expiry,
        false
      );

      otokenBad = (await MockOToken.deploy()) as MockOToken;
      await otokenBad.init("oWETHUSDC", "oWETHUSDC", 18);
      await otokenBad.initMockOTokenDetail(
        token.address,
        usdc.address,
        token.address,
        otokenBadStrikePrice,
        otoken2Expiry,
        false
      );

      await oracle.setAssetPrice(token.address, 10000000000);
    });
    it("should revert if calling mint + sell in idle phase", async () => {
      const collateral = utils.parseUnits("10");
      const amountOTokenToMint = 10 * 1e8;
      const premium = parseUnits("1");
      const order = await getAirSwapOrder(
        action.address,
        otoken1.address,
        amountOTokenToMint,
        counterpartyWallet.address,
        token.address,
        premium.toString(),
        swap.address,
        counterpartyWallet.privateKey
      );
      await expect(
        action.connect(owner).mintAndTradeAirSwapOTC(collateral, amountOTokenToMint, order)
      ).to.be.revertedWith("!Activated");
    });
    it("should not be able to token with invalid strike price", async () => {
      await expect(action.connect(owner).commitOToken(otokenBad.address)).to.be.revertedWith("Bad Strike Price");
    });
    it("should be able to commit next token", async () => {
      await action.connect(owner).commitOToken(otoken1.address);
      expect((await action.nextOToken()) === otoken1.address);
      expect((await action.state()) === ActionState.Committed).to.be.true;
    });
    it("should revert if the vault is trying to rollover before min commit period is spent", async () => {
      await expect(action.connect(vault).rolloverPosition()).to.be.revertedWith("COMMIT_PHASE_NOT_OVER");
    });
  });

  describe("activating the action", () => {
    const mintOTokenAmount = 10 * 1e8;
    before("increase blocktime to get it over with minimal commit period", async () => {
      const minPeriod = await action.MIN_COMMIT_PERIOD();
      await provider.send("evm_increaseTime", [minPeriod.toNumber()]); // increase time
      await provider.send("evm_mine", []);
    });
    it("should revert if the vault is trying to rollover from non-vault address", async () => {
      await expect(action.connect(owner).rolloverPosition()).to.be.revertedWith("!VAULT");
    });
    it("should be able to roll over the position", async () => {
      await action.connect(vault).rolloverPosition();

      expect((await action.nextOToken()) === ethers.constants.AddressZero);
    });
    it("should get currentValue as total amount in gamma as ", async () => {
      expect((await action.currentValue()).eq(totalDepositInAction)).to.be.true;
    });
    describe("short with AirSwap", async () => {
      it("should not be able to mint and sell if less than min premium", async () => {
        const collateralAmount = utils.parseUnits("10");
        const sellAmount = 10 * 1e8;
        const premium = utils.parseUnits("0");
        const order = await getAirSwapOrder(
          action.address,
          otoken1.address,
          sellAmount,
          counterpartyWallet.address,
          token.address,
          premium.toString(),
          swap.address,
          counterpartyWallet.privateKey
        );
        await expect(
          action.connect(owner).mintAndTradeAirSwapOTC(collateralAmount, mintOTokenAmount, order)
        ).revertedWith("Need minimum option premium");
      });
      it("should be able to mint and sell in this phase", async () => {
        const collateralAmount = utils.parseUnits("10");
        const otokenBalanceBefore = await otoken1.balanceOf(action.address);
        const sellAmount = 10 * 1e8;
        const premium = utils.parseUnits("1");
        const order = await getAirSwapOrder(
          action.address,
          otoken1.address,
          sellAmount,
          counterpartyWallet.address,
          token.address,
          premium.toString(),
          swap.address,
          counterpartyWallet.privateKey
        );
        await action.connect(owner).mintAndTradeAirSwapOTC(collateralAmount, mintOTokenAmount, order);
        const otokenBalanceAfter = await otoken1.balanceOf(action.address);
        expect(otokenBalanceAfter.sub(otokenBalanceBefore).eq("0")).to.be.true;
      });
      it("should revert when trying to fill wrong order", async () => {
        const collateralAmount = utils.parseUnits("10");
        const badOrder1 = await getAirSwapOrder(
          action.address,
          ethers.constants.AddressZero,
          mintOTokenAmount,
          counterpartyWallet.address,
          token.address,
          "1",
          swap.address,
          counterpartyWallet.privateKey
        );
        await expect(
          action.connect(owner).mintAndTradeAirSwapOTC(collateralAmount, mintOTokenAmount, badOrder1)
        ).to.be.revertedWith("Can only sell otoken");

        const badOrder2 = await getAirSwapOrder(
          action.address,
          otoken1.address,
          mintOTokenAmount,
          counterpartyWallet.address,
          ethers.constants.AddressZero,
          "1",
          swap.address,
          counterpartyWallet.privateKey
        );
        await expect(
          action.connect(owner).mintAndTradeAirSwapOTC(collateralAmount, mintOTokenAmount, badOrder2)
        ).to.be.revertedWith("Can only sell for asset");
      });
    });
    describe("short by starting an EasyAuction", async () => {
      let auctionDeadline: number;

      it("should be able to mint and start an auction phase", async () => {
        const collateralAmount = utils.parseUnits("10");
        const auctionOtokenBalanceBefore = await otoken1.balanceOf(auction.address);
        const mintAmount = 10 * 1e8;
        const sellAmount = 5 * 1e8;
        const minPremium = utils.parseUnits("1"); // 1 eth min premium

        const blockNumber = await provider.getBlockNumber();
        const block = await provider.getBlock(blockNumber);
        const currentTimestamp = block.timestamp;
        auctionDeadline = currentTimestamp + 86400 * 1;

        const minimalBidAmountPerOrder = 0.1 * 1e8; // min bid each order: 0.1 otoken
        const minFundingThreshold = 0;

        await action.connect(owner).mintAndStartAuction(
          collateralAmount,
          mintAmount,
          sellAmount,
          auctionDeadline, // order cancel deadline
          auctionDeadline,
          minPremium,
          minimalBidAmountPerOrder,
          minFundingThreshold,
          false
        );
        const auctionOtokenBalanceAfter = await otoken1.balanceOf(auction.address);
        expect(auctionOtokenBalanceAfter.sub(auctionOtokenBalanceBefore).eq(sellAmount)).to.be.true;
      });

      it("should start another auction with otoken left in the contract", async () => {
        const collateralAmount = 0;
        const auctionOtokenBalanceBefore = await otoken1.balanceOf(auction.address);
        const mintAmount = 0;
        const sellAmount = 5 * 1e8;
        const minPremium = utils.parseUnits("1"); // 1 eth min premium

        const blockNumber = await provider.getBlockNumber();
        const block = await provider.getBlock(blockNumber);
        const currentTimestamp = block.timestamp;
        auctionDeadline = currentTimestamp + 86400 * 1;

        const minimalBidAmountPerOrder = 0.1 * 1e8; // min bid each order: 0.1 otoken
        const minFundingThreshold = 0;

        await action
          .connect(owner)
          .mintAndStartAuction(
            collateralAmount,
            mintAmount,
            sellAmount,
            auctionDeadline,
            auctionDeadline,
            minPremium,
            minimalBidAmountPerOrder,
            minFundingThreshold,
            false
          );
        const auctionOtokenBalanceAfter = await otoken1.balanceOf(auction.address);
        expect(auctionOtokenBalanceAfter.sub(auctionOtokenBalanceBefore).eq(sellAmount)).to.be.true;
      });

      it('can short by participate in a "buy otoken auction"', async () => {
        const blockNumber = await provider.getBlockNumber();
        const block = await provider.getBlock(blockNumber);
        const currentTimestamp = block.timestamp;
        const auctionDeadline = currentTimestamp + 86400 * 1;
        // buyer create an auction to use 5 eth to buy 60 otokens
        const buyer = accounts[3];
        const sellAmount = utils.parseUnits("5");
        await token.mint(buyer.address, sellAmount);
        await token.connect(buyer).approve(auction.address, sellAmount);
        await auction.connect(buyer).initiateAuction(
          token.address,
          otoken1.address,
          auctionDeadline,
          auctionDeadline,
          sellAmount,
          60 * 1e8, // min buy amount
          1e6, // minimumBiddingAmountPerOrder
          0, // minFundingThreshold
          false, // isAtomicClosureAllowed
          ethers.constants.AddressZero, // accessManagerContract
          "0x00" // accessManagerContractData
        );

        const auctionIdToParticipate = await auction.auctionCounter();

        // the action participate in the action
        const collateralAmount = utils.parseUnits("5");
        const mintAmount = 5 * 1e8;
        const minPremium = utils.parseUnits("0.2");

        const auctionOtokenBalanceBefore = await otoken1.balanceOf(auction.address);
        await action
          .connect(owner)
          .mintAndBidInAuction(
            auctionIdToParticipate,
            collateralAmount,
            mintAmount,
            [minPremium],
            [mintAmount],
            ["0x0000000000000000000000000000000000000000000000000000000000000001"],
            "0x00"
          );
        const auctionOtokenBalanceAfter = await otoken1.balanceOf(auction.address);
        expect(auctionOtokenBalanceAfter.sub(auctionOtokenBalanceBefore).eq(mintAmount)).to.be.true;
      });
    });
    it("should not be able to commit next token", async () => {
      await expect(action.connect(owner).commitOToken(otoken2.address)).to.be.revertedWith("Activated");
    });
    it("should revert if the vault is trying to rollover", async () => {
      await expect(action.connect(vault).rolloverPosition()).to.be.revertedWith("!COMMITED");
    });
  });

  describe("close position", () => {
    before("increase blocktime to otoken expiry", async () => {
      await provider.send("evm_setNextBlockTimestamp", [otoken1Expiry.toNumber()]);
      await provider.send("evm_mine", []);
    });
    it("should revert if the vault is trying to close from non-vault address", async () => {
      await expect(action.connect(owner).closePosition()).to.be.revertedWith("!VAULT");
    });
    it("should be able to close the position", async () => {
      const actionBalanceBefore = await token.balanceOf(action.address);
      const settlePayout = utils.parseUnits("9"); // assume we can get back 9 eth
      await controller.setSettlePayout(settlePayout);

      await action.connect(vault).closePosition();
      const actionBalanceAfter = await token.balanceOf(action.address);
      expect(actionBalanceAfter.sub(actionBalanceBefore).eq(settlePayout)).to.be.true;
      expect((await action.state()) === ActionState.Idle).to.be.true;
    });
    it("should revert if calling mint in idle phase", async () => {
      const collateral = utils.parseUnits("10");
      const amountOTokenToMint = 10 * 1e8;
      const premium = utils.parseUnits("1");
      const order = await getAirSwapOrder(
        action.address,
        otoken1.address,
        amountOTokenToMint,
        counterpartyWallet.address,
        token.address,
        premium.toString(),
        swap.address,
        counterpartyWallet.privateKey
      );
      await expect(
        action.connect(owner).mintAndTradeAirSwapOTC(collateral, amountOTokenToMint, order)
      ).to.be.revertedWith("!Activated");
    });
  });
});
Example #12
Source File: ShortPutWithETH.ts    From perp-vault-templates with MIT License 4 votes vote down vote up
describe("Short Put with ETH Action", function () {
  const provider = waffle.provider;

  const counterpartyWallet = ethers.Wallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/30");

  let action: ShortPutWithETH;
  // asset used by this action: in this case, weth
  let usdc: MockERC20;
  let weth: MockWETH;
  let cusdc: MockCErc20;
  let ceth: MockCEth;
  let comptroller: MockComptroller;
  let swap: MockSwap;

  let whitelist: MockWhitelist;
  let controller: MockController;

  let accounts: SignerWithAddress[] = [];

  let owner: SignerWithAddress;
  let vault: SignerWithAddress;

  let otoken1: MockOToken;
  let otoken1Expiry: BigNumber;
  const otoken1StrikePriceHumanReadable = 2000;
  const otoken1StrikePrice = otoken1StrikePriceHumanReadable * 1e8;

  // pretend to be gamma margin pool
  let pool: MockPool;

  this.beforeAll("Set accounts", async () => {
    accounts = await ethers.getSigners();
    const [_owner, _vault] = accounts;

    owner = _owner;
    vault = _vault;
  });

  this.beforeAll("Set timestamps", async () => {
    const blockNumber = await provider.getBlockNumber();
    const block = await provider.getBlock(blockNumber);
    const currentTimestamp = block.timestamp;
    // 7 days from now
    otoken1Expiry = BigNumber.from(parseInt(currentTimestamp.toString()) + 86400 * 7);
  });

  this.beforeAll("Deploy Mock contracts", async () => {
    const ERC20 = await ethers.getContractFactory("MockERC20");
    const WETH = await ethers.getContractFactory("MockWETH");

    weth = (await WETH.deploy()) as MockWETH;
    await weth.init("WETH", "WETH", 18);

    usdc = (await ERC20.deploy()) as MockERC20;
    await usdc.init("USDC", "USDC", 6);

    const MockCERC20Contract = await ethers.getContractFactory("MockCErc20");
    cusdc = (await MockCERC20Contract.deploy(usdc.address, "compound USDC", "cUSDC", 8)) as MockCErc20;

    await cusdc.setExchangeRate(240000000000000);

    const MockCETHContract = await ethers.getContractFactory("MockCEth");
    ceth = (await MockCETHContract.deploy()) as MockCEth;

    // once we mock ceth exchange rate
    // await ceth.setExchangeRate(249136934438441580419980843)

    const Swap = await ethers.getContractFactory("MockSwap");
    swap = (await Swap.deploy()) as MockSwap;

    // deploy mock swap and mock whitelist
    const Whitelist = await ethers.getContractFactory("MockWhitelist");
    whitelist = (await Whitelist.deploy()) as MockWhitelist;

    const MockPool = await ethers.getContractFactory("MockPool");
    pool = (await MockPool.deploy()) as MockPool;

    const Controller = await ethers.getContractFactory("MockController");
    controller = (await Controller.deploy()) as MockController;

    const Comptroller = await ethers.getContractFactory("MockComptroller");
    comptroller = (await Comptroller.deploy()) as MockComptroller;

    await controller.setPool(pool.address);
    await controller.setWhitelist(whitelist.address);
  });

  describe("deployment ", () => {
    it("deploy short put action example ", async () => {
      const ShortActionContract = await ethers.getContractFactory("ShortPutWithETH");
      action = (await ShortActionContract.deploy(
        vault.address,
        weth.address,
        ceth.address,
        usdc.address,
        cusdc.address,
        swap.address,
        controller.address,
        comptroller.address
      )) as ShortPutWithETH;

      expect((await action.owner()) == owner.address).to.be.true;

      expect((await action.asset()) === weth.address).to.be.true;

      expect(await controller.vaultOpened()).to.be.true;

      expect((await usdc.allowance(action.address, pool.address)).eq(ethers.constants.MaxUint256)).to.be.true;
      expect((await weth.allowance(action.address, vault.address)).eq(ethers.constants.MaxUint256)).to.be.true;

      // init state should be idle
      expect((await action.state()) === ActionState.Idle).to.be.true;
    });
  });

  const totalDepositInAction = utils.parseEther("20");

  describe("idle phase", () => {
    before("Mint some eth to action", async () => {
      // transfer 100 weth to the action contract
      await weth.deposit({ value: totalDepositInAction });
      await weth.transfer(action.address, totalDepositInAction);
    });
    before("mint some usdc to cUSDC contract", async () => {
      await usdc.mint(cusdc.address, 1000000 * 1e6);
    });
    before("Deploy mock otoken", async () => {
      const MockOToken = await ethers.getContractFactory("MockOToken");
      otoken1 = (await MockOToken.deploy()) as MockOToken;
      await otoken1.init("oWETHUSDC", "oWETHUSDC", 18);
      await otoken1.initMockOTokenDetail(
        weth.address,
        usdc.address,
        usdc.address,
        otoken1StrikePrice,
        otoken1Expiry,
        true
      );
    });
    it("should revert if calling mint + sell in idle phase", async () => {
      const wethSupply = utils.parseUnits("10");
      const amountOTokenToMintHumanReadable = 10;
      const usdcAmountToBorrow = otoken1StrikePriceHumanReadable * 1e6 * amountOTokenToMintHumanReadable;
      const amountOTokenToMint = amountOTokenToMintHumanReadable * 1e8;
      const premium = parseUnits("1");
      const order = await getAirSwapOrder(
        action.address,
        otoken1.address,
        amountOTokenToMint,
        counterpartyWallet.address,
        weth.address,
        premium.toString(),
        swap.address,
        counterpartyWallet.privateKey
      );
      await expect(
        action.connect(owner).borrowMintAndTradeOTC(wethSupply, usdcAmountToBorrow, amountOTokenToMint, order)
      ).to.be.revertedWith("!Activated");
    });
    it("should be able to commit next token", async () => {
      await action.connect(owner).commitOToken(otoken1.address);
      expect((await action.nextOToken()) === otoken1.address);
      expect((await action.state()) === ActionState.Committed).to.be.true;
    });
    it("should revert if the vault is trying to rollover before min commit period is spent", async () => {
      await expect(action.connect(vault).rolloverPosition()).to.be.revertedWith("COMMIT_PHASE_NOT_OVER");
    });
  });

  describe("activating the action", () => {
    before("increase blocktime to get it over with minimal commit period", async () => {
      const minPeriod = await action.MIN_COMMIT_PERIOD();
      await provider.send("evm_increaseTime", [minPeriod.toNumber()]); // increase time
      await provider.send("evm_mine", []);
    });
    it("should revert if the vault is trying to rollover from non-vault address", async () => {
      await expect(action.connect(owner).rolloverPosition()).to.be.revertedWith("!VAULT");
    });
    it("should be able to roll over the position", async () => {
      await action.connect(vault).rolloverPosition();
      expect((await action.nextOToken()) === ethers.constants.AddressZero);
    });
    it("should execute the trade with borrowed eth", async () => {
      const wethSupply = utils.parseUnits("10");
      const amountOTokenToMintHumanReadable = 10;
      const usdcAmountToBorrow = otoken1StrikePriceHumanReadable * 1e6 * amountOTokenToMintHumanReadable;
      const amountOTokenToMint = amountOTokenToMintHumanReadable * 1e8;
      const premium = parseUnits("1");
      const order = await getAirSwapOrder(
        action.address,
        otoken1.address,
        amountOTokenToMint,
        counterpartyWallet.address,
        weth.address,
        premium.toString(),
        swap.address,
        counterpartyWallet.privateKey
      );
      await action.connect(owner).borrowMintAndTradeOTC(wethSupply, usdcAmountToBorrow, amountOTokenToMint, order);
      const counterPartyOToken = await otoken1.balanceOf(counterpartyWallet.address);
      expect(counterPartyOToken.eq(amountOTokenToMint)).to.be.true;
    });
    it("should not be able to commit next token", async () => {
      await expect(action.connect(owner).commitOToken(usdc.address)).to.be.revertedWith("Activated");
    });
    it("should revert if the vault is trying to rollover", async () => {
      await expect(action.connect(vault).rolloverPosition()).to.be.revertedWith("!COMMITED");
    });
  });

  describe("close position", () => {
    before("increase blocktime to otoken expiry", async () => {
      await provider.send("evm_setNextBlockTimestamp", [otoken1Expiry.toNumber()]);
      await provider.send("evm_mine", []);
    });
    it("should revert if the vault is trying to close from non-vault address", async () => {
      await expect(action.connect(owner).closePosition()).to.be.revertedWith("!VAULT");
    });
    it("should be able to close the position", async () => {
      await controller.setCollateralAsset(usdc.address);
      const actionBalanceBefore = await weth.balanceOf(action.address);

      // assume we get back full usdc
      const mockPayout = otoken1StrikePriceHumanReadable * 10 * 1e6;

      await controller.setSettlePayout(mockPayout);
      await action.connect(vault).closePosition();

      const wethSupplied = utils.parseUnits("10");
      const actionBalanceAfter = await weth.balanceOf(action.address);

      const amountGotBack = actionBalanceAfter.sub(actionBalanceBefore);

      expect(amountGotBack.eq(wethSupplied.mul(995).div(1000))).to.be.true;
      expect((await action.state()) === ActionState.Idle).to.be.true;
    });
  });
});
Example #13
Source File: SelfPermit.test.ts    From trident with GNU General Public License v3.0 4 votes vote down vote up
describe("SelfPermit", () => {
  let wallet: Wallet;
  let other: Wallet;

  const fixture: Fixture<{
    token: ERC20PermitAllowedMock;
    selfPermitTest: SelfPermitMock;
  }> = async (wallets, provider) => {
    const tokenFactory = await ethers.getContractFactory<ERC20PermitAllowedMock__factory>("ERC20PermitAllowedMock");
    const token = await tokenFactory.deploy(0);

    const selfPermitTestFactory = await ethers.getContractFactory<SelfPermitMock__factory>("SelfPermitMock");
    const selfPermitTest = await selfPermitTestFactory.deploy();

    return {
      token,
      selfPermitTest,
    };
  };

  let token: ERC20PermitAllowedMock;
  let selfPermitTest: SelfPermitMock;

  let loadFixture: ReturnType<typeof waffle.createFixtureLoader>;

  before("create fixture loader", async () => {
    const wallets = await (ethers as any).getSigners();
    [wallet, other] = wallets;
    loadFixture = waffle.createFixtureLoader(wallets);
  });

  beforeEach("load fixture", async () => {
    ({ token, selfPermitTest } = await loadFixture(fixture));
  });

  it("#permit", async () => {
    const value = 123;

    const { v, r, s } = await getPermitSignature(wallet, token, other.address, value);

    expect(await token.allowance(wallet.address, other.address)).to.be.eq(0);
    await token["permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"](
      wallet.address,
      other.address,
      value,
      constants.MaxUint256,
      v,
      r,
      s
    );
    expect(await token.allowance(wallet.address, other.address)).to.be.eq(value);
  });

  describe("#selfPermit", () => {
    const value = 456;

    it("works", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, value);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await selfPermitTest.selfPermit(token.address, value, constants.MaxUint256, v, r, s);
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(value);
    });

    it("fails if permit is submitted externally", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, value);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await token["permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"](
        wallet.address,
        selfPermitTest.address,
        value,
        constants.MaxUint256,
        v,
        r,
        s
      );
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(value);

      await expect(selfPermitTest.selfPermit(token.address, value, constants.MaxUint256, v, r, s)).to.be.revertedWith(
        "ERC20Permit: invalid signature"
      );
    });
  });

  describe("#selfPermitIfNecessary", () => {
    const value = 789;

    it("works", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, value);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await selfPermitTest.selfPermitIfNecessary(token.address, value, constants.MaxUint256, v, r, s);
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(value);
    });

    it("does not fail if permit is submitted externally", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, value);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await token["permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"](
        wallet.address,
        selfPermitTest.address,
        value,
        constants.MaxUint256,
        v,
        r,
        s
      );
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(value);

      await selfPermitTest.selfPermitIfNecessary(token.address, value, constants.MaxUint256, v, r, s);
    });
  });

  describe("#selfPermitAllowed", () => {
    it("works", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, constants.MaxUint256);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await expect(selfPermitTest.selfPermitAllowed(token.address, 0, constants.MaxUint256, v, r, s))
        .to.emit(token, "Approval")
        .withArgs(wallet.address, selfPermitTest.address, constants.MaxUint256);
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(constants.MaxUint256);
    });

    it("fails if permit is submitted externally", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, constants.MaxUint256);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await token["permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)"](
        wallet.address,
        selfPermitTest.address,
        0,
        constants.MaxUint256,
        true,
        v,
        r,
        s
      );
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(constants.MaxUint256);

      await expect(
        selfPermitTest.selfPermitAllowed(token.address, 0, constants.MaxUint256, v, r, s)
      ).to.be.revertedWith("ERC20PermitAllowedMock::permit: wrong nonce");
    });
  });

  describe("#selfPermitAllowedIfNecessary", () => {
    it("works", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, constants.MaxUint256);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.eq(0);
      await expect(selfPermitTest.selfPermitAllowedIfNecessary(token.address, 0, constants.MaxUint256, v, r, s))
        .to.emit(token, "Approval")
        .withArgs(wallet.address, selfPermitTest.address, constants.MaxUint256);
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.eq(constants.MaxUint256);
    });

    it("skips if already max approved", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, constants.MaxUint256);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await token.approve(selfPermitTest.address, constants.MaxUint256);
      await expect(
        selfPermitTest.selfPermitAllowedIfNecessary(token.address, 0, constants.MaxUint256, v, r, s)
      ).to.not.emit(token, "Approval");
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.eq(constants.MaxUint256);
    });

    it("does not fail if permit is submitted externally", async () => {
      const { v, r, s } = await getPermitSignature(wallet, token, selfPermitTest.address, constants.MaxUint256);

      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(0);
      await token["permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)"](
        wallet.address,
        selfPermitTest.address,
        0,
        constants.MaxUint256,
        true,
        v,
        r,
        s
      );
      expect(await token.allowance(wallet.address, selfPermitTest.address)).to.be.eq(constants.MaxUint256);

      await selfPermitTest.selfPermitAllowedIfNecessary(token.address, 0, constants.MaxUint256, v, r, s);
    });
  });
});