ethers/lib/utils#defaultAbiCoder TypeScript Examples

The following examples show how to use ethers/lib/utils#defaultAbiCoder. 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: pubkey.ts    From hubble-contracts with MIT License 6 votes vote down vote up
static fromEncoded(data: BytesLike): Pubkey {
        const pubkeyDecoded = defaultAbiCoder.decode(solidityPubkeyType, data);

        const pubkeySolG2: solG2 = [
            pubkeyDecoded[0].toHexString(),
            pubkeyDecoded[1].toHexString(),
            pubkeyDecoded[2].toHexString(),
            pubkeyDecoded[3].toHexString()
        ];

        return new this(pubkeySolG2);
    }
Example #2
Source File: sign-utils.ts    From shoyu with MIT License 6 votes vote down vote up
getHash = (types: string[], values: any[]): string => {
    return keccak256(defaultAbiCoder.encode(types, values));
}
Example #3
Source File: nounsDao.ts    From nouns-monorepo with GNU General Public License v3.0 6 votes vote down vote up
formatProposalTransactionDetails = (details: ProposalTransactionDetails | Result) => {
  return details.targets.map((target: string, i: number) => {
    const signature = details.signatures[i];
    const value = EthersBN.from(
      // Handle both logs and subgraph responses
      (details as ProposalTransactionDetails).values?.[i] ?? (details as Result)?.[3]?.[i] ?? 0,
    );
    const [name, types] = signature.substring(0, signature.length - 1)?.split('(');
    if (!name || !types) {
      return {
        target,
        functionSig: name === '' ? 'transfer' : name === undefined ? 'unknown' : name,
        callData: types ? types : value ? `${utils.formatEther(value)} ETH` : '',
      };
    }
    const calldata = details.calldatas[i];
    const decoded = defaultAbiCoder.decode(types.split(','), calldata);
    return {
      target,
      functionSig: name,
      callData: decoded.join(),
      value: value.gt(0) ? `{ value: ${utils.formatEther(value)} ETH }` : '',
    };
  });
}
Example #4
Source File: signatures.ts    From ERC20Permit with GNU General Public License v3.0 6 votes vote down vote up
// Returns the EIP712 hash which should be signed by the user
// in order to make a call to `permit`
export function getPermitDigest(
  name: string,
  address: string,
  chainId: number,
  approve: {
    owner: string
    spender: string
    value: BigNumberish
  },
  nonce: BigNumberish,
  deadline: BigNumberish
) {
  const DOMAIN_SEPARATOR = getDomainSeparator(name, address, chainId)
  return keccak256(
    solidityPack(
      ['bytes1', 'bytes1', 'bytes32', 'bytes32'],
      [
        '0x19',
        '0x01',
        DOMAIN_SEPARATOR,
        keccak256(
          defaultAbiCoder.encode(
            ['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'],
            [PERMIT_TYPEHASH, approve.owner, approve.spender, approve.value, nonce, deadline]
          )
        ),
      ]
    )
  )
}
Example #5
Source File: signatures.ts    From ERC20Permit with GNU General Public License v3.0 6 votes vote down vote up
// Gets the EIP712 domain separator
export function getDomainSeparator(name: string, contractAddress: string, chainId: number) {
  return keccak256(
    defaultAbiCoder.encode(
      ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
      [
        keccak256(toUtf8Bytes('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')),
        keccak256(toUtf8Bytes(name)),
        keccak256(toUtf8Bytes('1')),
        chainId,
        contractAddress,
      ]
    )
  )
}
Example #6
Source File: bridge.ts    From arbitrum-dai-bridge with GNU Affero General Public License v3.0 5 votes vote down vote up
export async function depositToStandardBridge({
  from,
  to,
  l2Provider,
  deposit,
  l1Gateway,
  l1TokenAddress,
  l2GatewayAddress,
}: {
  from: Wallet
  to: string
  l2Provider: ethers.providers.BaseProvider
  deposit: BigNumber | string
  l1Gateway: L1DaiGateway
  l1TokenAddress: string
  l2GatewayAddress: string
}) {
  const gasPriceBid = await getGasPriceBid(l2Provider)

  const onlyData = '0x'
  const depositCalldata = await l1Gateway.getOutboundCalldata(l1TokenAddress, from.address, to, deposit, onlyData)
  const maxSubmissionPrice = await getMaxSubmissionPrice(l2Provider, depositCalldata)

  const maxGas = await getMaxGas(
    l2Provider,
    l1Gateway.address,
    l2GatewayAddress,
    from.address,
    maxSubmissionPrice,
    gasPriceBid,
    depositCalldata,
  )
  const defaultData = defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionPrice, onlyData])
  const ethValue = await maxSubmissionPrice.add(gasPriceBid.mul(maxGas))

  return await waitForTx(
    l1Gateway.connect(from).outboundTransfer(l1TokenAddress, to, deposit, maxGas, gasPriceBid, defaultData, {
      value: ethValue,
    }),
  )
}
Example #7
Source File: bridge.ts    From arbitrum-dai-bridge with GNU Affero General Public License v3.0 5 votes vote down vote up
export async function depositToStandardRouter({
  from,
  to,
  l2Provider,
  deposit,
  l1Gateway,
  l1Router,
  l1TokenAddress,
  l2GatewayAddress,
}: {
  from: Wallet
  to: string
  l2Provider: ethers.providers.BaseProvider
  deposit: BigNumber | string
  l1Router: any
  l1Gateway: L1DaiGateway
  l1TokenAddress: string
  l2GatewayAddress: string
}) {
  const gasPriceBid = await getGasPriceBid(l2Provider)

  const onlyData = '0x'
  const depositCalldata = await l1Gateway.getOutboundCalldata(l1TokenAddress, from.address, to, deposit, onlyData)
  const maxSubmissionPrice = await getMaxSubmissionPrice(l2Provider, depositCalldata)

  const maxGas = await getMaxGas(
    l2Provider,
    l1Gateway.address,
    l2GatewayAddress,
    from.address,
    maxSubmissionPrice,
    gasPriceBid,
    depositCalldata,
  )
  const defaultData = defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionPrice, onlyData])
  const ethValue = await maxSubmissionPrice.add(gasPriceBid.mul(maxGas))

  return await waitForTx(
    l1Router.connect(from).outboundTransfer(l1TokenAddress, to, deposit, maxGas, gasPriceBid, defaultData, {
      value: ethValue,
    }),
  )
}
Example #8
Source File: governance.ts    From sybil-interface with GNU General Public License v3.0 5 votes vote down vote up
export async function fetchProposals(client: any, key: string, govId: string): Promise<ProposalData[] | null> {
  return (PROPOSAL_PROMISES[key] =
    PROPOSAL_PROMISES[key] ??
    client
      .query({
        query: PROPOSALS,
        fetchPolicy: 'cache-first',
      })
      .then(async (res: ProposalResponse) => {
        if (res) {
          return res.data.proposals.map((p, i) => {
            let description = PRELOADED_PROPOSALS[govId]?.[res.data.proposals.length - i - 1] || p.description
            if (p.startBlock === '13551293') {
              description = description.replace(/  /g, '\n').replace(/\d\. /g, '\n$&')
            }

            return {
              id: p.id,
              title: description?.split(/# |\n/g)[1] || 'Untitled',
              description: description || 'No description.',
              proposer: p.proposer.id,
              status: undefined, // initialize as 0
              forCount: undefined, // initialize as 0
              againstCount: undefined, // initialize as 0
              startBlock: parseInt(p.startBlock),
              endBlock: parseInt(p.endBlock),
              forVotes: p.forVotes,
              againstVotes: p.againstVotes,
              details: p.targets.map((target, i) => {
                let name = '',
                  types = ''
                const signature = p.signatures[i]
                let calldata = p.calldatas[i]

                if (signature === '') {
                  const fourbyte = calldata.slice(0, 10)
                  const sig = FOUR_BYTES_DIR[fourbyte] ?? 'UNKNOWN()'
                  if (!sig) throw new Error('Missing four byte sig')
                  ;[name, types] = sig.substring(0, sig.length - 1).split('(')
                  calldata = `0x${calldata.slice(10)}`
                } else {
                  ;[name, types] = signature.substring(0, signature.length - 1).split('(')
                }
                const decoded = defaultAbiCoder.decode(types.split(','), calldata)

                return {
                  target,
                  functionSig: name,
                  callData: decoded.join(', '),
                }
              }),
            }
          })
        }
        return null
      })).catch(() => {
    return Promise.reject('Error fetching proposals from subgraph')
  })
}
Example #9
Source File: AuthorizerAdaptor.test.ts    From balancer-v2-monorepo with GNU General Public License v3.0 4 votes vote down vote up
describe('AuthorizerAdaptor', () => {
  let vault: Vault;
  let authorizer: Contract;
  let adaptor: Contract;
  let admin: SignerWithAddress, grantee: SignerWithAddress, other: SignerWithAddress;

  before('setup signers', async () => {
    [, admin, grantee, other] = await ethers.getSigners();
  });

  sharedBeforeEach('deploy authorizer', async () => {
    vault = await Vault.create({ admin });
    if (!vault.authorizer) throw Error('Vault has no Authorizer');
    authorizer = vault.authorizer;
    adaptor = await deploy('AuthorizerAdaptor', { args: [vault.address] });
  });

  describe('constructor', () => {
    it('sets the vault address', async () => {
      expect(await adaptor.getVault()).to.be.eq(vault.address);
    });

    it('uses the authorizer of the vault', async () => {
      expect(await adaptor.getAuthorizer()).to.equal(authorizer.address);
    });

    it('tracks authorizer changes in the vault', async () => {
      const action = await actionId(vault.instance, 'setAuthorizer');
      await authorizer.connect(admin).grantPermissions([action], admin.address, [ANY_ADDRESS]);

      await vault.instance.connect(admin).setAuthorizer(other.address);

      expect(await adaptor.getAuthorizer()).to.equal(other.address);
    });
  });

  describe('performAction', () => {
    let action: string;
    let target: string;
    let calldata: string;
    let expectedResult: string;

    sharedBeforeEach('prepare action', async () => {
      action = await actionId(adaptor, 'getProtocolFeesCollector', vault.interface);

      target = vault.address;
      calldata = vault.interface.encodeFunctionData('getProtocolFeesCollector');

      expectedResult = defaultAbiCoder.encode(['address'], [await vault.instance.getProtocolFeesCollector()]);
    });

    context('when caller is authorized globally', () => {
      sharedBeforeEach('authorize caller globally', async () => {
        await authorizer.connect(admin).grantPermissions([action], grantee.address, [ANY_ADDRESS]);
      });

      it('performs the expected function call', async () => {
        const value = await adaptor.connect(grantee).callStatic.performAction(target, calldata);
        expect(value).to.be.eq(expectedResult);
      });
    });

    context('when caller is authorized locally on target', () => {
      sharedBeforeEach('authorize caller on target locally', async () => {
        await authorizer.connect(admin).grantPermissions([action], grantee.address, [vault.address]);
      });

      it('performs the expected function call', async () => {
        const value = await adaptor.connect(grantee).callStatic.performAction(target, calldata);

        expect(value).to.be.eq(expectedResult);
      });
    });

    context('when caller is authorized locally on a different target', () => {
      sharedBeforeEach('authorize caller on different target locally', async () => {
        await authorizer.connect(admin).grantPermissions([action], grantee.address, [other.address]);
      });

      it('reverts', async () => {
        await expect(adaptor.connect(grantee).performAction(target, calldata)).to.be.revertedWith('SENDER_NOT_ALLOWED');
      });
    });

    context('when caller is not authorized', () => {
      it('reverts', async () => {
        await expect(adaptor.connect(other).performAction(target, calldata)).to.be.revertedWith('SENDER_NOT_ALLOWED');
      });
    });
  });
});
Example #10
Source File: multicall.ts    From ethereum-multicall with MIT License 4 votes vote down vote up
/**
   * Call all the contract calls in 1
   * @param calls The calls
   */
  public async call(
    contractCallContexts: ContractCallContext[] | ContractCallContext
  ): Promise<ContractCallResults> {
    if (!Array.isArray(contractCallContexts)) {
      contractCallContexts = [contractCallContexts];
    }

    const aggregateResponse = await this.execute(
      this.buildAggregateCallContext(contractCallContexts)
    );

    const returnObject: ContractCallResults = {
      results: {},
      blockNumber: aggregateResponse.blockNumber,
    };

    for (
      let response = 0;
      response < aggregateResponse.results.length;
      response++
    ) {
      const contractCallsResults = aggregateResponse.results[response];
      const originalContractCallContext =
        contractCallContexts[contractCallsResults.contractContextIndex];

      const returnObjectResult: ContractCallReturnContext = {
        originalContractCallContext: Utils.deepClone(
          originalContractCallContext
        ),
        callsReturnContext: [],
      };

      for (
        let method = 0;
        method < contractCallsResults.methodResults.length;
        method++
      ) {
        const methodContext = contractCallsResults.methodResults[method];
        const originalContractCallMethodContext =
          originalContractCallContext.calls[methodContext.contractMethodIndex];

        const outputTypes = this.findOutputTypesFromAbi(
          originalContractCallContext.abi,
          originalContractCallMethodContext.methodName
        );

        if (this._options.tryAggregate && !methodContext.result.success) {
          returnObjectResult.callsReturnContext.push(
            Utils.deepClone<CallReturnContext>({
              returnValues: [],
              decoded: false,
              reference: originalContractCallMethodContext.reference,
              methodName: originalContractCallMethodContext.methodName,
              methodParameters:
                originalContractCallMethodContext.methodParameters,
              success: false,
            })
          );
          continue;
        }

        if (outputTypes && outputTypes.length > 0) {
          try {
            const decodedReturnValues = defaultAbiCoder.decode(
              // tslint:disable-next-line: no-any
              outputTypes as any,
              this.getReturnDataFromResult(methodContext.result)
            );

            returnObjectResult.callsReturnContext.push(
              Utils.deepClone<CallReturnContext>({
                returnValues: this.formatReturnValues(decodedReturnValues),
                decoded: true,
                reference: originalContractCallMethodContext.reference,
                methodName: originalContractCallMethodContext.methodName,
                methodParameters:
                  originalContractCallMethodContext.methodParameters,
                success: true,
              })
            );
          } catch (e) {
            if (!this._options.tryAggregate) {
              throw e
            }
            returnObjectResult.callsReturnContext.push(
              Utils.deepClone<CallReturnContext>({
                returnValues: [],
                decoded: false,
                reference: originalContractCallMethodContext.reference,
                methodName: originalContractCallMethodContext.methodName,
                methodParameters:
                  originalContractCallMethodContext.methodParameters,
                success: false,
              })
            );
          }
        } else {
          returnObjectResult.callsReturnContext.push(
            Utils.deepClone<CallReturnContext>({
              returnValues: this.getReturnDataFromResult(methodContext.result),
              decoded: false,
              reference: originalContractCallMethodContext.reference,
              methodName: originalContractCallMethodContext.methodName,
              methodParameters:
                originalContractCallMethodContext.methodParameters,
              success: true,
            })
          );
        }
      }

      returnObject.results[
        returnObjectResult.originalContractCallContext.reference
      ] = returnObjectResult;
    }

    return returnObject;
  }
Example #11
Source File: aaveLeverageStrategyIntegration.spec.ts    From index-coop-smart-contracts with Apache License 2.0 4 votes vote down vote up
describe("LeverageStrategyExtension", () => {
  let owner: Account;
  let methodologist: Account;
  let setV2Setup: SetFixture;
  let aaveSetup: AaveV2Fixture;
  let uniswapSetup: UniswapFixture;
  let sushiswapSetup: UniswapFixture;

  let deployer: DeployHelper;
  let setToken: SetToken;
  let aWETH: AaveV2AToken;
  let aUSDC: AaveV2AToken;
  let aWBTC: AaveV2AToken;
  let wethVariableDebtToken: AaveV2VariableDebtToken;
  let usdcVariableDebtToken: AaveV2VariableDebtToken;

  let strategy: AaveContractSettings;
  let methodology: MethodologySettings;
  let execution: ExecutionSettings;
  let incentive: IncentiveSettings;

  let leverageStrategyExtension: AaveLeverageStrategyExtension;
  let aaveLeverageModule: AaveLeverageModule;
  let baseManager: BaseManager;

  let chainlinkETH: ChainlinkAggregatorV3Mock;
  let chainlinkWBTC: ChainlinkAggregatorV3Mock;
  let chainlinkUSDC: ChainlinkAggregatorV3Mock;

  let wethUsdcPoolUni: UniswapV2Pair;
  let wethWbtcPoolUni: UniswapV2Pair;
  let wethUsdcPoolSushi: UniswapV2Pair;
  let wethWbtcPoolSushi: UniswapV2Pair;

  let scenarios: FLISettings[];

  before(async () => {
    [
      owner,
      methodologist,
    ] = await getAccounts();

    console.log("Deploying Base Protocols...");

    deployer = new DeployHelper(owner.wallet);

    setV2Setup = getSetFixture(owner.address);
    await setV2Setup.initialize();

    aaveSetup = getAaveV2Fixture(owner.address);
    await aaveSetup.initialize(setV2Setup.weth.address, setV2Setup.dai.address);

    uniswapSetup = getUniswapFixture(owner.address);
    await uniswapSetup.initialize(
      owner,
      setV2Setup.weth.address,
      setV2Setup.wbtc.address,
      setV2Setup.usdc.address,
      minimumInit
    );

    sushiswapSetup = getUniswapFixture(owner.address);
    await sushiswapSetup.initialize(
      owner,
      setV2Setup.weth.address,
      setV2Setup.wbtc.address,
      setV2Setup.usdc.address,
      minimumInit
    );

    wethUsdcPoolUni = await uniswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.usdc.address);
    wethWbtcPoolUni = await uniswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.wbtc.address);

    wethUsdcPoolSushi = await sushiswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.usdc.address);
    wethWbtcPoolSushi = await sushiswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.wbtc.address);

    await setV2Setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.usdc.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await uniswapSetup.router.addLiquidity(
      setV2Setup.weth.address,
      setV2Setup.usdc.address,
      ether(10000),
      usdc(10000000),
      ether(9999),
      usdc(9990000),
      owner.address,
      MAX_UINT_256
    );

    await setV2Setup.wbtc.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await uniswapSetup.router.addLiquidity(
      setV2Setup.wbtc.address,
      setV2Setup.weth.address,
      bitcoin(100),
      ether(4000),
      bitcoin(99),
      ether(3900),
      owner.address,
      MAX_UINT_256
    );

    await setV2Setup.weth.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.usdc.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await sushiswapSetup.router.addLiquidity(
      setV2Setup.weth.address,
      setV2Setup.usdc.address,
      ether(4000),
      usdc(4000000),
      ether(399),
      usdc(499000),
      owner.address,
      MAX_UINT_256
    );

    await setV2Setup.wbtc.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.weth.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await sushiswapSetup.router.addLiquidity(
      setV2Setup.wbtc.address,
      setV2Setup.weth.address,
      bitcoin(50),
      ether(2000),
      bitcoin(49),
      ether(1900),
      owner.address,
      MAX_UINT_256
    );

    const usdcReserveTokens = await aaveSetup.createAndEnableReserve(
      setV2Setup.usdc.address,
      "USDC",
      6,
      BigNumber.from(7500),   // base LTV: 75%
      BigNumber.from(8000),   // liquidation threshold: 80%
      BigNumber.from(10500),  // liquidation bonus: 105.00%
      BigNumber.from(1000),   // reserve factor: 10%
      true,					          // enable borrowing on reserve
      true					          // enable stable debts
    );

    const wbtcReserveTokens = await aaveSetup.createAndEnableReserve(
      setV2Setup.wbtc.address,
      "wBTC",
      8,
      BigNumber.from(7500),   // base LTV: 75%
      BigNumber.from(8000),   // liquidation threshold: 80%
      BigNumber.from(10500),  // liquidation bonus: 105.00%
      BigNumber.from(1000),   // reserve factor: 10%
      true,					          // enable borrowing on reserve
      true					          // enable stable debts
    );


    aUSDC = usdcReserveTokens.aToken;
    aWBTC = wbtcReserveTokens.aToken;
    aWETH = aaveSetup.wethReserveTokens.aToken;

    usdcVariableDebtToken = usdcReserveTokens.variableDebtToken;
    wethVariableDebtToken = aaveSetup.wethReserveTokens.variableDebtToken;

    const oneRay = BigNumber.from(10).pow(27);	// 1e27
    await aaveSetup.setMarketBorrowRate(setV2Setup.usdc.address, oneRay.mul(39).div(1000));
    await aaveSetup.setAssetPriceInOracle(setV2Setup.usdc.address, ether(0.001));
    await aaveSetup.setMarketBorrowRate(setV2Setup.wbtc.address, oneRay.mul(39).div(1000));
    await aaveSetup.setAssetPriceInOracle(setV2Setup.wbtc.address, ether(50));

    // Mint aTokens
    await setV2Setup.weth.approve(aaveSetup.lendingPool.address, MAX_UINT_256);
    await aaveSetup.lendingPool.deposit(setV2Setup.weth.address, ether(10000), owner.address, 0);
    await setV2Setup.usdc.approve(aaveSetup.lendingPool.address, MAX_UINT_256);
    await aaveSetup.lendingPool.deposit(setV2Setup.usdc.address, usdc(200000000), owner.address, 0);
    await setV2Setup.wbtc.approve(aaveSetup.lendingPool.address, MAX_UINT_256);
    await aaveSetup.lendingPool.deposit(setV2Setup.wbtc.address, bitcoin(1000), owner.address, 0);

    // Deploy Aave leverage module and add to controller
    aaveLeverageModule = await deployer.setV2.deployAaveLeverageModule(
      setV2Setup.controller.address,
      aaveSetup.lendingPoolAddressesProvider.address,
      aaveSetup.protocolDataProvider.address
    );
    await setV2Setup.controller.addModule(aaveLeverageModule.address);

    // Set integrations for AaveLeverageModule
    await setV2Setup.integrationRegistry.addIntegration(
      aaveLeverageModule.address,
      "UniswapTradeAdapter",
      uniswapSetup.uniswapTradeAdapter.address,
    );

    await setV2Setup.integrationRegistry.addIntegration(
      aaveLeverageModule.address,
      "SushiswapTradeAdapter",
      sushiswapSetup.uniswapTradeAdapter.address,
    );

    await setV2Setup.integrationRegistry.addIntegration(
      aaveLeverageModule.address,
      "DefaultIssuanceModule",
      setV2Setup.debtIssuanceModule.address,
    );

    // Deploy Chainlink mocks
    chainlinkETH = await deployer.mocks.deployChainlinkAggregatorMock();
    await chainlinkETH.setPrice(BigNumber.from(1000).mul(10 ** 8));
    chainlinkUSDC = await deployer.mocks.deployChainlinkAggregatorMock();
    await chainlinkUSDC.setPrice(10 ** 8);
    chainlinkWBTC = await deployer.mocks.deployChainlinkAggregatorMock();
    await chainlinkWBTC.setPrice(BigNumber.from(50000).mul(10 ** 8));
  });

  beforeEach(async () => {
    scenarios = [
      {
        name: "ETH/USDC 2x",
        collateralAsset: setV2Setup.weth,
        borrowAsset: setV2Setup.usdc,
        collateralAToken: aWETH,
        borrowDebtToken: usdcVariableDebtToken,
        chainlinkCollateral: chainlinkETH,
        chainlinkBorrow: chainlinkUSDC,
        targetLeverageRatio: ether(2),
        collateralPerSet: ether(1),
        exchangeNames: [ "UniswapTradeAdapter", "SushiswapTradeAdapter" ],
        exchanges: [
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: ether(5),
            incentivizedTwapMaxTradeSize: ether(10),
            leverExchangeData: EMPTY_BYTES,
            deleverExchangeData: EMPTY_BYTES,
          },
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: ether(5),
            incentivizedTwapMaxTradeSize: ether(10),
            leverExchangeData: EMPTY_BYTES,
            deleverExchangeData: EMPTY_BYTES,
          },
        ],
        checkpoints: [
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1000),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1100),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1100),
            exchangeName: "SushiswapTradeAdapter",
            exchangePools: [wethUsdcPoolSushi],
            router: sushiswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(800),
            borrowPrice: ether(1),
            elapsedTime: ONE_HOUR_IN_SECONDS.mul(12),
            wethPrice: ether(800),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
        ],
      } as FLISettings,
      {
        name: "ETH/USDC Inverse",
        collateralAsset: setV2Setup.usdc,
        borrowAsset: setV2Setup.weth,
        collateralAToken: aUSDC,
        borrowDebtToken: wethVariableDebtToken,
        chainlinkCollateral: chainlinkUSDC,
        chainlinkBorrow: chainlinkETH,
        targetLeverageRatio: ether(2),
        collateralPerSet: usdc(100),
        exchangeNames: [ "UniswapTradeAdapter" ],
        exchanges: [
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: ether(1000),
            incentivizedTwapMaxTradeSize: ether(100000),
            leverExchangeData: EMPTY_BYTES,
            deleverExchangeData: EMPTY_BYTES,
          },
        ],
        checkpoints: [
          {
            issueAmount: ether(500),
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1300),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1300),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ether(10000),
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1300),
            elapsedTime: ONE_HOUR_IN_SECONDS,
            wethPrice: ether(1300),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1300),
            elapsedTime: ONE_HOUR_IN_SECONDS,
            wethPrice: ether(1300),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1700),
            elapsedTime: ONE_HOUR_IN_SECONDS,
            wethPrice: ether(1700),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1100),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1100),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
        ],
      } as FLISettings,
      {
        name: "BTC/USDC 2x",
        collateralAsset: setV2Setup.wbtc,
        borrowAsset: setV2Setup.usdc,
        collateralAToken: aWBTC,
        borrowDebtToken: usdcVariableDebtToken,
        chainlinkCollateral: chainlinkWBTC,
        chainlinkBorrow: chainlinkUSDC,
        targetLeverageRatio: ether(2),
        collateralPerSet: bitcoin(0.1),
        exchangeNames: [ "UniswapTradeAdapter", "SushiswapTradeAdapter" ],
        exchanges: [
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: bitcoin(3),
            incentivizedTwapMaxTradeSize: bitcoin(5),
            leverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.usdc.address, setV2Setup.weth.address, setV2Setup.wbtc.address]]),
            deleverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.wbtc.address, setV2Setup.weth.address, setV2Setup.usdc.address]]),
          },
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: bitcoin(3),
            incentivizedTwapMaxTradeSize: bitcoin(5),
            leverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.usdc.address, setV2Setup.weth.address, setV2Setup.wbtc.address]]),
            deleverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.wbtc.address, setV2Setup.weth.address, setV2Setup.usdc.address]]),
          },
        ],
        checkpoints: [
          {
            issueAmount: ether(.5),
            redeemAmount: ZERO,
            collateralPrice: ether(55000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1375),
            exchangeName: "SushiswapTradeAdapter",
            exchangePools: [wethWbtcPoolSushi, wethUsdcPoolSushi],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ether(2),
            redeemAmount: ZERO,
            collateralPrice: ether(49000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1225),
            exchangeName: "SushiswapTradeAdapter",
            exchangePools: [wethWbtcPoolSushi, wethUsdcPoolSushi],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ether(5),
            collateralPrice: ether(35000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(875),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethWbtcPoolUni, wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
        ],
      } as FLISettings,
    ];
  });

  addSnapshotBeforeRestoreAfterEach();

  describe("#scenario1", async () => {
    let subjectScenario: FLISettings;

    beforeEach(async () => {
      subjectScenario = scenarios[0];

      await deployFLISetup(subjectScenario);

      await issueFLITokens(subjectScenario.collateralAToken, ether(10));

      await engageFLI(subjectScenario.checkpoints[0].exchangeName);
    });

    async function subject(): Promise<any> {
      return runScenarios(subjectScenario, false);
    }

    it("validate state", async () => {
      const [preRebalanceLeverageRatios, postRebalanceLeverageRatios] = await subject();

      const lastTradeTimestamp = await leverageStrategyExtension.globalLastTradeTimestamp();

      expect(lastTradeTimestamp).to.eq(await getLastBlockTimestamp());
      expect(postRebalanceLeverageRatios[0]).to.lt(preRebalanceLeverageRatios[0]);
      expect(postRebalanceLeverageRatios[1]).to.gt(preRebalanceLeverageRatios[1]);
      expect(postRebalanceLeverageRatios[2]).to.lt(preRebalanceLeverageRatios[2]);
    });
  });

  describe("#scenario2", async () => {
    let subjectScenario: FLISettings;

    beforeEach(async () => {
      subjectScenario = scenarios[1];

      await deployFLISetup(subjectScenario);

      await issueFLITokens(subjectScenario.collateralAToken, ether(10));

      await engageFLI(subjectScenario.checkpoints[0].exchangeName);
    });

    async function subject(): Promise<any> {
      return runScenarios(subjectScenario, false);
    }

    it("validate state", async () => {
      const [preRebalanceLeverageRatios, postRebalanceLeverageRatios] = await subject();

      const lastTradeTimestamp = await leverageStrategyExtension.globalLastTradeTimestamp();

      expect(lastTradeTimestamp).to.eq(await getLastBlockTimestamp());
      expect(postRebalanceLeverageRatios[0]).to.lt(preRebalanceLeverageRatios[0]);
      expect(postRebalanceLeverageRatios[1]).to.lt(preRebalanceLeverageRatios[1]);
      expect(postRebalanceLeverageRatios[2]).to.lt(preRebalanceLeverageRatios[2]);
      expect(postRebalanceLeverageRatios[3]).to.lt(preRebalanceLeverageRatios[3]);
      expect(postRebalanceLeverageRatios[4]).to.gt(preRebalanceLeverageRatios[4]);
    });
  });

  describe("#scenario3", async () => {
    let subjectScenario: FLISettings;

    beforeEach(async () => {
      subjectScenario = scenarios[2];

      await deployFLISetup(subjectScenario);

      await issueFLITokens(subjectScenario.collateralAToken, ether(10));

      await engageFLI(subjectScenario.checkpoints[0].exchangeName);
    });

    async function subject(): Promise<any> {
      return runScenarios(subjectScenario, true);
    }

    it("validate state", async () => {
      const [preRebalanceLeverageRatios, postRebalanceLeverageRatios] = await subject();

      const lastTradeTimestamp = await leverageStrategyExtension.globalLastTradeTimestamp();

      expect(lastTradeTimestamp).to.eq(await getLastBlockTimestamp());
      expect(postRebalanceLeverageRatios[0]).to.gt(preRebalanceLeverageRatios[0]);
      expect(postRebalanceLeverageRatios[1]).to.gt(preRebalanceLeverageRatios[1]);
      expect(postRebalanceLeverageRatios[2]).to.lt(preRebalanceLeverageRatios[2]);
    });
  });

  async function deployFLISetup(fliSettings: FLISettings): Promise<void> {
    console.log("Deploying FLI Strategy and SetToken...");

    setToken = await setV2Setup.createSetToken(
      [fliSettings.collateralAToken.address],
      [fliSettings.collateralPerSet],
      [
        setV2Setup.streamingFeeModule.address,
        aaveLeverageModule.address,
        setV2Setup.debtIssuanceModule.address,
      ]
    );
    await aaveLeverageModule.updateAnySetAllowed(true);

    // Initialize modules
    await setV2Setup.debtIssuanceModule.initialize(
      setToken.address,
      ether(1),
      ZERO,
      ZERO,
      owner.address,
      ADDRESS_ZERO
    );
    const feeRecipient = owner.address;
    const maxStreamingFeePercentage = ether(.1);
    const streamingFeePercentage = ether(.02);
    const streamingFeeSettings = {
      feeRecipient,
      maxStreamingFeePercentage,
      streamingFeePercentage,
      lastStreamingFeeTimestamp: ZERO,
    };
    await setV2Setup.streamingFeeModule.initialize(setToken.address, streamingFeeSettings);
    await aaveLeverageModule.initialize(
      setToken.address,
      [fliSettings.collateralAsset.address],
      [fliSettings.borrowAsset.address]
    );

    baseManager = await deployer.manager.deployBaseManager(
      setToken.address,
      owner.address,
      methodologist.address,
    );

    // Transfer ownership to ic manager
    await setToken.setManager(baseManager.address);

    strategy = {
      setToken: setToken.address,
      leverageModule: aaveLeverageModule.address,
      aaveProtocolDataProvider: aaveSetup.protocolDataProvider.address,
      collateralPriceOracle: fliSettings.chainlinkCollateral.address,
      borrowPriceOracle: fliSettings.chainlinkBorrow.address,
      targetCollateralAToken: fliSettings.collateralAToken.address,
      targetBorrowDebtToken: fliSettings.borrowDebtToken.address,
      collateralAsset: fliSettings.collateralAsset.address,
      borrowAsset: fliSettings.borrowAsset.address,
      collateralDecimalAdjustment: BigNumber.from(28 - await fliSettings.collateralAsset.decimals()),
      borrowDecimalAdjustment: BigNumber.from(28 - await fliSettings.borrowAsset.decimals()),
    };
    methodology = {
      targetLeverageRatio: fliSettings.targetLeverageRatio,
      minLeverageRatio: preciseMul(fliSettings.targetLeverageRatio, PRECISE_UNIT.sub(minLeverageBuffer)),
      maxLeverageRatio: preciseMul(fliSettings.targetLeverageRatio, PRECISE_UNIT.add(maxLeverageBuffer)),
      recenteringSpeed: recenteringSpeed,
      rebalanceInterval: rebalanceInterval,
    };
    execution = {
      unutilizedLeveragePercentage: unutilizedLeveragePercentage,
      twapCooldownPeriod: twapCooldownPeriod,
      slippageTolerance: slippageTolerance,
    };
    incentive = {
      incentivizedTwapCooldownPeriod: incentivizedTwapCooldownPeriod,
      incentivizedSlippageTolerance: incentivizedSlippageTolerance,
      etherReward: etherReward,
      incentivizedLeverageRatio: incentivizedLeverageRatio,
    };

    leverageStrategyExtension = await deployer.extensions.deployAaveLeverageStrategyExtension(
      baseManager.address,
      strategy,
      methodology,
      execution,
      incentive,
      fliSettings.exchangeNames,
      fliSettings.exchanges
    );
    await leverageStrategyExtension.updateCallerStatus([owner.address], [true]);

    // Add adapter
    await baseManager.connect(owner.wallet).addAdapter(leverageStrategyExtension.address);
  }

  async function issueFLITokens(collateralAToken: AaveV2AToken, amount: BigNumber): Promise<void> {
    console.log(`Issuing ${amount.toString()} SetTokens`);
    if (amount.gt(ZERO)) {
      await collateralAToken.approve(setV2Setup.debtIssuanceModule.address, MAX_UINT_256);
      await setV2Setup.debtIssuanceModule.issue(setToken.address, amount, owner.address);
    }
  }

  async function redeemFLITokens(amount: BigNumber): Promise<void> {
    console.log(`Redeeming ${amount.toString()} SetTokens`);
    if (amount.gt(ZERO)) {
      await setV2Setup.debtIssuanceModule.issue(setToken.address, amount, owner.address);
    }
  }

  async function engageFLI(exchangeName: string): Promise<void> {
    console.log("Engaging FLI...");
    await leverageStrategyExtension.engage(exchangeName);
    await increaseTimeAsync(twapCooldownPeriod);
    await leverageStrategyExtension.iterateRebalance(exchangeName);
  }

  async function runScenarios(fliSettings: FLISettings, isMultihop: boolean): Promise<[BigNumber[], BigNumber[]]> {
    console.log(`Running Scenarios ${fliSettings.name}`);
    await increaseTimeAsync(rebalanceInterval);

    await leverageStrategyExtension.rebalance(fliSettings.checkpoints[0].exchangeName);

    const preRebalanceLeverageRatios = [];
    const postRebalanceLeverageRatios = [];
    for (let i = 0; i < fliSettings.checkpoints.length; i++) {
      console.log("----------------------");

      await setPricesAndUniswapPool(fliSettings, i, isMultihop);

      await liquidateIfLiquidatable(fliSettings);

      await issueFLITokens(fliSettings.collateralAToken, fliSettings.checkpoints[i].issueAmount);
      await redeemFLITokens(fliSettings.checkpoints[i].redeemAmount);

      await increaseTimeAsync(fliSettings.checkpoints[i].elapsedTime);

      const rebalanceInfo = await leverageStrategyExtension.shouldRebalance();

      const preRebalanceLeverageRatio = await leverageStrategyExtension.getCurrentLeverageRatio();
      preRebalanceLeverageRatios.push(preRebalanceLeverageRatio);
      console.log("Pre-Rebalance Leverage Ratio:", preRebalanceLeverageRatio.toString());

      const rebalanceInfoIndex = rebalanceInfo[0].indexOf(fliSettings.checkpoints[i].exchangeName);
      const rebalanceType = rebalanceInfo[1][rebalanceInfoIndex];
      if (rebalanceType != 0) {
        await executeTrade(rebalanceType, fliSettings.checkpoints[i].exchangeName);
      }
      console.log("RebalanceType:", rebalanceType);
      const postRebalanceLeverageRatio = await leverageStrategyExtension.getCurrentLeverageRatio();
      postRebalanceLeverageRatios.push(postRebalanceLeverageRatio);
      console.log("Leverage Ratio:", postRebalanceLeverageRatio.toString());
      console.log(
        "Debt Position:",
        (await setToken.getExternalPositionRealUnit(
          fliSettings.borrowAsset.address,
          aaveLeverageModule.address
        )).toString()
      );
      console.log("Collateral Position:", (await setToken.getDefaultPositionRealUnit(fliSettings.collateralAToken.address)).toString());
      console.log("Borrow Asset Price:", fliSettings.checkpoints[i].borrowPrice.toString());
      console.log("Collateral Asset Price:", fliSettings.checkpoints[i].collateralPrice.toString());
      console.log("Set Value:", (await calculateSetValue(fliSettings, i)).toString());
    }

    return [preRebalanceLeverageRatios, postRebalanceLeverageRatios];
  }

  async function setPricesAndUniswapPool(
    fliSettings: FLISettings,
    checkpoint: number,
    isMultihop: boolean
  ): Promise<void> {

    await aaveSetup.setAssetPriceInOracle(fliSettings.collateralAsset.address,
      preciseDiv(
        fliSettings.checkpoints[checkpoint].collateralPrice,
        fliSettings.checkpoints[checkpoint].wethPrice
      )
    );
    await fliSettings.chainlinkCollateral.setPrice(fliSettings.checkpoints[checkpoint].collateralPrice.div(10 ** 10));

    await aaveSetup.setAssetPriceInOracle(fliSettings.borrowAsset.address,
      preciseDiv(
        fliSettings.checkpoints[checkpoint].borrowPrice,
        fliSettings.checkpoints[checkpoint].wethPrice
      )
    );
    await fliSettings.chainlinkBorrow.setPrice(fliSettings.checkpoints[checkpoint].borrowPrice.div(10 ** 10));

    const collateralPrice = fliSettings.checkpoints[checkpoint].collateralPrice;
    const borrowPrice = fliSettings.checkpoints[checkpoint].borrowPrice;
    const wethPrice = fliSettings.checkpoints[checkpoint].wethPrice;
    if (isMultihop) {
      // Set collateral asset <> WETH pool
      await calculateAndSetUniswapPool(
        fliSettings.checkpoints[checkpoint].router,
        fliSettings.collateralAsset,
        setV2Setup.weth,
        collateralPrice,
        wethPrice,
        fliSettings.checkpoints[checkpoint].exchangePools[0],
      );

      // Set WETH <> borrow asset pool
      await calculateAndSetUniswapPool(
        fliSettings.checkpoints[checkpoint].router,
        setV2Setup.weth,
        fliSettings.borrowAsset,
        wethPrice,
        borrowPrice,
        fliSettings.checkpoints[checkpoint].exchangePools[1],
      );
    } else {
      await calculateAndSetUniswapPool(
        fliSettings.checkpoints[checkpoint].router,
        fliSettings.collateralAsset,
        fliSettings.borrowAsset,
        collateralPrice,
        borrowPrice,
        fliSettings.checkpoints[checkpoint].exchangePools[0],
      );
    }
  }

  async function executeTrade(shouldRebalance: number, exchangeName: string): Promise<void> {
    switch (shouldRebalance) {
      case 1: {
        await leverageStrategyExtension.rebalance(exchangeName);
        break;
      }
      case 2: {
        await leverageStrategyExtension.iterateRebalance(exchangeName);
        break;
    }
      case 3: {
        await leverageStrategyExtension.ripcord(exchangeName);
        break;
      }
    }
  }

  async function calculateAndSetUniswapPool(
    router: UniswapV2Router02,
    assetOne: StandardTokenMock | WETH9,
    assetTwo: StandardTokenMock | WETH9,
    assetOnePrice: BigNumber,
    assetTwoPrice: BigNumber,
    uniswapPool: UniswapV2Pair
  ): Promise<void> {
    const [ assetOneAmount, buyAssetOne ] = await calculateUniswapTradeAmount(
      assetOne,
      assetTwo,
      assetOnePrice,
      assetTwoPrice,
      uniswapPool,
    );

    if (buyAssetOne) {
      await router.swapTokensForExactTokens(
        assetOneAmount,
        MAX_UINT_256,
        [assetTwo.address, assetOne.address],
        owner.address,
        MAX_UINT_256
      );
    } else {
      await router.swapExactTokensForTokens(
        assetOneAmount,
        ZERO,
        [assetOne.address, assetTwo.address],
        owner.address,
        MAX_UINT_256
      );
    }
  }

  async function calculateUniswapTradeAmount(
    assetOne: StandardTokenMock | WETH9,
    assetTwo: StandardTokenMock | WETH9,
    assetOnePrice: BigNumber,
    assetTwoPrice: BigNumber,
    uniswapPool: UniswapV2Pair
  ): Promise<[BigNumber, boolean]> {
    const assetOneDecimals = BigNumber.from(10).pow((await assetOne.decimals()));
    const assetTwoDecimals = BigNumber.from(10).pow((await assetTwo.decimals()));
    const [ assetOneReserve, assetTwoReserve ] = await getUniswapReserves(uniswapPool, assetOne);
    const expectedPrice = preciseDiv(assetOnePrice, assetTwoPrice);

    const currentK = assetOneReserve.mul(assetTwoReserve);
    const assetOneLeft = sqrt(currentK.div(expectedPrice.mul(assetTwoDecimals).div(assetOneDecimals))).mul(BigNumber.from(10).pow(9));

    return assetOneLeft.gt(assetOneReserve) ?
      [ assetOneLeft.sub(assetOneReserve), false ] :
      [ assetOneReserve.sub(assetOneLeft), true ];
  }

  async function getUniswapReserves(
    uniswapPool: UniswapV2Pair,
    assetOne: StandardTokenMock | WETH9
  ): Promise<[BigNumber, BigNumber]> {
    const [ reserveOne, reserveTwo ] = await uniswapPool.getReserves();
    const tokenOne = await uniswapPool.token0();
    return tokenOne == assetOne.address ? [reserveOne, reserveTwo] : [reserveTwo, reserveOne];
  }

  async function liquidateIfLiquidatable(fliSettings: FLISettings): Promise<void> {
    const accountData = await aaveSetup.lendingPool.getUserAccountData(setToken.address);

    if (accountData.healthFactor.lt(ether(1))) {
      await aaveSetup.lendingPool.liquidationCall(
        fliSettings.collateralAsset.address,
        fliSettings.borrowAsset.address,
        setToken.address,
        MAX_UINT_256,
        true
      );
    }
  }

  async function calculateSetValue(fliSettings: FLISettings, checkpoint: number): Promise<BigNumber> {
    const totalSupply = await setToken.totalSupply();
    const collateralATokenUnit = (await setToken.getDefaultPositionRealUnit(fliSettings.collateralAToken.address));
    const borrowUnit = (await setToken.getExternalPositionRealUnit(fliSettings.borrowAsset.address, aaveLeverageModule.address));
    const borrowDecimals = BigNumber.from(10).pow(await fliSettings.borrowAsset.decimals());

    const collateralValue = preciseMul(collateralATokenUnit, totalSupply).mul(fliSettings.checkpoints[checkpoint].collateralPrice).div(bitcoin(50));
    const borrowValue = preciseMul(borrowUnit, totalSupply).mul(fliSettings.checkpoints[checkpoint].borrowPrice).div(borrowDecimals);

    return collateralValue.add(borrowValue);
  }

  function sqrt(value: BigNumber) {
    let z = value.add(ONE).div(TWO);
    let y = value;
    while (z.sub(y).isNegative()) {
        y = z;
        z = value.div(z).add(z).div(TWO);
    }
    return y;
  }
});
Example #12
Source File: L1DaiGateway.ts    From arbitrum-dai-bridge with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('L1DaiGateway', () => {
  describe('outboundTransfer()', () => {
    const depositAmount = 100
    const defaultGas = 42
    const maxSubmissionCost = 7
    const emptyCallHookData = '0x'
    const defaultEthValue = parseUnits('0.1', 'ether')
    const defaultData = defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionCost, emptyCallHookData])
    const notEmptyCallHookData = '0x12'
    const defaultDataWithNotEmptyCallHookData = defaultAbiCoder.encode(
      ['uint256', 'bytes'],
      [maxSubmissionCost, notEmptyCallHookData],
    )

    it('escrows funds and sends xdomain message', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender] = await ethers.getSigners()
      const defaultInboxBalance = await inboxImpersonator.getBalance()
      const { l1Dai, inboxMock, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      const depositTx = await l1DaiGateway
        .connect(sender)
        .outboundTransfer(l1Dai.address, sender.address, depositAmount, defaultGas, 0, defaultData, {
          value: defaultEthValue,
        })
      const depositCallToMessengerCall = inboxMock.smocked.createRetryableTicket.calls[0]

      const expectedDepositId = 0
      const l2EncodedData = defaultAbiCoder.encode(['bytes', 'bytes'], ['0x', emptyCallHookData])
      const expectedDepositXDomainCallData = new L2DaiGateway__factory().interface.encodeFunctionData(
        'finalizeInboundTransfer',
        [l1Dai.address, sender.address, sender.address, depositAmount, l2EncodedData],
      )

      expect(await l1Dai.balanceOf(sender.address)).to.be.eq(initialTotalL1Supply - depositAmount)
      expect(await l1Dai.balanceOf(l1DaiGateway.address)).to.be.eq(0)
      expect(await l1Dai.balanceOf(l1EscrowEOA.address)).to.be.eq(depositAmount)

      expect(await inboxImpersonator.getBalance()).to.equal(defaultInboxBalance.add(defaultEthValue))
      expect(depositCallToMessengerCall.destAddr).to.equal(l2DaiGatewayEOA.address)
      expect(depositCallToMessengerCall.l2CallValue).to.equal(0)
      expect(depositCallToMessengerCall.maxSubmissionCost).to.equal(maxSubmissionCost)
      expect(depositCallToMessengerCall.excessFeeRefundAddress).to.equal(sender.address)
      expect(depositCallToMessengerCall.callValueRefundAddress).to.equal(sender.address)
      expect(depositCallToMessengerCall.maxGas).to.equal(defaultGas)
      expect(depositCallToMessengerCall.gasPriceBid).to.equal(0)
      expect(depositCallToMessengerCall.data).to.equal(expectedDepositXDomainCallData)

      await expect(depositTx)
        .to.emit(l1DaiGateway, 'DepositInitiated')
        .withArgs(l1Dai.address, sender.address, sender.address, expectedDepositId, depositAmount)
      await expect(depositTx)
        .to.emit(l1DaiGateway, 'TxToL2')
        .withArgs(sender.address, l2DaiGatewayEOA.address, expectedDepositId, expectedDepositXDomainCallData)
    })

    it('escrows funds and sends xdomain message for 3rd party', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender, receiver] =
        await ethers.getSigners()
      const { l1Dai, inboxMock, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      const depositTx = await l1DaiGateway
        .connect(sender)
        .outboundTransfer(l1Dai.address, receiver.address, depositAmount, defaultGas, 0, defaultData)
      const depositCallToMessengerCall = inboxMock.smocked.createRetryableTicket.calls[0]

      const expectedDepositId = 0
      const l2EncodedData = defaultAbiCoder.encode(['bytes', 'bytes'], ['0x', emptyCallHookData])
      const expectedDepositXDomainCallData = new L2DaiGateway__factory().interface.encodeFunctionData(
        'finalizeInboundTransfer',
        [l1Dai.address, sender.address, receiver.address, depositAmount, l2EncodedData],
      )

      expect(await l1Dai.balanceOf(sender.address)).to.be.eq(initialTotalL1Supply - depositAmount)
      expect(await l1Dai.balanceOf(receiver.address)).to.be.eq(0)
      expect(await l1Dai.balanceOf(l1DaiGateway.address)).to.be.eq(0)
      expect(await l1Dai.balanceOf(l1EscrowEOA.address)).to.be.eq(depositAmount)

      expect(depositCallToMessengerCall.destAddr).to.equal(l2DaiGatewayEOA.address)
      expect(depositCallToMessengerCall.l2CallValue).to.equal(0)
      expect(depositCallToMessengerCall.maxSubmissionCost).to.equal(maxSubmissionCost)
      expect(depositCallToMessengerCall.excessFeeRefundAddress).to.equal(sender.address)
      expect(depositCallToMessengerCall.callValueRefundAddress).to.equal(sender.address)
      expect(depositCallToMessengerCall.maxGas).to.equal(defaultGas)
      expect(depositCallToMessengerCall.gasPriceBid).to.equal(0)
      expect(depositCallToMessengerCall.data).to.equal(expectedDepositXDomainCallData)

      await expect(depositTx)
        .to.emit(l1DaiGateway, 'DepositInitiated')
        .withArgs(l1Dai.address, sender.address, receiver.address, expectedDepositId, depositAmount)
      await expect(depositTx)
        .to.emit(l1DaiGateway, 'TxToL2')
        .withArgs(sender.address, l2DaiGatewayEOA.address, expectedDepositId, expectedDepositXDomainCallData)
    })

    it('decodes data correctly when called via router', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender] = await ethers.getSigners()
      const { l1Dai, inboxMock, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })
      const routerEncodedData = defaultAbiCoder.encode(['address', 'bytes'], [sender.address, defaultData])

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      const depositTx = await l1DaiGateway
        .connect(routerEOA)
        .outboundTransfer(l1Dai.address, sender.address, depositAmount, defaultGas, 0, routerEncodedData)
      const depositCallToMessengerCall = inboxMock.smocked.createRetryableTicket.calls[0]

      const expectedDepositId = 0
      const l2EncodedData = defaultAbiCoder.encode(['bytes', 'bytes'], ['0x', emptyCallHookData])
      const expectedDepositXDomainCallData = new L2DaiGateway__factory().interface.encodeFunctionData(
        'finalizeInboundTransfer',
        [l1Dai.address, sender.address, sender.address, depositAmount, l2EncodedData],
      )

      expect(await l1Dai.balanceOf(sender.address)).to.be.eq(initialTotalL1Supply - depositAmount)
      expect(await l1Dai.balanceOf(l1DaiGateway.address)).to.be.eq(0)
      expect(await l1Dai.balanceOf(l1EscrowEOA.address)).to.be.eq(depositAmount)

      expect(depositCallToMessengerCall.destAddr).to.equal(l2DaiGatewayEOA.address)
      expect(depositCallToMessengerCall.l2CallValue).to.equal(0)
      expect(depositCallToMessengerCall.maxSubmissionCost).to.equal(maxSubmissionCost)
      expect(depositCallToMessengerCall.excessFeeRefundAddress).to.equal(sender.address)
      expect(depositCallToMessengerCall.callValueRefundAddress).to.equal(sender.address)
      expect(depositCallToMessengerCall.maxGas).to.equal(defaultGas)
      expect(depositCallToMessengerCall.gasPriceBid).to.equal(0)
      expect(depositCallToMessengerCall.data).to.equal(expectedDepositXDomainCallData)

      await expect(depositTx)
        .to.emit(l1DaiGateway, 'DepositInitiated')
        .withArgs(l1Dai.address, sender.address, sender.address, expectedDepositId, depositAmount)
      await expect(depositTx)
        .to.emit(l1DaiGateway, 'TxToL2')
        .withArgs(sender.address, l2DaiGatewayEOA.address, expectedDepositId, expectedDepositXDomainCallData)
    })

    it('reverts when called with a different token', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender] = await ethers.getSigners()
      const { l1Dai, l1DaiGateway, l2Dai } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      await expect(
        l1DaiGateway
          .connect(sender)
          .outboundTransfer(l2Dai.address, sender.address, depositAmount, defaultGas, 0, defaultData),
      ).to.be.revertedWith(errorMessages.tokenMismatch)
    })

    it('reverts when called with hook calldata', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender] = await ethers.getSigners()
      const { l1Dai, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      await expect(
        l1DaiGateway
          .connect(sender)
          .outboundTransfer(
            l1Dai.address,
            sender.address,
            depositAmount,
            defaultGas,
            0,
            defaultDataWithNotEmptyCallHookData,
          ),
      ).to.be.revertedWith(errorMessages.callHookDataNotAllowed)
    })

    it('reverts when approval is too low', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender, receiver] =
        await ethers.getSigners()
      const { l1Dai, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })

      await expect(
        l1DaiGateway
          .connect(sender)
          .outboundTransfer(l1Dai.address, receiver.address, depositAmount, defaultGas, 0, defaultData),
      ).to.be.revertedWith(errorMessages.insufficientAllowance)
    })

    it('reverts when funds too low', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, user1, sender, receiver] =
        await ethers.getSigners()
      const { l1Dai, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
      })

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      await expect(
        l1DaiGateway
          .connect(sender)
          .outboundTransfer(l1Dai.address, receiver.address, depositAmount, defaultGas, 0, defaultData),
      ).to.be.revertedWith(errorMessages.insufficientFunds)
    })

    it('reverts when bridge is closed', async () => {
      const [deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, sender, receiver] =
        await ethers.getSigners()
      const { l1Dai, l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
      })

      await l1DaiGateway.connect(deployer).close()

      await l1Dai.connect(sender).approve(l1DaiGateway.address, depositAmount)
      await expect(
        l1DaiGateway
          .connect(sender)
          .outboundTransfer(l1Dai.address, receiver.address, depositAmount, defaultGas, 0, defaultData),
      ).to.revertedWith(errorMessages.closed)
    })
  })

  describe('finalizeInboundTransfer', () => {
    const withdrawAmount = 100
    const expectedTransferId = 1
    const defaultWithdrawData = ethers.utils.defaultAbiCoder.encode(['uint256', 'bytes'], [expectedTransferId, '0x'])

    it('sends funds from the escrow', async () => {
      const [
        _deployer,
        inboxImpersonator,
        l1EscrowEOA,
        l2DaiGatewayEOA,
        routerEOA,
        bridgeImpersonator,
        outboxImpersonator,
        user1,
      ] = await ethers.getSigners()
      const { l1Dai, outboxMock, l1DaiGateway } = await setupWithdrawalTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
        bridgeImpersonator,
        outboxImpersonator,
      })
      outboxMock.smocked.l2ToL1Sender.will.return.with(() => l2DaiGatewayEOA.address)

      const finalizeWithdrawalTx = await l1DaiGateway
        .connect(bridgeImpersonator)
        .finalizeInboundTransfer(l1Dai.address, user1.address, user1.address, withdrawAmount, defaultWithdrawData)

      expect(await l1Dai.balanceOf(user1.address)).to.be.equal(withdrawAmount)
      expect(await l1Dai.balanceOf(l1EscrowEOA.address)).to.be.equal(initialTotalL1Supply - withdrawAmount)
      await expect(finalizeWithdrawalTx)
        .to.emit(l1DaiGateway, 'WithdrawalFinalized')
        .withArgs(l1Dai.address, user1.address, user1.address, expectedTransferId, withdrawAmount)
      //   await expect(finalizeWithdrawalTx).not.to.emit(l1DaiGateway, 'TransferAndCallTriggered')
    })

    it('sends funds from the escrow to the 3rd party', async () => {
      const [
        _deployer,
        inboxImpersonator,
        l1EscrowEOA,
        l2DaiGatewayEOA,
        routerEOA,
        bridgeImpersonator,
        outboxImpersonator,
        sender,
        receiver,
      ] = await ethers.getSigners()
      const { l1Dai, outboxMock, l1DaiGateway } = await setupWithdrawalTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1: sender,
        bridgeImpersonator,
        outboxImpersonator,
      })
      outboxMock.smocked.l2ToL1Sender.will.return.with(() => l2DaiGatewayEOA.address)

      const finalizeWithdrawalTx = await l1DaiGateway
        .connect(bridgeImpersonator)
        .finalizeInboundTransfer(l1Dai.address, sender.address, receiver.address, withdrawAmount, defaultWithdrawData)

      expect(await l1Dai.balanceOf(sender.address)).to.be.equal(0)
      expect(await l1Dai.balanceOf(receiver.address)).to.be.equal(withdrawAmount)
      expect(await l1Dai.balanceOf(l1EscrowEOA.address)).to.be.equal(initialTotalL1Supply - withdrawAmount)
      await expect(finalizeWithdrawalTx)
        .to.emit(l1DaiGateway, 'WithdrawalFinalized')
        .withArgs(l1Dai.address, sender.address, receiver.address, expectedTransferId, withdrawAmount)
      //   await expect(finalizeWithdrawalTx).not.to.emit(l1DaiGateway, 'TransferAndCallTriggered')
    })

    // todo: test revert when calldata !=  0

    // pending withdrawals MUST success even if bridge is closed
    it('completes withdrawals even when closed', async () => {
      const [
        deployer,
        inboxImpersonator,
        l1EscrowEOA,
        l2DaiGatewayEOA,
        routerEOA,
        bridgeImpersonator,
        outboxImpersonator,
        user1,
      ] = await ethers.getSigners()
      const { l1Dai, outboxMock, l1DaiGateway } = await setupWithdrawalTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
        bridgeImpersonator,
        outboxImpersonator,
      })
      outboxMock.smocked.l2ToL1Sender.will.return.with(() => l2DaiGatewayEOA.address)
      await l1DaiGateway.connect(deployer).close()

      const finalizeWithdrawalTx = await l1DaiGateway
        .connect(bridgeImpersonator)
        .finalizeInboundTransfer(l1Dai.address, user1.address, user1.address, withdrawAmount, defaultWithdrawData)

      expect(await l1Dai.balanceOf(user1.address)).to.be.equal(withdrawAmount)
      expect(await l1Dai.balanceOf(l1EscrowEOA.address)).to.be.equal(initialTotalL1Supply - withdrawAmount)
      await expect(finalizeWithdrawalTx)
        .to.emit(l1DaiGateway, 'WithdrawalFinalized')
        .withArgs(l1Dai.address, user1.address, user1.address, expectedTransferId, withdrawAmount)
    })

    it('reverts when called with a different token', async () => {
      const [
        _deployer,
        inboxImpersonator,
        l1EscrowEOA,
        l2DaiGatewayEOA,
        routerEOA,
        bridgeImpersonator,
        outboxImpersonator,
        user1,
      ] = await ethers.getSigners()
      const { l2Dai, outboxMock, l1DaiGateway } = await setupWithdrawalTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
        bridgeImpersonator,
        outboxImpersonator,
      })
      outboxMock.smocked.l2ToL1Sender.will.return.with(() => l2DaiGatewayEOA.address)

      await expect(
        l1DaiGateway
          .connect(bridgeImpersonator)
          .finalizeInboundTransfer(l2Dai.address, user1.address, user1.address, withdrawAmount, defaultWithdrawData),
      ).to.be.revertedWith(errorMessages.tokenMismatch)
    })

    it('reverts when called not by the outbox', async () => {
      const [
        _deployer,
        inboxImpersonator,
        l1EscrowEOA,
        l2DaiGatewayEOA,
        routerEOA,
        bridgeImpersonator,
        outboxImpersonator,
        user1,
      ] = await ethers.getSigners()
      const { l1Dai, outboxMock, l1DaiGateway } = await setupWithdrawalTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
        bridgeImpersonator,
        outboxImpersonator,
      })
      outboxMock.smocked.l2ToL1Sender.will.return.with(() => l2DaiGatewayEOA.address)

      await expect(
        l1DaiGateway.finalizeInboundTransfer(
          l1Dai.address,
          user1.address,
          user1.address,
          withdrawAmount,
          defaultWithdrawData,
        ),
      ).to.be.revertedWith(errorMessages.notFromBridge)
    })

    it('reverts when called by the outbox but not relying message from l2 counterpart', async () => {
      const [
        _deployer,
        inboxImpersonator,
        l1EscrowEOA,
        l2DaiGatewayEOA,
        routerEOA,
        bridgeImpersonator,
        outboxImpersonator,
        user1,
        user2,
      ] = await ethers.getSigners()
      const { l1Dai, outboxMock, l1DaiGateway } = await setupWithdrawalTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
        bridgeImpersonator,
        outboxImpersonator,
      })
      outboxMock.smocked.l2ToL1Sender.will.return.with(() => user2.address)

      await expect(
        l1DaiGateway
          .connect(bridgeImpersonator)
          .finalizeInboundTransfer(l1Dai.address, user1.address, user1.address, withdrawAmount, defaultWithdrawData),
      ).to.be.revertedWith(errorMessages.l2CounterpartMismatch)
    })
  })

  describe('close()', () => {
    it('can be called by owner', async () => {
      const [owner, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, user1] = await ethers.getSigners()
      const { l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
      })

      expect(await l1DaiGateway.isOpen()).to.be.eq(1)
      const closeTx = await l1DaiGateway.connect(owner).close()

      await expect(closeTx).to.emit(l1DaiGateway, 'Closed')

      expect(await l1DaiGateway.isOpen()).to.be.eq(0)
    })

    it('can be called multiple times by the owner but nothing changes', async () => {
      const [owner, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, user1] = await ethers.getSigners()
      const { l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
      })

      await l1DaiGateway.connect(owner).close()
      expect(await l1DaiGateway.isOpen()).to.be.eq(0)

      await l1DaiGateway.connect(owner).close()
      expect(await l1DaiGateway.isOpen()).to.be.eq(0)
    })

    it('reverts when called not by the owner', async () => {
      const [_deployer, inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, user1] = await ethers.getSigners()
      const { l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
      })

      await expect(l1DaiGateway.connect(user1).close()).to.be.revertedWith(errorMessages.notOwner)
    })
  })

  describe('calculateL2TokenAddress', () => {
    it('return l2Dai address when asked about dai', async () => {
      const [inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, user1] = await ethers.getSigners()
      const { l1DaiGateway, l1Dai, l2Dai } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
      })

      expect(await l1DaiGateway.calculateL2TokenAddress(l1Dai.address)).to.eq(l2Dai.address)
    })

    it('returns zero address for unknown tokens', async () => {
      const [inboxImpersonator, l1EscrowEOA, l2DaiGatewayEOA, routerEOA, user1] = await ethers.getSigners()
      const randomToken = await getRandomAddress()
      const { l1DaiGateway } = await setupTest({
        inboxImpersonator,
        l1Escrow: l1EscrowEOA,
        l2DaiGateway: l2DaiGatewayEOA,
        router: routerEOA,
        user1,
      })

      expect(await l1DaiGateway.calculateL2TokenAddress(randomToken)).to.eq(ethers.constants.AddressZero)
    })
  })

  describe('constructor', () => {
    it('assigns all variables properly', async () => {
      const [l2DaiGateway, l1Router, inbox, l1Dai, l2Dai, l1Escrow] = await getRandomAddresses()

      const l1DaiGateway = await simpleDeploy<L1DaiGateway__factory>('L1DaiGateway', [
        l2DaiGateway,
        l1Router,
        inbox,
        l1Dai,
        l2Dai,
        l1Escrow,
      ])

      expect(await l1DaiGateway.l2Counterpart()).to.be.eq(l2DaiGateway)
      expect(await l1DaiGateway.l1Router()).to.be.eq(l1Router)
      expect(await l1DaiGateway.inbox()).to.be.eq(inbox)
      expect(await l1DaiGateway.l1Dai()).to.be.eq(l1Dai)
      expect(await l1DaiGateway.l2Dai()).to.be.eq(l2Dai)
      expect(await l1DaiGateway.l1Escrow()).to.be.eq(l1Escrow)
      expect(await l1DaiGateway.isOpen()).to.be.eq(1)
    })
  })

  it('has correct public interface', async () => {
    await assertPublicMutableMethods('L1DaiGateway', [
      'finalizeInboundTransfer(address,address,address,uint256,bytes)', // withdraw
      'outboundTransfer(address,address,uint256,uint256,uint256,bytes)', // deposit
      'close()',
      'deny(address)',
      'rely(address)',
    ])

    await assertPublicNotMutableMethods('L1DaiGateway', [
      'calculateL2TokenAddress(address)',
      'getOutboundCalldata(address,address,address,uint256,bytes)',

      // storage variables:
      'inbox()',
      'isOpen()',
      'l1Dai()',
      'l1Escrow()',
      'l1Router()',
      'l2Counterpart()',
      'l2Dai()',
      'wards(address)',
      'counterpartGateway()',
    ])
  })

  testAuth({
    name: 'L1DaiGateway',
    getDeployArgs: async () => {
      const [l2Counterpart, l1Router, inbox, l1Dai, l2Dai, l1Escrow] = await getRandomAddresses()

      return [l2Counterpart, l1Router, inbox, l1Dai, l2Dai, l1Escrow]
    },
    authedMethods: [(c) => c.close()],
  })
})
Example #13
Source File: fliRebalanceViewer.spec.ts    From index-coop-smart-contracts with Apache License 2.0 4 votes vote down vote up
describe("FLIRebalanceViewer", async () => {

  let owner: Account;
  let deployer: DeployHelper;

  let setV2Setup: SetFixture;
  let uniswapV2Setup: UniswapFixture;
  let uniswapV3Setup: UniswapV3Fixture;

  let fliExtensionMock: FLIStrategyExtensionMock;
  let fliViewer: FLIRebalanceViewer;

  let uniswapV2ExchangeName: string;
  let uniswapV3ExchangeName: string;

  before(async () => {
    [ owner ] = await getAccounts();

    deployer = new DeployHelper(owner.wallet);

    setV2Setup = getSetFixture(owner.address);
    uniswapV2Setup = getUniswapFixture(owner.address);
    uniswapV3Setup = getUniswapV3Fixture(owner.address);

    await setV2Setup.initialize();
    await uniswapV2Setup.initialize(owner, setV2Setup.weth.address, setV2Setup.wbtc.address, setV2Setup.usdc.address, false);
    await uniswapV3Setup.initialize(owner, setV2Setup.weth, 2000, setV2Setup.wbtc, 35000, setV2Setup.dai);

    uniswapV2ExchangeName = "UniswapV2ExchangeAdapter";
    uniswapV3ExchangeName = "UniswapV3ExchangeAdapter";

    fliExtensionMock = await deployer.mocks.deployFLIStrategyExtensionMock();
    fliViewer = await deployer.viewers.deployFLIRebalanceViewer(
      fliExtensionMock.address,
      uniswapV3Setup.quoter.address,
      uniswapV2Setup.router.address,
      uniswapV3ExchangeName,
      uniswapV2ExchangeName
    );

    // Setup FLI extension mock
    const strategy: ContractSettings = {
      setToken: await getRandomAddress(),
      leverageModule: await getRandomAddress(),
      comptroller: await getRandomAddress(),
      collateralPriceOracle: await getRandomAddress(),
      borrowPriceOracle: await getRandomAddress(),
      targetCollateralCToken: await getRandomAddress(),
      targetBorrowCToken: await getRandomAddress(),
      collateralAsset: setV2Setup.weth.address,
      borrowAsset: setV2Setup.usdc.address,
      collateralDecimalAdjustment: BigNumber.from(10),
      borrowDecimalAdjustment: BigNumber.from(22),
    };

    const uniV2ExchangeSettings: ExchangeSettings = {
      twapMaxTradeSize: ether(100),
      incentivizedTwapMaxTradeSize: ether(100),
      exchangeLastTradeTimestamp: BigNumber.from(0),
      leverExchangeData: EMPTY_BYTES,
      deleverExchangeData: EMPTY_BYTES,
    };

    const uniV3LeverData = solidityPack(
      ["address", "uint24", "address"],
      [setV2Setup.usdc.address, BigNumber.from(3000), setV2Setup.weth.address]
    );
    const uniV3DeleverData = solidityPack(
      ["address", "uint24", "address"],
      [setV2Setup.weth.address, BigNumber.from(3000), setV2Setup.usdc.address]
    );
    const uniV3ExchangeSettings: ExchangeSettings = {
      twapMaxTradeSize: ether(100),
      incentivizedTwapMaxTradeSize: ether(100),
      exchangeLastTradeTimestamp: BigNumber.from(0),
      leverExchangeData: uniV3LeverData,
      deleverExchangeData: uniV3DeleverData,
    };

    await fliExtensionMock.setExchangeSettings(uniswapV2ExchangeName, uniV2ExchangeSettings);
    await fliExtensionMock.setExchangeSettings(uniswapV3ExchangeName, uniV3ExchangeSettings);
    await fliExtensionMock.setStrategy(strategy);
  });

  addSnapshotBeforeRestoreAfterEach();

  describe("#constructor", async () => {

    let subjectFLIStrategyExtension: Address;
    let subjectUniV3Quoter: Address;
    let subjectUniV2Router: Address;
    let subjectUniV3Name: string;
    let subjectUniV2Name: string;

    beforeEach(async () => {
      subjectFLIStrategyExtension = await getRandomAddress();
      subjectUniV3Quoter = await getRandomAddress();
      subjectUniV2Router =  await getRandomAddress();
      subjectUniV3Name = uniswapV3ExchangeName;
      subjectUniV2Name = uniswapV2ExchangeName;
    });

    async function subject(): Promise<FLIRebalanceViewer> {
      return deployer.viewers.deployFLIRebalanceViewer(
        subjectFLIStrategyExtension,
        subjectUniV3Quoter,
        subjectUniV2Router,
        subjectUniV3Name,
        subjectUniV2Name
      );
    }

    it("should set the correct state variables", async () => {
      const viewer = await subject();

      expect(await viewer.fliStrategyExtension()).to.eq(subjectFLIStrategyExtension);
      expect(await viewer.uniswapV3Quoter()).to.eq(subjectUniV3Quoter);
      expect(await viewer.uniswapV2Router()).to.eq(subjectUniV2Router);
      expect(await viewer.uniswapV3ExchangeName()).to.eq(subjectUniV3Name);
      expect(await viewer.uniswapV2ExchangeName()).to.eq(subjectUniV2Name);
    });
  });

  describe("#shouldRebalanceWithBound", async () => {

    let subjectMinLeverageRatio: BigNumber;
    let subjectMaxLeverageRatio: BigNumber;

    beforeEach(async () => {
      subjectMinLeverageRatio = ether(1.7);
      subjectMaxLeverageRatio = ether(2.3);

      const shouldRebalanceNames = [ uniswapV3ExchangeName, uniswapV2ExchangeName ];
      const shouldRebalanceEnums = [ 1, 1 ];

      await fliExtensionMock.setShouldRebalanceWithBounds(shouldRebalanceNames, shouldRebalanceEnums);

      await setV2Setup.weth.approve(uniswapV2Setup.router.address, MAX_UINT_256);
      await setV2Setup.usdc.approve(uniswapV2Setup.router.address, MAX_UINT_256);
      await setV2Setup.wbtc.approve(uniswapV2Setup.router.address, MAX_UINT_256);
      await setV2Setup.weth.approve(uniswapV3Setup.nftPositionManager.address, MAX_UINT_256);
      await setV2Setup.usdc.approve(uniswapV3Setup.nftPositionManager.address, MAX_UINT_256);
    });

    async function subject(): Promise<[string[], number[]]> {
      return await fliViewer.callStatic.shouldRebalanceWithBounds(subjectMinLeverageRatio, subjectMaxLeverageRatio);
    }

    context("when delevering", async () => {

      beforeEach(async () => {
        const chunkRebalanceSizes = [ ether(5), ether(3) ];
        const chunkRebalanceSellAsset = setV2Setup.weth.address;
        const chunkRebalanceBuyAsset = setV2Setup.usdc.address;

        await fliExtensionMock.setGetChunkRebalanceWithBounds(chunkRebalanceSizes, chunkRebalanceSellAsset, chunkRebalanceBuyAsset);
      });

      context("when Uniswap V3 will produce a better trade", async () => {

        beforeEach(async () => {
          await uniswapV2Setup.router.addLiquidity(
            setV2Setup.weth.address,
            setV2Setup.usdc.address,
            ether(100),
            usdc(200_000),
            0,
            0,
            owner.address,
            MAX_UINT_256
          );

          await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
          await uniswapV3Setup.addLiquidityWide(
            setV2Setup.weth,
            setV2Setup.usdc,
            3000,
            ether(1000),
            usdc(2_000_000),
            owner.address
          );
        });

        it("should set Uniswap V3 as the preferred exchange", async () => {
          const [ exchangeNames, rebalanceEnums ] = await subject();

          expect(exchangeNames[0]).to.eq(uniswapV3ExchangeName);
          expect(rebalanceEnums[0]).to.eq(1);

          expect(exchangeNames[1]).to.eq(uniswapV2ExchangeName);
          expect(rebalanceEnums[1]).to.eq(1);
        });
      });

      context("when Uniswap V2 will produce a better trade", async () => {

        beforeEach(async () => {
          await uniswapV2Setup.router.addLiquidity(
            setV2Setup.weth.address,
            setV2Setup.usdc.address,
            ether(1000),
            usdc(2_000_000),
            0,
            0,
            owner.address,
            MAX_UINT_256
          );

          await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
          await uniswapV3Setup.addLiquidityWide(
            setV2Setup.weth,
            setV2Setup.usdc,
            3000,
            ether(100),
            usdc(200_000),
            owner.address
          );
        });

        it("should set Uniswap V2 as the preferred exchange", async () => {
          const [ exchangeNames, rebalanceEnums ] = await subject();

          expect(exchangeNames[0]).to.eq(uniswapV2ExchangeName);
          expect(rebalanceEnums[0]).to.eq(1);

          expect(exchangeNames[1]).to.eq(uniswapV3ExchangeName);
          expect(rebalanceEnums[1]).to.eq(1);
        });
      });

      context("when Uniswap V3 should rebalance, but V2 should not", async () => {

        beforeEach(async () => {
          const shouldRebalanceNames = [ uniswapV3ExchangeName, uniswapV2ExchangeName ];
          const shouldRebalanceEnums = [ 1, 0 ];

          await fliExtensionMock.setShouldRebalanceWithBounds(shouldRebalanceNames, shouldRebalanceEnums);
        });

        context("when Uniswap V3 will produce a better trade", async () => {

          beforeEach(async () => {
            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.weth.address,
              setV2Setup.usdc.address,
              ether(100),
              usdc(200_000),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
            await uniswapV3Setup.addLiquidityWide(
              setV2Setup.weth,
              setV2Setup.usdc,
              3000,
              ether(1000),
              usdc(2_000_000),
              owner.address
            );
          });

          it("should set Uniswap V3 as the preferred exchange", async () => {
            const [ exchangeNames, rebalanceEnums ] = await subject();

            expect(exchangeNames[0]).to.eq(uniswapV3ExchangeName);
            expect(rebalanceEnums[0]).to.eq(1);

            expect(exchangeNames[1]).to.eq(uniswapV2ExchangeName);
            expect(rebalanceEnums[1]).to.eq(0);
          });
        });

        context("when Uniswap V2 will produce a better trade", async () => {

          beforeEach(async () => {
            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.weth.address,
              setV2Setup.usdc.address,
              ether(1000),
              usdc(2_000_000),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
            await uniswapV3Setup.addLiquidityWide(
              setV2Setup.weth,
              setV2Setup.usdc,
              3000,
              ether(100),
              usdc(200_000),
              owner.address
            );
          });

          it("should set Uniswap V3 as the preferred exchange", async () => {
            const [ exchangeNames, rebalanceEnums ] = await subject();

            expect(exchangeNames[0]).to.eq(uniswapV3ExchangeName);
            expect(rebalanceEnums[0]).to.eq(1);

            expect(exchangeNames[1]).to.eq(uniswapV2ExchangeName);
            expect(rebalanceEnums[1]).to.eq(0);
          });
        });
      });

      context("when Uniswap V2 should rebalance, but V3 should not", async () => {

        beforeEach(async () => {
          const shouldRebalanceNames = [ uniswapV3ExchangeName, uniswapV2ExchangeName ];
          const shouldRebalanceEnums = [ 0, 1 ];

          await fliExtensionMock.setShouldRebalanceWithBounds(shouldRebalanceNames, shouldRebalanceEnums);
        });

        context("when Uniswap V3 will produce a better trade", async () => {

          beforeEach(async () => {
            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.weth.address,
              setV2Setup.usdc.address,
              ether(100),
              usdc(200_000),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
            await uniswapV3Setup.addLiquidityWide(
              setV2Setup.weth,
              setV2Setup.usdc,
              3000,
              ether(1000),
              usdc(2_000_000),
              owner.address
            );
          });

          it("should set Uniswap V2 as the preferred exchange", async () => {
            const [ exchangeNames, rebalanceEnums ] = await subject();

            expect(exchangeNames[0]).to.eq(uniswapV2ExchangeName);
            expect(rebalanceEnums[0]).to.eq(1);

            expect(exchangeNames[1]).to.eq(uniswapV3ExchangeName);
            expect(rebalanceEnums[1]).to.eq(0);
          });
        });

        context("when Uniswap V2 will produce a better trade", async () => {

          beforeEach(async () => {
            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.weth.address,
              setV2Setup.usdc.address,
              ether(1000),
              usdc(2_000_000),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
            await uniswapV3Setup.addLiquidityWide(
              setV2Setup.weth,
              setV2Setup.usdc,
              3000,
              ether(100),
              usdc(200_000),
              owner.address
            );
          });

          it("should set Uniswap V2 as the preferred exchange", async () => {
            const [ exchangeNames, rebalanceEnums ] = await subject();

            expect(exchangeNames[0]).to.eq(uniswapV2ExchangeName);
            expect(rebalanceEnums[0]).to.eq(1);

            expect(exchangeNames[1]).to.eq(uniswapV3ExchangeName);
            expect(rebalanceEnums[1]).to.eq(0);
          });
        });
      });

      context("when the rebalance will execute a multi-hop trade for Uniswap V2", async () => {
        beforeEach(async () => {

          const leverExchangeData = defaultAbiCoder.encode(
            [ "address[]" ],
            [[setV2Setup.usdc.address, setV2Setup.wbtc.address, setV2Setup.weth.address]]
          );

          const deleverExchangeData = defaultAbiCoder.encode(
            [ "address[]" ],
            [[setV2Setup.weth.address, setV2Setup.wbtc.address, setV2Setup.usdc.address]]
          );

          const uniV2ExchangeSettings: ExchangeSettings = {
            twapMaxTradeSize: ether(100),
            incentivizedTwapMaxTradeSize: ether(100),
            exchangeLastTradeTimestamp: BigNumber.from(0),
            leverExchangeData: leverExchangeData,
            deleverExchangeData: deleverExchangeData,
          };

          await fliExtensionMock.setExchangeSettings(uniswapV2ExchangeName, uniV2ExchangeSettings);
        });

        context("when Uniswap V3 will produce a better trade", async () => {

          beforeEach(async () => {
            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.weth.address,
              setV2Setup.wbtc.address,
              ether(1000),
              bitcoin(20),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.usdc.address,
              setV2Setup.wbtc.address,
              usdc(100000),
              bitcoin(2),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
            await uniswapV3Setup.addLiquidityWide(
              setV2Setup.weth,
              setV2Setup.usdc,
              3000,
              ether(1000),
              usdc(2_000_000),
              owner.address
            );
          });

          it("should set Uniswap V3 as the preferred exchange", async () => {
            const [ exchangeNames, rebalanceEnums ] = await subject();

            expect(exchangeNames[0]).to.eq(uniswapV3ExchangeName);
            expect(rebalanceEnums[0]).to.eq(1);

            expect(exchangeNames[1]).to.eq(uniswapV2ExchangeName);
            expect(rebalanceEnums[1]).to.eq(1);
          });
        });

        context("when Uniswap V2 will produce a better trade", async () => {

          beforeEach(async () => {
            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.weth.address,
              setV2Setup.wbtc.address,
              ether(1000),
              bitcoin(20),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            await uniswapV2Setup.router.addLiquidity(
              setV2Setup.usdc.address,
              setV2Setup.wbtc.address,
              usdc(100000),
              bitcoin(2),
              0,
              0,
              owner.address,
              MAX_UINT_256
            );

            // very bad ETH-USDC rate
            await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
            await uniswapV3Setup.addLiquidityWide(
              setV2Setup.weth,
              setV2Setup.usdc,
              3000,
              ether(100),
              usdc(2000),
              owner.address
            );
          });

          it("should set Uniswap V2 as the preferred exchange", async () => {
            const [ exchangeNames, rebalanceEnums ] = await subject();

            expect(exchangeNames[0]).to.eq(uniswapV2ExchangeName);
            expect(rebalanceEnums[0]).to.eq(1);

            expect(exchangeNames[1]).to.eq(uniswapV3ExchangeName);
            expect(rebalanceEnums[1]).to.eq(1);
          });
        });
      });
    });

    context("when levering", async () => {

      beforeEach(async () => {
        const chunkRebalanceSizes = [ usdc(5000), usdc(3000) ];
        const chunkRebalanceSellAsset = setV2Setup.usdc.address;
        const chunkRebalanceBuyAsset = setV2Setup.weth.address;

        await fliExtensionMock.setGetChunkRebalanceWithBounds(chunkRebalanceSizes, chunkRebalanceSellAsset, chunkRebalanceBuyAsset);
      });

      context("when Uniswap V3 will produce a better trade", async () => {

        beforeEach(async () => {
          await uniswapV2Setup.router.addLiquidity(
            setV2Setup.weth.address,
            setV2Setup.usdc.address,
            ether(100),
            usdc(200_000),
            0,
            0,
            owner.address,
            MAX_UINT_256
          );

          await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
          await uniswapV3Setup.addLiquidityWide(
            setV2Setup.weth,
            setV2Setup.usdc,
            3000,
            ether(1000),
            usdc(2_000_000),
            owner.address
          );
        });

        it("should set Uniswap V3 as the preferred exchange", async () => {
          const [ exchangeNames, rebalanceEnums ] = await subject();

          expect(exchangeNames[0]).to.eq(uniswapV3ExchangeName);
          expect(rebalanceEnums[0]).to.eq(1);

          expect(exchangeNames[1]).to.eq(uniswapV2ExchangeName);
          expect(rebalanceEnums[1]).to.eq(1);
        });
      });

      context("when Uniswap V2 will produce a better trade", async () => {

        beforeEach(async () => {
          await uniswapV2Setup.router.addLiquidity(
            setV2Setup.weth.address,
            setV2Setup.usdc.address,
            ether(1000),
            usdc(2_000_000),
            0,
            0,
            owner.address,
            MAX_UINT_256
          );

          await uniswapV3Setup.createNewPair(setV2Setup.weth, setV2Setup.usdc, 3000, 2000);
          await uniswapV3Setup.addLiquidityWide(
            setV2Setup.weth,
            setV2Setup.usdc,
            3000,
            ether(100),
            usdc(200_000),
            owner.address
          );
        });

        it("should set Uniswap V2 as the preferred exchange", async () => {
          const [ exchangeNames, rebalanceEnums ] = await subject();

          expect(exchangeNames[0]).to.eq(uniswapV2ExchangeName);
          expect(rebalanceEnums[0]).to.eq(1);

          expect(exchangeNames[1]).to.eq(uniswapV3ExchangeName);
          expect(rebalanceEnums[1]).to.eq(1);
        });
      });
    });
  });
});
Example #14
Source File: ERC1155Exchange.test.ts    From shoyu with MIT License 4 votes vote down vote up
describe("ERC1155Exchange", () => {
    beforeEach(async () => {
        await ethers.provider.send("hardhat_reset", []);
    });
    function getWallets() {
        const alice = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/3"
        ).connect(ethers.provider);
        const bob = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/4"
        ).connect(ethers.provider);
        const carol = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/5"
        ).connect(ethers.provider);
        const dan = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/7"
        ).connect(ethers.provider);
        const erin = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/8"
        ).connect(ethers.provider);
        const frank = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/9"
        ).connect(ethers.provider);

        const proxy = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/10"
        ).connect(ethers.provider);

        return { alice, bob, carol, dan, erin, frank, proxy };
    }
    function fees(price: BigNumberish, protocol: number, operator: number, royalty: number): BigNumberish[] {
        assert.isBelow(protocol, 255);
        assert.isBelow(operator, 255);
        assert.isBelow(royalty, 255);

        const fee: BigNumberish[] = [];

        const p = BigNumber.from(price).mul(protocol).div(1000);
        const o = BigNumber.from(price).mul(operator).div(1000);
        const r = BigNumber.from(price).mul(royalty).div(1000);
        const seller = BigNumber.from(price).sub(p.add(o).add(r));

        fee.push(p);
        fee.push(o);
        fee.push(r);
        fee.push(seller);

        return fee;
    }
    function name(contract: NFT1155V0): string {
        return contract.address.toLowerCase();
    }
    async function checkEvent(contract: Contract, eventName: string, args?: any[]) {
        const events: any = await contract.queryFilter(contract.filters[eventName](), "latest");
        expect(events[0].event).to.be.equal(eventName);

        if (args !== undefined) {
            const length = events[0].args.length;
            expect(length).to.be.gt(0);
            for (let i = 0; i < length; i++) {
                assert.isTrue(args[i] == events[0].args[i]);
            }
        }
    }

    it("should be that initial paremeters are set properly", async () => {
        const { factory, erc1155Exchange } = await setupTest();

        expect(await erc1155Exchange.DOMAIN_SEPARATOR()).to.be.equal(
            await domainSeparator(ethers.provider, "ERC1155Exchange", erc1155Exchange.address)
        );

        expect(await erc1155Exchange.factory()).to.be.equal(factory.address);
    });

    it("should be that the cancel function works well", async () => {
        const { erc1155Exchange, erc1155Mock0, exchangeName, erc20Mock, fixedPriceSale } = await setupTest();

        const { alice, bob } = getWallets();

        await erc1155Mock0.mintBatch(alice.address, [0, 1, 2], [10, 10, 30], []);
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            bob,
            AddressZero,
            erc1155Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await expect(erc1155Exchange.connect(bob).cancel(askOrder0.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(erc1155Exchange.connect(alice).cancel(askOrder1.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        expect(await erc1155Exchange.connect(alice).cancel(askOrder0.order));

        expect(await erc1155Exchange.connect(bob).cancel(askOrder1.order));

        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            2,
            30,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        await erc20Mock.mint(bob.address, 10000);
        await erc20Mock.connect(bob).approve(erc1155Exchange.address, 10000);

        const fees0 = fees(11 * 50, 25, 5, 0);
        expect(await erc1155Mock0.balanceOf(alice.address, 2)).to.be.equal(30);
        expect(await erc1155Exchange.amountFilled(askOrder2.hash)).to.be.equal(0);
        await expect(() => bid2(erc1155Exchange, bob, askOrder2.order, 11, 50, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [bob, alice],
            [-550, fees0[3]]
        );
        expect(await erc1155Exchange.amountFilled(askOrder2.hash)).to.be.equal(11);
        expect(await erc1155Mock0.balanceOf(alice.address, 2)).to.be.equal(19);

        await expect(erc1155Exchange.connect(alice).cancel(askOrder2.order)).to.emit(erc1155Exchange, "Cancel");
    });

    it("should be that fees are transfered properly", async () => {
        const {
            factory,
            erc1155Exchange,
            erc1155Mock0,
            erc1155Mock1,
            exchangeName,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
            protocolVault,
            operationalVault,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc1155Mock0.mintBatch(alice.address, [0, 1, 2, 3], [100, 200, 300, 400], []);
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.connect(bob).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc1155Exchange.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            1,
            20,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            2,
            30,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline - 99,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [15000, currentTime])
        );

        const fees0 = fees(12345, 25, 5, 0);
        await expect(() => bid2(erc1155Exchange, carol, askOrder1.order, 1, 12345, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, alice],
            [-12345, fees0[0], fees0[1], fees0[3]]
        );

        await erc20Mock.connect(dan).approve(proxy.address, 10000000);
        const bidOrder2 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrder2.hash,
            dan,
            3,
            31313,
            dan.address,
            AddressZero
        );
        const fees1 = fees(31313 * 3, 25, 5, 0);
        await expect(bid1(erc1155Exchange, frank, askOrder2.order, bidOrder2.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await expect(() => bid1(erc1155Exchange, proxy, askOrder2.order, bidOrder2.order)).to.changeTokenBalances(
            erc20Mock,
            [dan, protocolVault, operationalVault, alice, frank, proxy],
            [-31313 * 3, fees1[0], fees1[1], fees1[3], 0, 0]
        );

        await factory.setProtocolFeeRecipient(erin.address);
        await factory.setOperationalFeeRecipient(frank.address);
        await factory.setOperationalFee(17);

        //erin 25/1000 frank 17/1000
        const fees2 = fees(15000, 25, 17, 0);
        await expect(() => bid2(erc1155Exchange, dan, askOrder3.order, 1, 15000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, alice, protocolVault, operationalVault],
            [-15000, fees2[0], fees2[1], fees2[3], 0, 0, 0]
        );

        await mine(100);
        await erc1155Mock1.mint(alice.address, 0, 10, []);
        await erc1155Mock1.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const askOrder4 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline + 1000,
            defaultAbiCoder.encode(["uint256", "uint256"], [11000, currentTime])
        );

        //erin 25/1000 frank 17/1000
        const fees4 = fees(11000, 25, 17, 0);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);
        await expect(() => bid2(erc1155Exchange, dan, askOrder4.order, 1, 11000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, alice, protocolVault, operationalVault],
            [-11000, fees4[0], fees4[1], fees4[3], 0, 0]
        );
    });

    it("should be that NFT1155 tokens can't be traded on ERC1155Exchange but the other ERC1155 tokens can", async () => {
        const {
            factory,
            nft1155,
            erc1155Exchange,
            erc1155Mock0,
            erc1155Mock1,
            erc1155Mock2,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol } = getWallets();
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(alice.address, [0, 1, 2, 3], [1, 2, 3, 4], carol.address, 10);
        const nft1155_0 = await getNFT1155(factory);

        await factory.deployNFT1155AndMintBatch(alice.address, [0, 1, 2, 3], [9, 8, 7, 6], carol.address, 10);
        const nft1155_1 = await getNFT1155(factory);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.connect(bob).approve(erc1155Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            nft1155_0.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            nft1155_1.address,
            2,
            2,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isFalse(await erc1155Exchange.canTrade(nft1155_0.address));
        assert.isFalse(await erc1155Exchange.canTrade(nft1155_1.address));

        await expect(bid2(erc1155Exchange, bob, askOrder0.order, 1, 50, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );
        await expect(bid2(erc1155Exchange, bob, askOrder1.order, 1, 50, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );

        await erc1155Mock0.mint(bob.address, 3, 1, []);
        await erc1155Mock1.mint(bob.address, 4, 11, []);
        await erc1155Mock2.mint(bob.address, 5, 111, []);
        await erc1155Mock0.connect(bob).setApprovalForAll(erc1155Exchange.address, true);
        await erc1155Mock1.connect(bob).setApprovalForAll(erc1155Exchange.address, true);
        await erc1155Mock2.connect(bob).setApprovalForAll(erc1155Exchange.address, true);

        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.connect(carol).approve(erc1155Exchange.address, 10000000);

        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            bob,
            AddressZero,
            erc1155Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder4 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            bob,
            AddressZero,
            erc1155Mock1.address,
            4,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder5 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            bob,
            AddressZero,
            erc1155Mock2.address,
            5,
            101,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isTrue(await erc1155Exchange.canTrade(erc1155Mock0.address));
        assert.isTrue(await erc1155Exchange.canTrade(erc1155Mock1.address));
        assert.isTrue(await erc1155Exchange.canTrade(erc1155Mock2.address));

        await bid2(erc1155Exchange, carol, askOrder3.order, 1, 50, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder3.hash, carol.address, 1, 50, carol.address, AddressZero]);
        await bid2(erc1155Exchange, carol, askOrder4.order, 7, 50, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder4.hash, carol.address, 7, 50, carol.address, AddressZero]);
        await bid2(erc1155Exchange, carol, askOrder5.order, 99, 50, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder5.hash, carol.address, 99, 50, carol.address, AddressZero]);
    });

    it("should be that unfullfilled orders can be bidden again but fullfilled orders can't be bidden again", async () => {
        const {
            erc1155Exchange,
            erc1155Mock0,
            exchangeName,
            erc20Mock,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc1155Mock0.mintBatch(alice.address, [1, 2, 3], [10, 20, 30], []);
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc1155Exchange.address, 10000000);

        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            1,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            2,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            3,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        await bid2(erc1155Exchange, carol, askOrder1.order, 9, 999, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder1.hash, carol.address, 9, 999, carol.address, AddressZero]);

        await bid2(erc1155Exchange, dan, askOrder2.order, 9, 100, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder2.hash, dan.address, 9, 100, dan.address, AddressZero]);

        await erc20Mock.connect(dan).approve(proxy.address, 10000);
        const bidOrder3 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrder3.hash,
            dan,
            9,
            100,
            dan.address,
            AddressZero
        );
        await bid1(erc1155Exchange, proxy, askOrder3.order, bidOrder3.order);
        await checkEvent(erc1155Exchange, "Claim", [askOrder3.hash, dan.address, 9, 100, dan.address, AddressZero]);

        await expect(bid2(erc1155Exchange, carol, askOrder1.order, 9, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );
        await expect(bid2(erc1155Exchange, dan, askOrder2.order, 9, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await bid2(erc1155Exchange, carol, askOrder1.order, 1, 990, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder1.hash, carol.address, 1, 990, carol.address, AddressZero]);

        await bid2(erc1155Exchange, dan, askOrder2.order, 1, 100, AddressZero);
        await checkEvent(erc1155Exchange, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);

        await expect(bid1(erc1155Exchange, proxy, askOrder3.order, bidOrder3.order)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await erc1155Mock0.connect(carol).safeTransferFrom(carol.address, alice.address, 1, 9, []);
        await expect(bid2(erc1155Exchange, carol, askOrder1.order, 9, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        const bidOrder3_ = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrder3.hash,
            dan,
            1,
            100,
            dan.address,
            AddressZero
        );
        await bid1(erc1155Exchange, proxy, askOrder3.order, bidOrder3_.order);
        await checkEvent(erc1155Exchange, "Claim", [askOrder3.hash, dan.address, 1, 100, dan.address, AddressZero]);
    });

    it("should be that bid(Orders.Ask memory askOrder, Orders.Bid memory bidOrder) function works well", async () => {
        const {
            erc1155Exchange,
            erc1155Mock0,
            exchangeName,
            erc20Mock,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, frank } = getWallets();

        await erc1155Mock0.mintBatch(
            alice.address,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
            []
        );
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(carol).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);

        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            1,
            5,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            2,
            3,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrder1.hash,
            carol,
            4,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrder2 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrder2.hash,
            dan,
            3,
            100,
            AddressZero,
            AddressZero
        );

        await bid1(erc1155Exchange, frank, askOrder1.order, bidOrder1.order);
        await checkEvent(erc1155Exchange, "Claim", [askOrder1.hash, carol.address, 4, 990, carol.address, AddressZero]);
        await bid1(erc1155Exchange, bob, askOrder2.order, bidOrder2.order);
        await checkEvent(erc1155Exchange, "Claim", [askOrder2.hash, dan.address, 3, 100, dan.address, AddressZero]);
    });

    it("should be that fees and nft go to recipients if they are set in orders", async () => {
        const {
            operationalVault,
            protocolVault,
            erc1155Exchange,
            erc1155Mock0,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, frank } = getWallets();

        await erc1155Mock0.mintBatch(alice.address, [0, 1], [10, 11], []);
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(bob).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            1,
            5,
            fixedPriceSale.address,
            erc20Mock.address,
            frank.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        const fees0 = fees(12345 * 3, 25, 5, 0);
        await expect(() => bid2(erc1155Exchange, carol, askOrder1.order, 3, 12345, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, frank, alice],
            [-12345 * 3, fees0[0], fees0[1], fees0[3], 0]
        );
        expect(await erc1155Mock0.balanceOf(carol.address, 1)).to.be.equal(0);
        expect(await erc1155Mock0.balanceOf(bob.address, 1)).to.be.equal(3);
    });

    it("should be that token implementing EIP2981 give royalty to recipient when auction is finished", async () => {
        const {
            deployer,
            operationalVault,
            protocolVault,
            erc1155Exchange,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol } = getWallets();

        const ERC1155RoyaltyMockContract = await ethers.getContractFactory("ERC1155RoyaltyMock");
        const erc1155RoyaltyMock0 = (await ERC1155RoyaltyMockContract.deploy()) as ERC1155RoyaltyMock;

        await erc1155RoyaltyMock0.mintBatch(alice.address, [8, 25], [22, 55], []);
        await erc1155RoyaltyMock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.connect(bob).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc1155Exchange.address, 10000000);

        expect((await erc1155RoyaltyMock0.royaltyInfo(8, 1000))[0]).to.be.equal(deployer.address);
        expect((await erc1155RoyaltyMock0.royaltyInfo(8, 1000))[1]).to.be.equal(10);
        expect((await erc1155RoyaltyMock0.royaltyInfo(25, 1000))[0]).to.be.equal(deployer.address);
        expect((await erc1155RoyaltyMock0.royaltyInfo(25, 1000))[1]).to.be.equal(100);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155RoyaltyMock0.address,
            8,
            20,
            fixedPriceSale.address,
            erc20Mock.address,
            alice.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        const fees0 = fees(12345 * 15, 25, 5, 10);
        await expect(() =>
            bid2(erc1155Exchange, carol, askOrder0.order, 15, 12345, carol.address)
        ).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, deployer, alice],
            [-12345 * 15, fees0[0], fees0[1], fees0[2], fees0[3]]
        );
        expect(await erc1155RoyaltyMock0.balanceOf(carol.address, 8)).to.be.equal(15);

        //protocol 25 operator 5 royalty 100
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155RoyaltyMock0.address,
            25,
            44,
            fixedPriceSale.address,
            erc20Mock.address,
            alice.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [54321, currentTime])
        );

        const fees1 = fees(54321 * 44, 25, 5, 100);
        await expect(() => bid2(erc1155Exchange, bob, askOrder1.order, 44, 54321, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [bob, protocolVault, operationalVault, deployer, alice],
            [-54321 * 44, fees1[0], fees1[1], fees1[2], fees1[3]]
        );
        expect(await erc1155RoyaltyMock0.balanceOf(bob.address, 25)).to.be.equal(44);
    });

    it("should be that bid and claim functions work properly with proxy", async () => {
        const {
            erc1155Exchange,
            erc1155Mock0,
            exchangeName,
            erc20Mock,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, dan, erin, frank, proxy } = getWallets();

        await erc1155Mock0.mintBatch(
            alice.address,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
            []
        );
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc1155Exchange.address, 10000000);

        const askOrderFwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            2,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            3,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            4,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            5,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderFwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderFwithP.hash,
            erin,
            3,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderFwithoutP.hash,
            erin,
            4,
            201,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc1155Exchange, bob, askOrderFwithP.order, bidOrderFwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(erc1155Exchange, proxy, askOrderFwithP.order, bidOrderFwithP.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderFwithP.hash,
            erin.address,
            3,
            200,
            erin.address,
            AddressZero,
        ]);

        await bid1(erc1155Exchange, bob, askOrderFwithoutP.order, bidOrderFwithoutP.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderFwithoutP.hash,
            erin.address,
            4,
            201,
            erin.address,
            AddressZero,
        ]);
        expect(await erc1155Mock0.balanceOf(erin.address, 2)).to.be.equal(3);
        expect(await erc1155Mock0.balanceOf(erin.address, 3)).to.be.equal(4);

        const bidOrderDwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderDwithP.hash,
            frank,
            2,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderDwithoutP.hash,
            frank,
            3,
            980,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc1155Exchange, bob, askOrderDwithP.order, bidOrderDwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(erc1155Exchange, proxy, askOrderDwithP.order, bidOrderDwithP.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderDwithP.hash,
            frank.address,
            2,
            990,
            frank.address,
            AddressZero,
        ]);

        await bid1(erc1155Exchange, bob, askOrderDwithoutP.order, bidOrderDwithoutP.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderDwithoutP.hash,
            frank.address,
            3,
            980,
            frank.address,
            AddressZero,
        ]);
        expect(await erc1155Mock0.balanceOf(frank.address, 4)).to.be.equal(2);
        expect(await erc1155Mock0.balanceOf(frank.address, 5)).to.be.equal(3);

        const askOrderFwithP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            6,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            7,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            8,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            AddressZero,
            erc1155Mock0.address,
            9,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        await mine(100);
        expect(await getBlockTimestamp()).gt(deadline);
        const bidOrderFwithP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderFwithP1.hash,
            erin,
            3,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderFwithoutP1.hash,
            erin,
            4,
            201,
            AddressZero,
            AddressZero
        );

        await bid1(erc1155Exchange, proxy, askOrderFwithP1.order, bidOrderFwithP1.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderFwithP1.hash,
            erin.address,
            3,
            200,
            erin.address,
            AddressZero,
        ]);
        await expect(bid1(erc1155Exchange, bob, askOrderFwithoutP1.order, bidOrderFwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        const bidOrderDwithP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderDwithP1.hash,
            frank,
            5,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderDwithoutP1.hash,
            frank,
            6,
            980,
            AddressZero,
            AddressZero
        );

        await bid1(erc1155Exchange, proxy, askOrderDwithP1.order, bidOrderDwithP1.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderDwithP1.hash,
            frank.address,
            5,
            990,
            frank.address,
            AddressZero,
        ]);
        await expect(bid1(erc1155Exchange, bob, askOrderDwithoutP1.order, bidOrderDwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    });

    it("should be that bid and claim functions work properly with _bidHashes", async () => {
        const {
            erc1155Exchange,
            erc1155Mock0,
            exchangeName,
            erc20Mock,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, carol, dan, erin, frank, proxy } = getWallets();

        await erc1155Mock0.mintBatch(
            alice.address,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
            []
        );
        await erc1155Mock0.connect(alice).setApprovalForAll(erc1155Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc1155Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc1155Exchange.address, 10000000);

        const askOrderF0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            2,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderF1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            3,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );
        const askOrderD0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            alice,
            proxy.address,
            erc1155Mock0.address,
            4,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderF0 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderF0.hash,
            erin,
            3,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderF1 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderF1.hash,
            erin,
            4,
            201,
            AddressZero,
            AddressZero
        );

        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            HashZero
        );
        await expect(
            erc1155Exchange.connect(proxy).updateApprovedBidHash(askOrderF0.hash, erin.address, bidOrderF0.hash)
        )
            .to.emit(erc1155Exchange, "UpdateApprovedBidHash")
            .withArgs(proxy.address, askOrderF0.hash, erin.address, bidOrderF0.hash);
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            bidOrderF0.hash
        );

        await expect(
            bid2(
                erc1155Exchange,
                erin,
                askOrderF0.order,
                bidOrderF0.order.amount,
                bidOrderF0.order.price,
                bidOrderF0.order.recipient
            )
        ).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(erc1155Exchange.connect(carol).claim(askOrderF0.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(erc1155Exchange, frank, askOrderF0.order, bidOrderF0.order); //frank can call
        await checkEvent(erc1155Exchange, "Claim", [askOrderF0.hash, erin.address, 3, 200, erin.address, AddressZero]);
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            HashZero
        );

        const bidOrderF1_ = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderF1.hash,
            erin,
            9,
            201,
            AddressZero,
            AddressZero
        );
        await erc1155Exchange.connect(erin).updateApprovedBidHash(askOrderF1.hash, erin.address, bidOrderF1_.hash); //make fake hash for abusing
        expect(await erc1155Exchange.approvedBidHash(erin.address, askOrderF1.hash, erin.address)).to.be.equal(
            bidOrderF1_.hash
        );
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderF1.hash, erin.address)).to.be.equal(
            HashZero
        );
        await expect(bid1(erc1155Exchange, erin, askOrderF1.order, bidOrderF1_.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        await mine(100);
        expect(await getBlockTimestamp()).to.be.gt(askOrderF1.order.deadline);
        await erc1155Exchange.connect(proxy).updateApprovedBidHash(askOrderF1.hash, erin.address, bidOrderF1.hash); //timeover but update available
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderF1.hash, erin.address)).to.be.equal(
            bidOrderF1.hash
        );

        const bidOrderF1__ = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderF1.hash,
            erin,
            3,
            201,
            AddressZero,
            AddressZero
        ); //change conditions after hash approved
        await expect(bid1(erc1155Exchange, erin, askOrderF1.order, bidOrderF1__.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        await bid1(erc1155Exchange, erin, askOrderF1.order, bidOrderF1.order);
        await checkEvent(erc1155Exchange, "Claim", [askOrderF1.hash, erin.address, 4, 201, erin.address, AddressZero]);
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderF1.hash, erin.address)).to.be.equal(
            HashZero
        );
        expect(await erc1155Mock0.balanceOf(erin.address, 2)).to.be.equal(3);
        expect(await erc1155Mock0.balanceOf(erin.address, 3)).to.be.equal(4);

        const bidOrderD0 = await signBid(
            ethers.provider,
            exchangeName,
            erc1155Exchange.address,
            askOrderD0.hash,
            frank,
            2,
            990,
            AddressZero,
            AddressZero
        );

        expect(await getBlockTimestamp()).to.be.gt(askOrderD0.order.deadline);
        await erc1155Exchange.connect(proxy).updateApprovedBidHash(askOrderD0.hash, frank.address, bidOrderD0.hash); //timeover but update available
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            bidOrderD0.hash
        );

        await bid1(erc1155Exchange, frank, askOrderD0.order, bidOrderD0.order);
        await checkEvent(erc1155Exchange, "Claim", [
            askOrderD0.hash,
            frank.address,
            2,
            990,
            frank.address,
            AddressZero,
        ]);
        expect(await erc1155Exchange.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            HashZero
        );
        expect(await erc1155Mock0.balanceOf(frank.address, 4)).to.be.equal(2);
    });
});
Example #15
Source File: ERC721Exchange.test.ts    From shoyu with MIT License 4 votes vote down vote up
describe("ERC721Exchange", () => {
    beforeEach(async () => {
        await ethers.provider.send("hardhat_reset", []);
    });
    function getWallets() {
        const alice = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/3"
        ).connect(ethers.provider);
        const bob = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/4"
        ).connect(ethers.provider);
        const carol = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/5"
        ).connect(ethers.provider);
        const dan = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/7"
        ).connect(ethers.provider);
        const erin = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/8"
        ).connect(ethers.provider);
        const frank = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/9"
        ).connect(ethers.provider);

        const proxy = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/10"
        ).connect(ethers.provider);

        return { alice, bob, carol, dan, erin, frank, proxy };
    }
    function fees(price: BigNumberish, protocol: number, operator: number, royalty: number): BigNumberish[] {
        assert.isBelow(protocol, 255);
        assert.isBelow(operator, 255);
        assert.isBelow(royalty, 255);

        const fee: BigNumberish[] = [];

        const p = BigNumber.from(price).mul(protocol).div(1000);
        const o = BigNumber.from(price).mul(operator).div(1000);
        const r = BigNumber.from(price).mul(royalty).div(1000);
        const seller = BigNumber.from(price).sub(p.add(o).add(r));

        fee.push(p);
        fee.push(o);
        fee.push(r);
        fee.push(seller);

        return fee;
    }
    async function checkEvent(contract: Contract, eventName: string, args?: any[]) {
        const events: any = await contract.queryFilter(contract.filters[eventName](), "latest");
        expect(events[0].event).to.be.equal(eventName);

        if (args !== undefined) {
            const length = events[0].args.length;
            expect(length).to.be.gt(0);
            for (let i = 0; i < length; i++) {
                assert.isTrue(args[i] == events[0].args[i]);
            }
        }
    }

    it("should be that initial paremeters are set properly", async () => {
        const { factory, erc721Exchange } = await setupTest();

        expect(await erc721Exchange.DOMAIN_SEPARATOR()).to.be.equal(
            await domainSeparator(ethers.provider, "ERC721Exchange", erc721Exchange.address)
        );

        expect(await erc721Exchange.factory()).to.be.equal(factory.address);
    });

    it("should be that the cancel function works well", async () => {
        const { erc721Exchange, erc721Mock0, exchangeName, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol } = getWallets();

        await erc721Mock0.safeMint(alice.address, 0, []);
        await erc721Mock0.safeMint(bob.address, 1, []);
        await erc721Mock0.safeMint(carol.address, 2, []);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await expect(erc721Exchange.connect(bob).cancel(askOrder0.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(erc721Exchange.connect(alice).cancel(askOrder1.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        expect(await erc721Exchange.connect(alice).cancel(askOrder0.order));

        expect(await erc721Exchange.connect(bob).cancel(askOrder1.order));

        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            carol,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        await bid2(erc721Exchange, bob, askOrder2.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Bid", [askOrder2.hash, bob.address, 1, 100, AddressZero, AddressZero]);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(bob.address);

        await expect(erc721Exchange.connect(carol).cancel(askOrder2.order)).to.be.revertedWith("SHOYU: BID_EXISTS");
    });

    it("should be that the claim function can be called by anyone", async () => {
        const { erc721Exchange, erc721Mock0, exchangeName, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol, dan } = getWallets();

        await erc721Mock0.safeMintBatch0([alice.address, bob.address, alice.address], [0, 1, 2], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);
        await erc721Mock0.connect(bob).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);

        await erc20Mock.mint(carol.address, 10000);
        await erc20Mock.mint(dan.address, 10000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await bid2(erc721Exchange, carol, askOrder0.order, 1, 100, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(100);

        await bid2(erc721Exchange, dan, askOrder1.order, 1, 300, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder1.hash))[0]).to.be.equal(dan.address);
        expect((await erc721Exchange.bestBid(askOrder1.hash))[2]).to.be.equal(300);

        await bid2(erc721Exchange, dan, askOrder2.order, 1, 500, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(dan.address);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[2]).to.be.equal(500);

        await mine(100);
        assert.isTrue(deadline0 < (await getBlockTimestamp()));

        //nft0 : seller-Alice / buyer-Carol. Dan can claim.
        expect(await erc721Exchange.connect(dan).claim(askOrder0.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(carol.address);
        expect(await erc20Mock.balanceOf(carol.address)).to.be.equal(9900);

        //nft1 : seller-Bob / buyer-Dan.  Seller Bob can claim.
        expect(await erc721Exchange.connect(bob).claim(askOrder1.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(dan.address);
        expect(await erc20Mock.balanceOf(dan.address)).to.be.equal(9700);

        //nft2 : seller-Alice / buyer-Dan.  Buyer Dan can claim.
        expect(await erc721Exchange.connect(dan).claim(askOrder2.order)).to.emit(erc721Exchange, "Claim");
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        expect(await erc721Mock0.ownerOf(2)).to.be.equal(dan.address);
        expect(await erc20Mock.balanceOf(dan.address)).to.be.equal(9200);
    });

    it("should be that the claim function will be reverted if BestBid is not exist", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder1.hash))[0]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder3.hash))[0]).to.be.equal(AddressZero);

        await expect(erc721Exchange.claim(askOrder3.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(erc721Exchange.claim(askOrder2.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(erc721Exchange.claim(askOrder1.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(erc721Exchange.claim(askOrder0.order)).to.be.revertedWith("SHOYU: FAILURE");
        assert.isFalse(deadline0 < (await getBlockTimestamp()));
        assert.isFalse(await erc721Exchange.isCancelledOrClaimed(askOrder0.hash));
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);

        await mine(100);
        assert.isTrue(deadline0 < (await getBlockTimestamp()));
        await expect(erc721Exchange.claim(askOrder0.order)).to.be.revertedWith("SHOYU: FAILED_TO_TRANSFER_FUNDS");
        assert.isFalse(await erc721Exchange.isCancelledOrClaimed(askOrder0.hash));
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(alice.address);
    });

    it("should be that fees are transfered properly", async () => {
        const {
            factory,
            erc721Exchange,
            erc721Mock0,
            erc721Mock1,
            exchangeName,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
            protocolVault,
            operationalVault,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline - 90,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [15000, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        const fees0 = fees(12345, 25, 5, 0);
        await expect(() => bid2(erc721Exchange, carol, askOrder1.order, 1, 12345, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, alice],
            [-12345, fees0[0], fees0[1], fees0[3]]
        );

        await erc20Mock.connect(dan).approve(proxy.address, 10000000);
        const bidOrder2 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder2.hash,
            dan,
            1,
            31313,
            dan.address,
            AddressZero
        );
        const fees1 = fees(31313, 25, 5, 0);
        await expect(() => bid1(erc721Exchange, proxy, askOrder2.order, bidOrder2.order)).to.changeTokenBalances(
            erc20Mock,
            [dan, protocolVault, operationalVault, alice, frank, proxy],
            [-31313, fees1[0], fees1[1], fees1[3], 0, 0]
        );

        await factory.setProtocolFeeRecipient(erin.address);
        await factory.setOperationalFeeRecipient(frank.address);
        await factory.setOperationalFee(17);

        //erin 25/1000 frank 17/1000
        const fees2 = fees(15000, 25, 17, 0);
        await expect(() => bid2(erc721Exchange, dan, askOrder3.order, 1, 15000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, alice, protocolVault, operationalVault],
            [-15000, fees2[0], fees2[1], fees2[3], 0, 0, 0]
        );

        await mine(100);

        const fees3 = fees(100, 25, 17, 0);
        assert.isTrue(deadline < (await getBlockTimestamp()));
        await expect(() => erc721Exchange.claim(askOrder0.order)).to.changeTokenBalances(
            erc20Mock,
            [bob, erin, frank, alice, protocolVault, operationalVault],
            [-100, fees3[0], fees3[1], fees3[3], 0, 0, 0]
        );

        await erc721Mock1.safeMint(alice.address, 0, []);
        await erc721Mock1.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const askOrder4 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline + 1000,
            defaultAbiCoder.encode(["uint256", "uint256"], [11000, currentTime])
        );

        //erin 25/1000 frank 17/1000
        const fees4 = fees(11000, 25, 17, 0);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await expect(() => bid2(erc721Exchange, dan, askOrder4.order, 1, 11000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, alice, protocolVault, operationalVault],
            [-11000, fees4[0], fees4[1], fees4[3], 0, 0]
        );
    });

    it("should be that NFT721 tokens can't be traded on ERC721Exchange but the other ERC721 tokens can", async () => {
        const {
            factory,
            nft721,
            erc721Exchange,
            erc721Mock0,
            erc721Mock1,
            erc721Mock2,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol } = getWallets();
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2, 3], carol.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.deployNFT721AndMintBatch(alice.address, "Name2", "Symbol2", [0, 1, 2, 3], carol.address, 10);
        const nft721_1 = await getNFT721(factory);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            nft721_1.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isFalse(await erc721Exchange.canTrade(nft721_0.address));
        assert.isFalse(await erc721Exchange.canTrade(nft721_1.address));

        await expect(bid2(erc721Exchange, bob, askOrder0.order, 1, 50, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );
        await expect(bid2(erc721Exchange, bob, askOrder1.order, 1, 50, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );

        await erc721Mock0.safeMint(bob.address, 3, []);
        await erc721Mock1.safeMint(bob.address, 4, []);
        await erc721Mock2.safeMint(bob.address, 5, []);
        await erc721Mock0.connect(bob).setApprovalForAll(erc721Exchange.address, true);
        await erc721Mock1.connect(bob).setApprovalForAll(erc721Exchange.address, true);
        await erc721Mock2.connect(bob).setApprovalForAll(erc721Exchange.address, true);

        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);

        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder4 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock1.address,
            4,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder5 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            bob,
            AddressZero,
            erc721Mock2.address,
            5,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isTrue(await erc721Exchange.canTrade(erc721Mock0.address));
        assert.isTrue(await erc721Exchange.canTrade(erc721Mock1.address));
        assert.isTrue(await erc721Exchange.canTrade(erc721Mock2.address));

        await bid2(erc721Exchange, carol, askOrder3.order, 1, 50, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder3.hash, carol.address, 1, 50, carol.address, AddressZero]);
        await bid2(erc721Exchange, carol, askOrder4.order, 1, 50, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder4.hash, carol.address, 1, 50, carol.address, AddressZero]);
        await bid2(erc721Exchange, carol, askOrder5.order, 1, 50, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder5.hash, carol.address, 1, 50, carol.address, AddressZero]);
    });

    it("should be that claimed orders can't be used again even if it's back to the initial owner", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            3,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 5,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        await bid2(erc721Exchange, carol, askOrder1.order, 1, 999, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder1.hash, carol.address, 1, 999, carol.address, AddressZero]);

        await bid2(erc721Exchange, dan, askOrder2.order, 1, 100, AddressZero);
        await checkEvent(erc721Exchange, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);

        await erc20Mock.connect(dan).approve(proxy.address, 10000);
        const bidOrder3 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder3.hash,
            dan,
            1,
            101,
            dan.address,
            AddressZero
        );
        await bid1(erc721Exchange, proxy, askOrder3.order, bidOrder3.order);
        await checkEvent(erc721Exchange, "Claim", [askOrder3.hash, dan.address, 1, 101, dan.address, AddressZero]);

        await expect(bid2(erc721Exchange, carol, askOrder1.order, 1, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );
        await expect(bid2(erc721Exchange, dan, askOrder2.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        const bidOrder3_ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder3.hash,
            dan,
            1,
            101,
            dan.address,
            AddressZero
        );
        await expect(bid1(erc721Exchange, proxy, askOrder3.order, bidOrder3_.order)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await erc721Mock0.connect(carol).transferFrom(carol.address, alice.address, 1);
        await erc721Mock0.connect(dan).transferFrom(dan.address, alice.address, 2);
        await erc721Mock0.connect(dan).transferFrom(dan.address, alice.address, 3);

        await expect(bid2(erc721Exchange, carol, askOrder1.order, 1, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await expect(bid2(erc721Exchange, dan, askOrder2.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await expect(bid1(erc721Exchange, proxy, askOrder3.order, bidOrder3_.order)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await mine(100);
        await erc721Exchange.claim(askOrder0.order);

        await expect(bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        await erc721Mock0.connect(bob).transferFrom(bob.address, alice.address, 0);

        await expect(bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );
    });

    it("should be that BestBid is replaced if someone bid with higher price", async () => {
        const { erc721Exchange, erc721Mock0, exchangeName, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol, dan } = getWallets();

        await erc721Mock0.safeMint(alice.address, 0, []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, AddressZero);

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(bob.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(100);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(11);
        await bid2(erc721Exchange, carol, askOrder0.order, 1, 110, AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(110);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(11);
        await expect(bid2(erc721Exchange, dan, askOrder0.order, 1, 110, AddressZero)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await erc20Mock.mint(carol.address, 10000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000);

        await mine(100);
        expect(await erc721Exchange.claim(askOrder0.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(carol.address);
        expect(await erc20Mock.balanceOf(carol.address)).to.be.equal(10000 - 110);
    });

    it("should be that bid(Orders.Ask memory askOrder, Orders.Bid memory bidOrder) function works well", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            bob,
            1,
            101,
            AddressZero,
            AddressZero
        );
        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder1.hash,
            carol,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrder2 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder2.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, frank, askOrder1.order, bidOrder1.order);
        await checkEvent(erc721Exchange, "Claim", [askOrder1.hash, carol.address, 1, 990, carol.address, AddressZero]);
        await bid1(erc721Exchange, bob, askOrder2.order, bidOrder2.order);
        await checkEvent(erc721Exchange, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);
        await bid1(erc721Exchange, alice, askOrder0.order, bidOrder0.order);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 101, AddressZero, AddressZero]);

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(bob.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(101);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(15);

        const bidOrder0_ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            carol,
            1,
            111,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, alice, askOrder0.order, bidOrder0_.order);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, carol.address, 1, 111, AddressZero, AddressZero]);

        expect((await erc721Exchange.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[2]).to.be.equal(111);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await erc721Exchange.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());
    });

    it("should be that fees and nft go to recipients if they are set in orders", async () => {
        const {
            operationalVault,
            protocolVault,
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            erin.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            frank.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        await bid2(erc721Exchange, bob, askOrder0.order, 1, 100, dan.address);
        await checkEvent(erc721Exchange, "Bid", [askOrder0.hash, bob.address, 1, 100, dan.address, AddressZero]);

        const fees0 = fees(12345, 25, 5, 0);
        await expect(() => bid2(erc721Exchange, carol, askOrder1.order, 1, 12345, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, frank, alice],
            [-12345, fees0[0], fees0[1], fees0[3], 0]
        );
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(bob.address);

        await mine(100);

        const fees1 = fees(100, 25, 5, 0);
        await expect(() => erc721Exchange.claim(askOrder0.order)).to.changeTokenBalances(
            erc20Mock,
            [bob, protocolVault, operationalVault, erin, alice],
            [-100, fees1[0], fees1[1], fees1[3], 0]
        );
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(dan.address);
    });

    it("should be that token implementing EIP2981 give royalty to recipient when auction is finished", async () => {
        const {
            deployer,
            operationalVault,
            protocolVault,
            erc721Exchange,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol } = getWallets();

        const ERC721RoyaltyMockContract = await ethers.getContractFactory("ERC721RoyaltyMock");
        const erc721RoyaltyMock0 = (await ERC721RoyaltyMockContract.deploy()) as ERC721RoyaltyMock;

        await erc721RoyaltyMock0.safeMintBatch1(alice.address, [0, 1, 20], []);
        await erc721RoyaltyMock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.connect(bob).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(carol).approve(erc721Exchange.address, 10000000);

        expect((await erc721RoyaltyMock0.royaltyInfo(1, 1000))[0]).to.be.equal(deployer.address);
        expect((await erc721RoyaltyMock0.royaltyInfo(1, 1000))[1]).to.be.equal(10);
        expect((await erc721RoyaltyMock0.royaltyInfo(20, 1000))[0]).to.be.equal(deployer.address);
        expect((await erc721RoyaltyMock0.royaltyInfo(20, 1000))[1]).to.be.equal(100);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721RoyaltyMock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            alice.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        const fees0 = fees(12345, 25, 5, 10);
        await expect(() =>
            bid2(erc721Exchange, carol, askOrder0.order, 1, 12345, carol.address)
        ).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, deployer, alice],
            [-12345, fees0[0], fees0[1], fees0[2], fees0[3]]
        );
        expect(await erc721RoyaltyMock0.ownerOf(1)).to.be.equal(carol.address);

        //protocol 25 operator 5 royalty 100
        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721RoyaltyMock0.address,
            20,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            alice.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [54321, currentTime])
        );

        const fees1 = fees(54321, 25, 5, 100);
        await expect(() => bid2(erc721Exchange, bob, askOrder1.order, 1, 54321, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [bob, protocolVault, operationalVault, deployer, alice],
            [-54321, fees1[0], fees1[1], fees1[2], fees1[3]]
        );
        expect(await erc721RoyaltyMock0.ownerOf(20)).to.be.equal(bob.address);
    });

    it("should be that bid and claim functions work properly with proxy", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrderEwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrderEwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        const askOrderFwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            4,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            5,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderEwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderEwithP.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );
        const bidOrderEwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderEwithoutP.hash,
            dan,
            1,
            101,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, bob, askOrderEwithP.order, bidOrderEwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await expect(bid1(erc721Exchange, proxy, askOrderEwithP.order, bidOrderEwithP.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await bid1(erc721Exchange, bob, askOrderEwithoutP.order, bidOrderEwithoutP.order);
        await checkEvent(erc721Exchange, "Bid", [
            askOrderEwithoutP.hash,
            dan.address,
            1,
            101,
            AddressZero,
            AddressZero,
        ]);

        await mine(30);
        await expect(erc721Exchange.connect(carol).claim(askOrderEwithP.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(erc721Exchange, proxy, askOrderEwithP.order, bidOrderEwithP.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderEwithP.hash, dan.address, 1, 100, dan.address, AddressZero]);

        expect(await erc721Exchange.connect(carol).claim(askOrderEwithoutP.order)).to.emit(erc721Exchange, "Claim");
        expect(await erc721Mock0.ownerOf(0)).to.be.equal(dan.address);
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(dan.address);

        const bidOrderFwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithP.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithoutP.hash,
            erin,
            1,
            201,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, bob, askOrderFwithP.order, bidOrderFwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(erc721Exchange, proxy, askOrderFwithP.order, bidOrderFwithP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderFwithP.hash,
            erin.address,
            1,
            200,
            erin.address,
            AddressZero,
        ]);

        await bid1(erc721Exchange, bob, askOrderFwithoutP.order, bidOrderFwithoutP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderFwithoutP.hash,
            erin.address,
            1,
            201,
            erin.address,
            AddressZero,
        ]);
        expect(await erc721Mock0.ownerOf(2)).to.be.equal(erin.address);
        expect(await erc721Mock0.ownerOf(3)).to.be.equal(erin.address);

        const bidOrderDwithP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithP.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithoutP.hash,
            frank,
            1,
            980,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, bob, askOrderDwithP.order, bidOrderDwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(erc721Exchange, proxy, askOrderDwithP.order, bidOrderDwithP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderDwithP.hash,
            frank.address,
            1,
            990,
            frank.address,
            AddressZero,
        ]);

        await bid1(erc721Exchange, bob, askOrderDwithoutP.order, bidOrderDwithoutP.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderDwithoutP.hash,
            frank.address,
            1,
            980,
            frank.address,
            AddressZero,
        ]);

        expect(await erc721Mock0.ownerOf(4)).to.be.equal(frank.address);
        expect(await erc721Mock0.ownerOf(5)).to.be.equal(frank.address);

        const askOrderFwithP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            6,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            7,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            8,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            9,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        await mine(100);
        expect(await getBlockTimestamp()).gt(deadline);
        const bidOrderFwithP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithP1.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderFwithoutP1.hash,
            erin,
            1,
            201,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, proxy, askOrderFwithP1.order, bidOrderFwithP1.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderFwithP1.hash,
            erin.address,
            1,
            200,
            erin.address,
            AddressZero,
        ]);
        await expect(bid1(erc721Exchange, bob, askOrderFwithoutP1.order, bidOrderFwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        const bidOrderDwithP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithP1.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderDwithoutP1.hash,
            frank,
            1,
            980,
            AddressZero,
            AddressZero
        );

        await bid1(erc721Exchange, proxy, askOrderDwithP1.order, bidOrderDwithP1.order);
        await checkEvent(erc721Exchange, "Claim", [
            askOrderDwithP1.hash,
            frank.address,
            1,
            990,
            frank.address,
            AddressZero,
        ]);
        await expect(bid1(erc721Exchange, bob, askOrderDwithoutP1.order, bidOrderDwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    });

    it("should be that bid and claim functions work properly with _bidHashes", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, carol, dan, erin, frank, proxy } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(erin).approve(erc721Exchange.address, 10000000);
        await erc20Mock.connect(frank).approve(erc721Exchange.address, 10000000);

        const askOrderE0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrderE1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        const askOrderF0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );

        const askOrderD0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            proxy.address,
            erc721Mock0.address,
            4,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderE0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE0.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );
        const bidOrderE1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE1.hash,
            dan,
            1,
            101,
            AddressZero,
            AddressZero
        );

        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(HashZero);
        await expect(erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderE0.hash, dan.address, bidOrderE0.hash))
            .to.emit(erc721Exchange, "UpdateApprovedBidHash")
            .withArgs(proxy.address, askOrderE0.hash, dan.address, bidOrderE0.hash);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(
            bidOrderE0.hash
        );

        await expect(
            bid2(
                erc721Exchange,
                dan,
                askOrderE0.order,
                bidOrderE0.order.amount,
                bidOrderE0.order.price,
                bidOrderE0.order.recipient
            )
        ).to.be.revertedWith("SHOYU: FORBIDDEN");

        await mine(30);
        await expect(erc721Exchange.connect(carol).claim(askOrderE0.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(erc721Exchange, frank, askOrderE0.order, bidOrderE0.order); //frank can call
        await checkEvent(erc721Exchange, "Claim", [askOrderE0.hash, dan.address, 1, 100, dan.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(HashZero);

        const bidOrderE1_ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE1.hash,
            dan,
            1,
            70,
            AddressZero,
            AddressZero
        );
        await erc721Exchange.connect(dan).updateApprovedBidHash(askOrderE1.hash, dan.address, bidOrderE1_.hash); //make fake hash for abusing
        expect(await erc721Exchange.approvedBidHash(dan.address, askOrderE1.hash, dan.address)).to.be.equal(
            bidOrderE1_.hash
        );
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(HashZero);
        await expect(bid1(erc721Exchange, dan, askOrderE1.order, bidOrderE1_.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        expect(await getBlockTimestamp()).to.be.gt(askOrderE1.order.deadline);
        await erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderE1.hash, dan.address, bidOrderE1.hash); //timeover but update available
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(
            bidOrderE1.hash
        );

        const bidOrderE1__ = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderE1.hash,
            dan,
            1,
            70,
            AddressZero,
            AddressZero
        ); //change conditions after hash approved
        await expect(bid1(erc721Exchange, dan, askOrderE1.order, bidOrderE1__.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        await bid1(erc721Exchange, dan, askOrderE1.order, bidOrderE1.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderE1.hash, dan.address, 1, 101, dan.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(HashZero);
        expect(await erc721Mock0.ownerOf(1)).to.be.equal(dan.address);

        const bidOrderF0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderF0.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await mine(100);
        expect(await getBlockTimestamp()).to.be.gt(askOrderF0.order.deadline);
        await erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderF0.hash, erin.address, bidOrderF0.hash); //timeover but update available
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            bidOrderF0.hash
        );

        await bid1(erc721Exchange, erin, askOrderF0.order, bidOrderF0.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderF0.hash, erin.address, 1, 200, erin.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            HashZero
        );
        expect(await erc721Mock0.ownerOf(2)).to.be.equal(erin.address);

        const bidOrderD0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrderD0.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        expect(await getBlockTimestamp()).to.be.gt(askOrderD0.order.deadline);
        await erc721Exchange.connect(proxy).updateApprovedBidHash(askOrderD0.hash, frank.address, bidOrderD0.hash); //timeover but update available
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            bidOrderD0.hash
        );

        await bid1(erc721Exchange, frank, askOrderD0.order, bidOrderD0.order);
        await checkEvent(erc721Exchange, "Claim", [askOrderD0.hash, frank.address, 1, 990, frank.address, AddressZero]);
        expect(await erc721Exchange.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            HashZero
        );
        expect(await erc721Mock0.ownerOf(4)).to.be.equal(frank.address);
    });

    it("should be that an auction with fixed price sale allows bids only between startTime and deadline", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, dan } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 50])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 80])
        );

        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder1.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    })

    it("should be that an auction with english auction allows bids only between startTime and deadline", async () => {
        const {
            erc721Exchange,
            erc721Mock0,
            exchangeName,
            erc20Mock,
            englishAuction,
        } = await setupTest();

        const { alice, dan } = getWallets();

        await erc721Mock0.safeMintBatch1(alice.address, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], []);
        await erc721Mock0.connect(alice).setApprovalForAll(erc721Exchange.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(dan).approve(erc721Exchange.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 50])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder0.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        const askOrder1 = await signAsk(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            alice,
            AddressZero,
            erc721Mock0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime + 80])
        );

        const bidOrder1 = await signBid(
            ethers.provider,
            exchangeName,
            erc721Exchange.address,
            askOrder1.hash,
            dan,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await expect(bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await bid1(erc721Exchange, dan, askOrder0.order, bidOrder0.order);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await mine(50);
        await expect(bid1(erc721Exchange, dan, askOrder1.order, bidOrder1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    })
});
Example #16
Source File: NFT1155.test.ts    From shoyu with MIT License 4 votes vote down vote up
describe("Exchange part of NFT1155", () => {
    beforeEach(async () => {
        await ethers.provider.send("hardhat_reset", []);
    });
    function getWallets() {
        const alice = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/3"
        ).connect(ethers.provider);
        const bob = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/4"
        ).connect(ethers.provider);
        const carol = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/5"
        ).connect(ethers.provider);
        const dan = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/7"
        ).connect(ethers.provider);
        const erin = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/8"
        ).connect(ethers.provider);
        const frank = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/9"
        ).connect(ethers.provider);

        const proxy = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/10"
        ).connect(ethers.provider);

        return { alice, bob, carol, dan, erin, frank, proxy };
    }
    function fees(price: BigNumberish, protocol: number, operator: number, royalty: number): BigNumberish[] {
        assert.isBelow(protocol, 255);
        assert.isBelow(operator, 255);
        assert.isBelow(royalty, 255);

        const fee: BigNumberish[] = [];

        const p = BigNumber.from(price).mul(protocol).div(1000);
        const o = BigNumber.from(price).mul(operator).div(1000);
        const r = BigNumber.from(price).mul(royalty).div(1000);
        const seller = BigNumber.from(price).sub(p.add(o).add(r));

        fee.push(p);
        fee.push(o);
        fee.push(r);
        fee.push(seller);

        return fee;
    }
    function name(contract: NFT1155V3): string {
        return contract.address.toLowerCase();
    }
    async function checkEvent(contract: Contract, eventName: string, args: any[]) {
        const events = await contract.queryFilter(contract.filters[eventName](), "latest");
        expect(events[0]?.event).to.be.equal(eventName);

        if (args !== undefined) {
            const length = events[0].args.length;
            expect(length).to.be.gt(0);
            for (let i = 0; i < length; i++) {
                assert.isTrue(args[i] == events[0].args[i]);
            }
        }
    }

    it("should be that the cancel function works well", async () => {
        const { factory, nft1155, royaltyVault, erc20Mock, fixedPriceSale } = await setupTest();

        const { alice, bob, carol } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(alice.address, [0, 1, 2], [10, 20, 40], royaltyVault.address, 10);
        const nft1155_0 = await getNFT1155(factory);
        await nft1155_0.connect(alice).safeTransferFrom(alice.address, bob.address, 1, 10, []);
        await nft1155_0.connect(alice).safeTransferFrom(alice.address, carol.address, 2, 10, []);

        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;

        const askOrder0 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [0, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            bob,
            AddressZero,
            nft1155_0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [0, currentTime])
        );

        await expect(nft1155_0.connect(bob).cancel(askOrder0.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(nft1155_0.connect(alice).cancel(askOrder1.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await nft1155_0.connect(alice).cancel(askOrder0.order);

        await nft1155_0.connect(bob).cancel(askOrder1.order);

        const askOrder3 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            2,
            30,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        await erc20Mock.mint(bob.address, 10000);
        await erc20Mock.connect(bob).approve(nft1155_0.address, 10000);

        const fees0 = fees(11 * 50, 25, 5, 10);
        expect(await nft1155_0.balanceOf(alice.address, 2)).to.be.equal(30);
        expect(await nft1155_0.amountFilled(askOrder3.hash)).to.be.equal(0);
        await expect(() => bid2(nft1155_0, bob, askOrder3.order, 11, 50, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [bob, royaltyVault, alice],
            [-550, fees0[2], fees0[3]]
        );
        expect(await nft1155_0.amountFilled(askOrder3.hash)).to.be.equal(11);
        expect(await nft1155_0.balanceOf(alice.address, 2)).to.be.equal(19);

        await expect(nft1155_0.connect(alice).cancel(askOrder3.order)).to.emit(nft1155_0, "Cancel");
    });

    it("should be that fees are transfered properly", async () => {
        const {
            factory,
            operationalVault,
            protocolVault,
            nft1155,
            royaltyVault,
            erc20Mock,
            fixedPriceSale,
            englishAuction,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(
            alice.address,
            [0, 1, 2, 3],
            [100, 200, 300, 400],
            royaltyVault.address,
            10
        );
        const nft1155_0 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.connect(bob).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft1155_0.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const param1 = defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime]);
        const askOrder1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            1,
            20,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            param1
        );
        const param2 = defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime]);
        const askOrder2 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            2,
            30,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 5,
            param2
        );
        const param3 = defaultAbiCoder.encode(["uint256", "uint256"], [15000, currentTime]);
        const askOrder3 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            param3
        );

        const fees0 = fees(12345, 25, 5, 10);
        await expect(() => bid2(nft1155_0, carol, askOrder1.order, 1, 12345, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, royaltyVault, alice],
            [-12345, fees0[0], fees0[1], fees0[2], fees0[3]]
        );

        await erc20Mock.connect(dan).approve(proxy.address, 10000000);
        const bidOrder2 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrder2.hash,
            dan,
            1,
            31313,
            dan.address,
            AddressZero
        );
        const fees1 = fees(31313, 25, 5, 10);
        await expect(() => bid1(nft1155_0, proxy, askOrder2.order, bidOrder2.order)).to.changeTokenBalances(
            erc20Mock,
            [dan, protocolVault, operationalVault, royaltyVault, alice, frank, proxy],
            [-31313, fees1[0], fees1[1], fees1[2], fees1[3], 0, 0]
        );

        await factory.setProtocolFeeRecipient(erin.address);
        await factory.setOperationalFeeRecipient(frank.address);
        await factory.setOperationalFee(17);
        await nft1155_0.connect(alice).setRoyaltyFeeRecipient(carol.address);
        await nft1155_0.connect(alice).setRoyaltyFee(4);

        //erin 25/1000 frank 17/1000 carol 4/1000
        const fees2 = fees(15000, 25, 17, 4);
        await expect(() => bid2(nft1155_0, dan, askOrder3.order, 1, 15000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, carol, alice, protocolVault, operationalVault, royaltyVault],
            [-15000, fees2[0], fees2[1], fees2[2], fees2[3], 0, 0, 0]
        );

        await mine(100);

        await factory.deployNFT1155AndMintBatch(alice.address, [0], [10], royaltyVault.address, 0);
        const nft1155_1 = await getNFT1155(factory);

        const askOrder4 = await signAsk(
            ethers.provider,
            name(nft1155_1),
            nft1155_1.address,
            alice,
            AddressZero,
            nft1155_1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline + 1000,
            defaultAbiCoder.encode(["uint256", "uint256"], [11000, currentTime])
        );

        //erin 25/1000 frank 17/1000 royalty 0/1000
        const fees4 = fees(11000, 25, 17, 0);
        await erc20Mock.connect(dan).approve(nft1155_1.address, 10000000);
        await expect(() => bid2(nft1155_1, dan, askOrder4.order, 1, 11000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, carol, alice, protocolVault, operationalVault, royaltyVault],
            [-11000, fees4[0], fees4[1], fees4[2], fees4[3], 0, 0, 0]
        );
        expect(fees4[2]).to.be.equal(0);
    });

    it("should be that NFT tokens can be traded on itself not others", async () => {
        const { factory, nft1155, royaltyVault, erc1155Mock, erc20Mock, fixedPriceSale } = await setupTest();

        const { alice, bob } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(alice.address, [0, 1, 2, 3], [1, 2, 3, 4], royaltyVault.address, 10);
        const nft1155_0 = await getNFT1155(factory);

        await factory.deployNFT1155AndMintBatch(alice.address, [0, 1, 2, 3], [9, 8, 7, 6], royaltyVault.address, 10);
        const nft1155_1 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.connect(bob).approve(nft1155_0.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            erc1155Mock.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isFalse(await nft1155_0.canTrade(nft1155_1.address));
        assert.isFalse(await nft1155_0.canTrade(erc1155Mock.address));

        await expect(bid2(nft1155_0, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );

        await expect(bid2(nft1155_0, bob, askOrder1.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );
    });

    it("should be that unfullfilled orders can be bidden again but fullfilled orders can't be bidden again", async () => {
        const {
            factory,
            nft1155,
            royaltyVault,
            erc20Mock,
            dutchAuction,
            fixedPriceSale,
            englishAuction,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(alice.address, [1, 2, 3], [10, 20, 30], royaltyVault.address, 10);
        const nft1155_0 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        await factory.setStrategyWhitelisted(englishAuction.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft1155_0.address, 10000000);

        const askOrder1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            1,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            2,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            3,
            10,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 5,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        await bid2(nft1155_0, carol, askOrder1.order, 9, 990, AddressZero);
        await checkEvent(nft1155_0, "Claim", [askOrder1.hash, carol.address, 9, 990, carol.address, AddressZero]);

        await bid2(nft1155_0, dan, askOrder2.order, 9, 100, AddressZero);
        await checkEvent(nft1155_0, "Claim", [askOrder2.hash, dan.address, 9, 100, dan.address, AddressZero]);

        await erc20Mock.connect(dan).approve(proxy.address, 10000);
        const bidOrder3 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrder3.hash,
            dan,
            9,
            101,
            dan.address,
            AddressZero
        );
        await bid1(nft1155_0, proxy, askOrder3.order, bidOrder3.order);
        await checkEvent(nft1155_0, "Claim", [askOrder3.hash, dan.address, 9, 101, dan.address, AddressZero]);

        await expect(bid2(nft1155_0, carol, askOrder1.order, 9, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );
        await expect(bid2(nft1155_0, dan, askOrder2.order, 9, 100, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await bid2(nft1155_0, carol, askOrder1.order, 1, 990, AddressZero);
        await checkEvent(nft1155_0, "Claim", [askOrder1.hash, carol.address, 1, 990, carol.address, AddressZero]);

        await bid2(nft1155_0, dan, askOrder2.order, 1, 100, AddressZero);
        await checkEvent(nft1155_0, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);

        await expect(bid1(nft1155_0, proxy, askOrder3.order, bidOrder3.order)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await nft1155_0.connect(carol).safeTransferFrom(carol.address, alice.address, 1, 9, []);
        await expect(bid2(nft1155_0, carol, askOrder1.order, 9, 999, AddressZero)).to.be.revertedWith(
            "SHOYU: SOLD_OUT"
        );

        const bidOrder3_ = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrder3.hash,
            dan,
            1,
            131,
            dan.address,
            AddressZero
        );
        await bid1(nft1155_0, proxy, askOrder3.order, bidOrder3_.order);
        await checkEvent(nft1155_0, "Claim", [askOrder3.hash, dan.address, 1, 131, dan.address, AddressZero]);
    });

    it("should be that bid(Orders.Ask memory askOrder, Orders.Bid memory bidOrder) function works well", async () => {
        const { factory, nft1155, royaltyVault, erc20Mock, dutchAuction, fixedPriceSale } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(
            alice.address,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
            royaltyVault.address,
            10
        );
        const nft1155_0 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft1155_0.address, 10000000);

        const askOrder1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            1,
            5,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            2,
            3,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        await erc20Mock.connect(dan).approve(proxy.address, 1000);

        const bidOrder1 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrder1.hash,
            carol,
            4,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrder2 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrder2.hash,
            dan,
            3,
            100,
            AddressZero,
            AddressZero
        );

        await bid1(nft1155_0, bob, askOrder1.order, bidOrder1.order);
        await checkEvent(nft1155_0, "Claim", [askOrder1.hash, carol.address, 4, 990, carol.address, AddressZero]);

        await bid1(nft1155_0, alice, askOrder2.order, bidOrder2.order);
        await checkEvent(nft1155_0, "Claim", [askOrder2.hash, dan.address, 3, 100, dan.address, AddressZero]);
    });

    it("should be that fees and nft go to recipients if they are set in orders", async () => {
        const {
            factory,
            operationalVault,
            protocolVault,
            nft1155,
            royaltyVault,
            erc20Mock,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, frank } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(alice.address, [0, 1], [10, 11], royaltyVault.address, 10);
        const nft1155_0 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(bob).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft1155_0.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const fees0 = fees(12345 * 3, 25, 5, 10);
        const askOrder1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            1,
            5,
            fixedPriceSale.address,
            erc20Mock.address,
            frank.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        await expect(() => bid2(nft1155_0, carol, askOrder1.order, 3, 12345, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, royaltyVault, frank, alice],
            [-12345 * 3, fees0[0], fees0[1], fees0[2], fees0[3], 0]
        );
        expect(await nft1155_0.balanceOf(carol.address, 1)).to.be.equal(0);
        expect(await nft1155_0.balanceOf(bob.address, 1)).to.be.equal(3);
    });

    it("should be that bid and claim functions work properly with proxy", async () => {
        const { factory, nft1155, royaltyVault, erc20Mock, dutchAuction, fixedPriceSale } = await setupTest();

        const { alice, bob, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(
            alice.address,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
            royaltyVault.address,
            10
        );
        const nft1155_0 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft1155_0.address, 10000000);

        const askOrderFwithP = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            2,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            3,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            4,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            5,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderFwithP = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderFwithP.hash,
            erin,
            3,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderFwithoutP.hash,
            erin,
            4,
            201,
            AddressZero,
            AddressZero
        );

        await expect(bid1(nft1155_0, bob, askOrderFwithP.order, bidOrderFwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(nft1155_0, proxy, askOrderFwithP.order, bidOrderFwithP.order);
        await checkEvent(nft1155_0, "Claim", [askOrderFwithP.hash, erin.address, 3, 200, erin.address, AddressZero]);

        await bid1(nft1155_0, bob, askOrderFwithoutP.order, bidOrderFwithoutP.order);
        await checkEvent(nft1155_0, "Claim", [askOrderFwithoutP.hash, erin.address, 4, 201, erin.address, AddressZero]);
        expect(await nft1155_0.balanceOf(erin.address, 2)).to.be.equal(3);
        expect(await nft1155_0.balanceOf(erin.address, 3)).to.be.equal(4);

        const bidOrderDwithP = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderDwithP.hash,
            frank,
            2,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderDwithoutP.hash,
            frank,
            3,
            980,
            AddressZero,
            AddressZero
        );

        await expect(bid1(nft1155_0, bob, askOrderDwithP.order, bidOrderDwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(nft1155_0, proxy, askOrderDwithP.order, bidOrderDwithP.order);
        await checkEvent(nft1155_0, "Claim", [askOrderDwithP.hash, frank.address, 2, 990, frank.address, AddressZero]);

        await bid1(nft1155_0, bob, askOrderDwithoutP.order, bidOrderDwithoutP.order);
        await checkEvent(nft1155_0, "Claim", [
            askOrderDwithoutP.hash,
            frank.address,
            3,
            980,
            frank.address,
            AddressZero,
        ]);
        expect(await nft1155_0.balanceOf(frank.address, 4)).to.be.equal(2);
        expect(await nft1155_0.balanceOf(frank.address, 5)).to.be.equal(3);

        const askOrderFwithP1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            6,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            7,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            8,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            AddressZero,
            nft1155_0.address,
            9,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        await mine(100);
        expect(await getBlockTimestamp()).gt(deadline);
        const bidOrderFwithP1 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderFwithP1.hash,
            erin,
            3,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP1 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderFwithoutP1.hash,
            erin,
            4,
            201,
            AddressZero,
            AddressZero
        );

        await bid1(nft1155_0, proxy, askOrderFwithP1.order, bidOrderFwithP1.order);
        await checkEvent(nft1155_0, "Claim", [askOrderFwithP1.hash, erin.address, 3, 200, erin.address, AddressZero]);
        await expect(bid1(nft1155_0, bob, askOrderFwithoutP1.order, bidOrderFwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        const bidOrderDwithP1 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderDwithP1.hash,
            frank,
            5,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP1 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderDwithoutP1.hash,
            frank,
            6,
            980,
            AddressZero,
            AddressZero
        );

        await bid1(nft1155_0, proxy, askOrderDwithP1.order, bidOrderDwithP1.order);
        await checkEvent(nft1155_0, "Claim", [askOrderDwithP1.hash, frank.address, 5, 990, frank.address, AddressZero]);
        await expect(bid1(nft1155_0, bob, askOrderDwithoutP1.order, bidOrderDwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    });

    it("should be that bid and claim functions work properly with _bidHashes", async () => {
        const { factory, nft1155, royaltyVault, erc20Mock, dutchAuction, fixedPriceSale } = await setupTest();

        const { alice, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT1155(nft1155.address);

        await factory.deployNFT1155AndMintBatch(
            alice.address,
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
            royaltyVault.address,
            10
        );
        const nft1155_0 = await getNFT1155(factory);

        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft1155_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft1155_0.address, 10000000);

        const askOrderF0 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            2,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderF1 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            3,
            10,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderD0 = await signAsk(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            alice,
            proxy.address,
            nft1155_0.address,
            4,
            10,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderF0 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderF0.hash,
            erin,
            3,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderF1 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderF1.hash,
            erin,
            4,
            201,
            AddressZero,
            AddressZero
        );

        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(HashZero);
        await expect(nft1155_0.connect(proxy).updateApprovedBidHash(askOrderF0.hash, erin.address, bidOrderF0.hash))
            .to.emit(nft1155_0, "UpdateApprovedBidHash")
            .withArgs(proxy.address, askOrderF0.hash, erin.address, bidOrderF0.hash);
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            bidOrderF0.hash
        );

        await expect(
            bid2(
                nft1155_0,
                erin,
                askOrderF0.order,
                bidOrderF0.order.amount,
                bidOrderF0.order.price,
                bidOrderF0.order.recipient
            )
        ).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(nft1155_0.connect(carol).claim(askOrderF0.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(nft1155_0, frank, askOrderF0.order, bidOrderF0.order); //frank can call
        await checkEvent(nft1155_0, "Claim", [askOrderF0.hash, erin.address, 3, 200, erin.address, AddressZero]);
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(HashZero);

        const bidOrderF1_ = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderF1.hash,
            erin,
            9,
            201,
            AddressZero,
            AddressZero
        );
        await nft1155_0.connect(erin).updateApprovedBidHash(askOrderF1.hash, erin.address, bidOrderF1_.hash); //make fake hash for abusing
        expect(await nft1155_0.approvedBidHash(erin.address, askOrderF1.hash, erin.address)).to.be.equal(
            bidOrderF1_.hash
        );
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderF1.hash, erin.address)).to.be.equal(HashZero);
        await expect(bid1(nft1155_0, erin, askOrderF1.order, bidOrderF1_.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await mine(100);
        expect(await getBlockTimestamp()).to.be.gt(askOrderF1.order.deadline);
        await nft1155_0.connect(proxy).updateApprovedBidHash(askOrderF1.hash, erin.address, bidOrderF1.hash); //timeover but update available
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderF1.hash, erin.address)).to.be.equal(
            bidOrderF1.hash
        );

        const bidOrderF1__ = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderF1.hash,
            erin,
            3,
            201,
            AddressZero,
            AddressZero
        ); //change conditions after hash approved
        await expect(bid1(nft1155_0, erin, askOrderF1.order, bidOrderF1__.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );

        await bid1(nft1155_0, erin, askOrderF1.order, bidOrderF1.order);
        await checkEvent(nft1155_0, "Claim", [askOrderF1.hash, erin.address, 4, 201, erin.address, AddressZero]);
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderF1.hash, erin.address)).to.be.equal(HashZero);
        expect(await nft1155_0.balanceOf(erin.address, 2)).to.be.equal(3);
        expect(await nft1155_0.balanceOf(erin.address, 3)).to.be.equal(4);

        const bidOrderD0 = await signBid(
            ethers.provider,
            name(nft1155_0),
            nft1155_0.address,
            askOrderD0.hash,
            frank,
            2,
            990,
            AddressZero,
            AddressZero
        );

        expect(await getBlockTimestamp()).to.be.gt(askOrderD0.order.deadline);
        await nft1155_0.connect(proxy).updateApprovedBidHash(askOrderD0.hash, frank.address, bidOrderD0.hash); //timeover but update available
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            bidOrderD0.hash
        );

        await bid1(nft1155_0, frank, askOrderD0.order, bidOrderD0.order);
        await checkEvent(nft1155_0, "Claim", [askOrderD0.hash, frank.address, 2, 990, frank.address, AddressZero]);
        expect(await nft1155_0.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(HashZero);
        expect(await nft1155_0.balanceOf(frank.address, 4)).to.be.equal(2);
    });
});
Example #17
Source File: NFT721.test.ts    From shoyu with MIT License 4 votes vote down vote up
describe("Exchange part of NFT721", () => {
    beforeEach(async () => {
        await ethers.provider.send("hardhat_reset", []);
    });
    function getWallets() {
        const alice = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/3"
        ).connect(ethers.provider);
        const bob = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/4"
        ).connect(ethers.provider);
        const carol = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/5"
        ).connect(ethers.provider);
        const dan = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/7"
        ).connect(ethers.provider);
        const erin = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/8"
        ).connect(ethers.provider);
        const frank = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/9"
        ).connect(ethers.provider);

        const proxy = Wallet.fromMnemonic(
            "test test test test test test test test test test test junk",
            "m/44'/60'/0'/0/10"
        ).connect(ethers.provider);

        return { alice, bob, carol, dan, erin, frank, proxy };
    }
    function fees(price: BigNumberish, protocol: number, operator: number, royalty: number): BigNumberish[] {
        assert.isBelow(protocol, 255);
        assert.isBelow(operator, 255);
        assert.isBelow(royalty, 255);

        const fee: BigNumberish[] = [];

        const p = BigNumber.from(price).mul(protocol).div(1000);
        const o = BigNumber.from(price).mul(operator).div(1000);
        const r = BigNumber.from(price).mul(royalty).div(1000);
        const seller = BigNumber.from(price).sub(p.add(o).add(r));

        fee.push(p);
        fee.push(o);
        fee.push(r);
        fee.push(seller);

        return fee;
    }
    async function checkEvent(contract: Contract, eventName: string, args: any[]) {
        const events: any = await contract.queryFilter(contract.filters[eventName](), "latest");
        expect(events[0].event).to.be.equal(eventName);

        if (args !== undefined) {
            const length = events[0].args.length;
            expect(length).to.be.gt(0);
            for (let i = 0; i < length; i++) {
                assert.isTrue(args[i] == events[0].args[i]);
            }
        }
    }

    it("should be that the cancel function works well", async () => {
        const { factory, nft721, royaltyVault, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);
        await nft721_0.connect(alice).transferFrom(alice.address, bob.address, 1);
        await nft721_0.connect(alice).transferFrom(alice.address, carol.address, 2);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        expect(await nft721_0.ownerOf(0)).to.be.equal(alice.address);
        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [0, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            bob,
            AddressZero,
            nft721_0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [0, currentTime])
        );

        await expect(nft721_0.connect(bob).cancel(askOrder0.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await expect(nft721_0.connect(alice).cancel(askOrder1.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        expect(await nft721_0.connect(alice).cancel(askOrder0.order));

        expect(await nft721_0.connect(bob).cancel(askOrder1.order));

        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            carol,
            AddressZero,
            nft721_0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        expect((await nft721_0.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        await bid2(nft721_0, bob, askOrder2.order, 1, 100, AddressZero);

        expect((await nft721_0.bestBid(askOrder2.hash))[0]).to.be.equal(bob.address);

        await expect(nft721_0.connect(carol).cancel(askOrder2.order)).to.be.revertedWith("SHOYU: BID_EXISTS");
    });

    it("should be that the claim function can be called by anyone", async () => {
        const { factory, nft721, royaltyVault, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol, dan } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);
        await nft721_0.connect(alice).transferFrom(alice.address, bob.address, 1);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        expect(await nft721_0.ownerOf(0)).to.be.equal(alice.address);

        await erc20Mock.mint(carol.address, 10000);
        await erc20Mock.mint(dan.address, 10000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000);

        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            bob,
            AddressZero,
            nft721_0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await bid2(nft721_0, carol, askOrder0.order, 1, 100, AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await nft721_0.bestBid(askOrder0.hash))[2]).to.be.equal(100);

        await bid2(nft721_0, dan, askOrder1.order, 1, 300, AddressZero);
        expect((await nft721_0.bestBid(askOrder1.hash))[0]).to.be.equal(dan.address);
        expect((await nft721_0.bestBid(askOrder1.hash))[2]).to.be.equal(300);

        await bid2(nft721_0, dan, askOrder2.order, 1, 500, AddressZero);
        expect((await nft721_0.bestBid(askOrder2.hash))[0]).to.be.equal(dan.address);
        expect((await nft721_0.bestBid(askOrder2.hash))[2]).to.be.equal(500);

        await mine(100);
        assert.isTrue(deadline0 < (await getBlockTimestamp()));

        //nft0 : seller-Alice / buyer-Carol. Dan can claim.
        expect(await nft721_0.connect(dan).claim(askOrder0.order)).to.emit(nft721_0, "Claim");
        expect(await nft721_0.ownerOf(0)).to.be.equal(carol.address);
        expect(await erc20Mock.balanceOf(carol.address)).to.be.equal(9900);

        //nft1 : seller-Bob / buyer-Dan.  Seller Bob can claim.
        expect(await nft721_0.connect(bob).claim(askOrder1.order)).to.emit(nft721_0, "Claim");
        expect(await nft721_0.ownerOf(1)).to.be.equal(dan.address);
        expect(await erc20Mock.balanceOf(dan.address)).to.be.equal(9700);

        //nft2 : seller-Alice / buyer-Dan.  Buyer Dan can claim.
        expect(await nft721_0.connect(dan).claim(askOrder2.order)).to.emit(nft721_0, "Claim");
        expect((await nft721_0.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        expect(await nft721_0.ownerOf(2)).to.be.equal(dan.address);
        expect(await erc20Mock.balanceOf(dan.address)).to.be.equal(9200);
    });

    it("should be that the claim function will be reverted if BestBid is not exist", async () => {
        const {
            factory,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2, 3], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline0 = currentTime + 100;
        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            3,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline0,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        expect((await nft721_0.bestBid(askOrder0.hash))[0]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder1.hash))[0]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder2.hash))[0]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder3.hash))[0]).to.be.equal(AddressZero);

        await expect(nft721_0.claim(askOrder3.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(nft721_0.claim(askOrder2.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(nft721_0.claim(askOrder1.order)).to.be.revertedWith("SHOYU: FAILURE");

        await expect(nft721_0.claim(askOrder0.order)).to.be.revertedWith("SHOYU: FAILURE");
        assert.isFalse(deadline0 < (await getBlockTimestamp()));
        assert.isFalse(await nft721_0.isCancelledOrClaimed(askOrder0.hash));
        expect(await nft721_0.ownerOf(0)).to.be.equal(alice.address);

        await mine(100);
        assert.isTrue(deadline0 < (await getBlockTimestamp()));
        await expect(nft721_0.claim(askOrder0.order)).to.be.revertedWith("SHOYU: FAILED_TO_TRANSFER_FUNDS");
        assert.isFalse(await nft721_0.isCancelledOrClaimed(askOrder0.hash));
        expect(await nft721_0.ownerOf(0)).to.be.equal(alice.address);
    });

    it("should be that fees are transfered properly", async () => {
        const {
            factory,
            operationalVault,
            protocolVault,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2, 3], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.connect(bob).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft721_0.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            2,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 5,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [15000, currentTime])
        );

        await bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(nft721_0, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        const fees0 = fees(12345, 25, 5, 10);
        await expect(() => bid2(nft721_0, carol, askOrder1.order, 1, 12345, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, royaltyVault, alice],
            [-12345, fees0[0], fees0[1], fees0[2], fees0[3]]
        );

        await erc20Mock.connect(dan).approve(proxy.address, 10000000);
        const bidOrder2 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder2.hash,
            dan,
            1,
            31313,
            dan.address,
            AddressZero
        );
        const fees1 = fees(31313, 25, 5, 10);
        await expect(() => bid1(nft721_0, proxy, askOrder2.order, bidOrder2.order)).to.changeTokenBalances(
            erc20Mock,
            [dan, protocolVault, operationalVault, royaltyVault, alice, frank, proxy],
            [-31313, fees1[0], fees1[1], fees1[2], fees1[3], 0, 0]
        );

        await factory.setProtocolFeeRecipient(erin.address);
        await factory.setOperationalFeeRecipient(frank.address);
        await factory.setOperationalFee(17);
        await nft721_0.connect(alice).setRoyaltyFeeRecipient(carol.address);
        await nft721_0.connect(alice).setRoyaltyFee(4);

        //erin 25/1000 frank 17/1000 carol 4/1000
        const fees2 = fees(15000, 25, 17, 4);
        await expect(() => bid2(nft721_0, dan, askOrder3.order, 1, 15000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, carol, alice, protocolVault, operationalVault, royaltyVault],
            [-15000, fees2[0], fees2[1], fees2[2], fees2[3], 0, 0, 0]
        );

        await mine(100);

        const fees3 = fees(100, 25, 17, 4);
        assert.isTrue(deadline < (await getBlockTimestamp()));
        await expect(() => nft721_0.claim(askOrder0.order)).to.changeTokenBalances(
            erc20Mock,
            [bob, erin, frank, carol, alice, protocolVault, operationalVault, royaltyVault],
            [-100, fees3[0], fees3[1], fees3[2], fees3[3], 0, 0, 0]
        );

        await factory.deployNFT721AndMintBatch(alice.address, "Name2", "Symbol2", [0], royaltyVault.address, 0);
        const nft721_1 = await getNFT721(factory);

        const askOrder4 = await signAsk(
            ethers.provider,
            "Name2",
            nft721_1.address,
            alice,
            AddressZero,
            nft721_1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline + 1000,
            defaultAbiCoder.encode(["uint256", "uint256"], [11000, currentTime])
        );

        //erin 25/1000 frank 17/1000 royalty 0/1000
        const fees4 = fees(11000, 25, 17, 0);
        await erc20Mock.connect(dan).approve(nft721_1.address, 10000000);
        await expect(() => bid2(nft721_1, dan, askOrder4.order, 1, 11000, AddressZero)).to.changeTokenBalances(
            erc20Mock,
            [dan, erin, frank, carol, alice, protocolVault, operationalVault, royaltyVault],
            [-11000, fees4[0], fees4[1], fees4[2], fees4[3], 0, 0, 0]
        );
        expect(fees4[2]).to.be.equal(0);
    });

    it("should be that parked tokens are minted automatically when they are claimed", async () => {
        const {
            factory,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndPark(alice.address, "Name", "Symbol", 10, royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft721_0.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder4 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            10,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        expect(await nft721_0.parked(0)).to.be.true;
        expect(await nft721_0.parked(1)).to.be.true;
        expect(await nft721_0.parked(2)).to.be.true;
        expect(await nft721_0.parked(3)).to.be.true;
        expect(await nft721_0.parked(10)).to.be.false;

        await bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(nft721_0, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        expect(await nft721_0.ownerOf(1)).to.be.equal(AddressZero);
        await bid2(nft721_0, carol, askOrder1.order, 1, 999, AddressZero);
        expect(await nft721_0.ownerOf(1)).to.be.equal(carol.address);

        expect(await nft721_0.ownerOf(2)).to.be.equal(AddressZero);
        await bid2(nft721_0, dan, askOrder2.order, 1, 100, AddressZero);
        expect(await nft721_0.ownerOf(2)).to.be.equal(dan.address);

        expect(await nft721_0.ownerOf(3)).to.be.equal(AddressZero);
        await erc20Mock.connect(dan).approve(proxy.address, 1000);
        const bidOrder3 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder3.hash,
            dan,
            1,
            100,
            dan.address,
            AddressZero
        );
        await bid1(nft721_0, proxy, askOrder3.order, bidOrder3.order);
        expect(await nft721_0.ownerOf(3)).to.be.equal(dan.address);

        expect(await nft721_0.ownerOf(10)).to.be.equal(AddressZero);
        expect(await nft721_0.parked(10)).to.be.false;
        await expect(bid2(nft721_0, erin, askOrder4.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: TRANSFER_FORBIDDEN"
        );

        await mine(100);
        expect(await nft721_0.ownerOf(0)).to.be.equal(AddressZero);
        await nft721_0.claim(askOrder0.order);
        expect(await nft721_0.ownerOf(0)).to.be.equal(bob.address);

        assert.isFalse(await nft721_0.parked(0));
        assert.isFalse(await nft721_0.parked(1));
        assert.isFalse(await nft721_0.parked(2));
        assert.isFalse(await nft721_0.parked(3));

        assert.isTrue(await nft721_0.parked(4));
        assert.isTrue(await nft721_0.parked(5));
        assert.isTrue(await nft721_0.parked(6));
        assert.isTrue(await nft721_0.parked(7));
        assert.isTrue(await nft721_0.parked(8));
        assert.isTrue(await nft721_0.parked(9));

        assert.isFalse(await nft721_0.parked(10));
        assert.isFalse(await nft721_0.parked(11));
        assert.isFalse(await nft721_0.parked(12));
    });

    it("should be that NFT tokens can be traded on itself not others", async () => {
        const { factory, nft721, royaltyVault, erc721Mock, erc20Mock, fixedPriceSale } = await setupTest();

        const { alice, bob, carol } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2, 3], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.deployNFT721AndMintBatch(
            alice.address,
            "Name2",
            "Symbol2",
            [0, 1, 2, 3],
            royaltyVault.address,
            10
        );
        const nft721_1 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.connect(bob).approve(nft721_0.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_1.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            erc721Mock.address,
            0,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        assert.isFalse(await nft721_0.canTrade(nft721_1.address));
        assert.isFalse(await nft721_0.canTrade(erc721Mock.address));

        await expect(bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );

        await expect(bid2(nft721_0, bob, askOrder1.order, 1, 100, AddressZero)).to.be.revertedWith(
            "SHOYU: INVALID_EXCHANGE"
        );
    });

    it("should be that claimed orders can't be used again even if it's back to the initial owner", async () => {
        const {
            factory,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndPark(alice.address, "Name", "Symbol", 10, royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft721_0.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );
        const askOrder3 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        await bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero);
        await checkEvent(nft721_0, "Bid", [askOrder0.hash, bob.address, 1, 100, AddressZero, AddressZero]);

        await bid2(nft721_0, carol, askOrder1.order, 1, 999, AddressZero);

        await bid2(nft721_0, dan, askOrder2.order, 1, 100, AddressZero);

        await erc20Mock.connect(dan).approve(proxy.address, 10000);
        const bidOrder3 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder3.hash,
            dan,
            1,
            100,
            dan.address,
            AddressZero
        );
        await bid1(nft721_0, proxy, askOrder3.order, bidOrder3.order);
        await expect(bid2(nft721_0, carol, askOrder1.order, 1, 999, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await expect(bid2(nft721_0, dan, askOrder2.order, 1, 100, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");
        const bidOrder3_ = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder3.hash,
            dan,
            1,
            100,
            dan.address,
            AddressZero
        );
        await expect(bid1(nft721_0, proxy, askOrder3.order, bidOrder3_.order)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await nft721_0.connect(carol).transferFrom(carol.address, alice.address, 1);
        await nft721_0.connect(dan).transferFrom(dan.address, alice.address, 2);
        await nft721_0.connect(dan).transferFrom(dan.address, alice.address, 3);

        await expect(bid2(nft721_0, carol, askOrder1.order, 1, 999, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await expect(bid2(nft721_0, dan, askOrder2.order, 1, 100, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await expect(bid1(nft721_0, proxy, askOrder3.order, bidOrder3_.order)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await mine(100);
        await nft721_0.claim(askOrder0.order);

        await expect(bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");

        await nft721_0.connect(bob).transferFrom(bob.address, alice.address, 0);

        await expect(bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero)).to.be.revertedWith("SHOYU: SOLD_OUT");
    });

    it("should be that BestBid is replaced if someone bid with higher price", async () => {
        const { factory, nft721, royaltyVault, erc20Mock, englishAuction } = await setupTest();

        const { alice, bob, carol, dan } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);
        await nft721_0.connect(alice).transferFrom(alice.address, bob.address, 1);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        await bid2(nft721_0, bob, askOrder0.order, 1, 100, AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[0]).to.be.equal(bob.address);
        expect((await nft721_0.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await nft721_0.bestBid(askOrder0.hash))[2]).to.be.equal(100);
        expect((await nft721_0.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(11);
        await bid2(nft721_0, carol, askOrder0.order, 1, 110, AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await nft721_0.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await nft721_0.bestBid(askOrder0.hash))[2]).to.be.equal(110);
        expect((await nft721_0.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(11);
        await expect(bid2(nft721_0, dan, askOrder0.order, 1, 110, AddressZero)).to.be.revertedWith("SHOYU: FAILURE");

        await erc20Mock.mint(carol.address, 10000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000);

        await mine(100);
        expect(await nft721_0.claim(askOrder0.order)).to.emit(nft721_0, "Claim");
        expect(await nft721_0.ownerOf(0)).to.be.equal(carol.address);
        expect(await erc20Mock.balanceOf(carol.address)).to.be.equal(10000 - 110);
    });

    it("should be that bid(Orders.Ask memory askOrder, Orders.Bid memory bidOrder) function works well", async () => {
        const {
            factory,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndPark(alice.address, "Name", "Symbol", 10, royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(bob).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft721_0.address, 10000000);

        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrder2 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [100, currentTime])
        );

        const bidOrder0 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder0.hash,
            bob,
            1,
            101,
            AddressZero,
            AddressZero
        );
        const bidOrder1 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder1.hash,
            carol,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrder2 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder2.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );

        await bid1(nft721_0, alice, askOrder1.order, bidOrder1.order);
        await checkEvent(nft721_0, "Claim", [askOrder1.hash, carol.address, 1, 990, carol.address, AddressZero]);

        await bid1(nft721_0, alice, askOrder2.order, bidOrder2.order);
        await checkEvent(nft721_0, "Claim", [askOrder2.hash, dan.address, 1, 100, dan.address, AddressZero]);

        await bid1(nft721_0, alice, askOrder0.order, bidOrder0.order);
        expect((await nft721_0.bestBid(askOrder0.hash))[0]).to.be.equal(bob.address);
        expect((await nft721_0.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await nft721_0.bestBid(askOrder0.hash))[2]).to.be.equal(101);
        expect((await nft721_0.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());

        await mine(15);

        const bidOrder0_ = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrder0.hash,
            carol,
            1,
            111,
            AddressZero,
            AddressZero
        );

        await bid1(nft721_0, alice, askOrder0.order, bidOrder0_.order);
        expect((await nft721_0.bestBid(askOrder0.hash))[0]).to.be.equal(carol.address);
        expect((await nft721_0.bestBid(askOrder0.hash))[1]).to.be.equal(1);
        expect((await nft721_0.bestBid(askOrder0.hash))[2]).to.be.equal(111);
        expect((await nft721_0.bestBid(askOrder0.hash))[3]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[4]).to.be.equal(AddressZero);
        expect((await nft721_0.bestBid(askOrder0.hash))[5]).to.be.equal(await getBlockTimestamp());
    });

    it("should be that fees and nft go to recipients if they are set in orders", async () => {
        const {
            factory,
            operationalVault,
            protocolVault,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(alice.address, "Name", "Symbol", [0, 1, 2, 3], royaltyVault.address, 10);
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);
        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(bob.address, 10000000);
        await erc20Mock.mint(carol.address, 10000000);
        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.connect(bob).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(carol).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);

        //protocol 25 operator 5 royalty 10
        const askOrder0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            erin.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrder1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            frank.address,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [12345, currentTime])
        );

        await bid2(nft721_0, bob, askOrder0.order, 1, 100, dan.address);
        await checkEvent(nft721_0, "Bid", [askOrder0.hash, bob.address, 1, 100, dan.address, AddressZero]);
        const fees0 = fees(12345, 25, 5, 10);

        await expect(() => bid2(nft721_0, carol, askOrder1.order, 1, 12345, bob.address)).to.changeTokenBalances(
            erc20Mock,
            [carol, protocolVault, operationalVault, royaltyVault, frank, alice],
            [-12345, fees0[0], fees0[1], fees0[2], fees0[3], 0]
        );
        expect(await nft721_0.ownerOf(1)).to.be.equal(bob.address);

        await mine(100);

        const fees1 = fees(100, 25, 5, 10);
        await expect(() => nft721_0.claim(askOrder0.order)).to.changeTokenBalances(
            erc20Mock,
            [bob, protocolVault, operationalVault, royaltyVault, erin, alice],
            [-100, fees1[0], fees1[1], fees1[2], fees1[3], 0]
        );
        expect(await nft721_0.ownerOf(0)).to.be.equal(dan.address);
    });

    it("should be that bid and claim functions work properly with proxy", async () => {
        const {
            factory,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(
            alice.address,
            "Name",
            "Symbol",
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            royaltyVault.address,
            10
        );
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft721_0.address, 10000000);

        const askOrderEwithP = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrderEwithoutP = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        const askOrderFwithP = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            3,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            4,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            5,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderEwithP = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderEwithP.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );
        const bidOrderEwithoutP = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderEwithoutP.hash,
            dan,
            1,
            101,
            AddressZero,
            AddressZero
        );

        await expect(bid1(nft721_0, bob, askOrderEwithP.order, bidOrderEwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await expect(bid1(nft721_0, proxy, askOrderEwithP.order, bidOrderEwithP.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        await bid1(nft721_0, bob, askOrderEwithoutP.order, bidOrderEwithoutP.order);
        await checkEvent(nft721_0, "Bid", [askOrderEwithoutP.hash, dan.address, 1, 101, AddressZero, AddressZero]);

        await mine(30);
        await expect(nft721_0.connect(carol).claim(askOrderEwithP.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(nft721_0, proxy, askOrderEwithP.order, bidOrderEwithP.order);
        await checkEvent(nft721_0, "Claim", [askOrderEwithP.hash, dan.address, 1, 100, dan.address, AddressZero]);

        expect(await nft721_0.connect(carol).claim(askOrderEwithoutP.order)).to.emit(nft721_0, "Claim");
        expect(await nft721_0.ownerOf(0)).to.be.equal(dan.address);
        expect(await nft721_0.ownerOf(1)).to.be.equal(dan.address);

        const bidOrderFwithP = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderFwithP.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderFwithoutP.hash,
            erin,
            1,
            201,
            AddressZero,
            AddressZero
        );

        await expect(bid1(nft721_0, bob, askOrderFwithP.order, bidOrderFwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(nft721_0, proxy, askOrderFwithP.order, bidOrderFwithP.order);
        await checkEvent(nft721_0, "Claim", [askOrderFwithP.hash, erin.address, 1, 200, erin.address, AddressZero]);

        await bid1(nft721_0, bob, askOrderFwithoutP.order, bidOrderFwithoutP.order);
        await checkEvent(nft721_0, "Claim", [askOrderFwithoutP.hash, erin.address, 1, 201, erin.address, AddressZero]);
        expect(await nft721_0.ownerOf(2)).to.be.equal(erin.address);
        expect(await nft721_0.ownerOf(3)).to.be.equal(erin.address);

        const bidOrderDwithP = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderDwithP.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderDwithoutP.hash,
            frank,
            1,
            980,
            AddressZero,
            AddressZero
        );

        await expect(bid1(nft721_0, bob, askOrderDwithP.order, bidOrderDwithP.order)).to.be.revertedWith(
            "SHOYU: FORBIDDEN"
        );
        await bid1(nft721_0, proxy, askOrderDwithP.order, bidOrderDwithP.order);
        await checkEvent(nft721_0, "Claim", [askOrderDwithP.hash, frank.address, 1, 990, frank.address, AddressZero]);

        await bid1(nft721_0, bob, askOrderDwithoutP.order, bidOrderDwithoutP.order);
        await checkEvent(nft721_0, "Claim", [
            askOrderDwithoutP.hash,
            frank.address,
            1,
            980,
            frank.address,
            AddressZero,
        ]);

        expect(await nft721_0.ownerOf(4)).to.be.equal(frank.address);
        expect(await nft721_0.ownerOf(5)).to.be.equal(frank.address);

        const askOrderFwithP1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            6,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );
        const askOrderFwithoutP1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            7,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [201, currentTime])
        );

        const askOrderDwithP1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            8,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );
        const askOrderDwithoutP1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            AddressZero,
            nft721_0.address,
            9,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        await mine(100);
        expect(await getBlockTimestamp()).gt(deadline);
        const bidOrderFwithP1 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderFwithP1.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );
        const bidOrderFwithoutP1 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderFwithoutP1.hash,
            erin,
            1,
            201,
            AddressZero,
            AddressZero
        );

        await bid1(nft721_0, proxy, askOrderFwithP1.order, bidOrderFwithP1.order);
        await checkEvent(nft721_0, "Claim", [askOrderFwithP1.hash, erin.address, 1, 200, erin.address, AddressZero]);
        await expect(bid1(nft721_0, bob, askOrderFwithoutP1.order, bidOrderFwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );

        const bidOrderDwithP1 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderDwithP1.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        const bidOrderDwithoutP1 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderDwithoutP1.hash,
            frank,
            1,
            980,
            AddressZero,
            AddressZero
        );

        await bid1(nft721_0, proxy, askOrderDwithP1.order, bidOrderDwithP1.order);
        await checkEvent(nft721_0, "Claim", [askOrderDwithP1.hash, frank.address, 1, 990, frank.address, AddressZero]);
        await expect(bid1(nft721_0, bob, askOrderDwithoutP1.order, bidOrderDwithoutP1.order)).to.be.revertedWith(
            "SHOYU: FAILURE"
        );
    });

    it("should be that bid and claim functions work properly with _bidHashes", async () => {
        const {
            factory,
            nft721,
            royaltyVault,
            erc20Mock,
            englishAuction,
            dutchAuction,
            fixedPriceSale,
        } = await setupTest();

        const { alice, bob, carol, dan, erin, frank, proxy } = getWallets();

        await factory.setDeployerWhitelisted(AddressZero, true);
        await factory.upgradeNFT721(nft721.address);

        await factory.deployNFT721AndMintBatch(
            alice.address,
            "Name",
            "Symbol",
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            royaltyVault.address,
            10
        );
        const nft721_0 = await getNFT721(factory);

        await factory.setStrategyWhitelisted(englishAuction.address, true);
        await factory.setStrategyWhitelisted(dutchAuction.address, true);
        await factory.setStrategyWhitelisted(fixedPriceSale.address, true);

        const currentTime = await getBlockTimestamp();
        const deadline = currentTime + 100;

        await erc20Mock.mint(dan.address, 10000000);
        await erc20Mock.mint(erin.address, 10000000);
        await erc20Mock.mint(frank.address, 10000000);
        await erc20Mock.connect(dan).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(erin).approve(nft721_0.address, 10000000);
        await erc20Mock.connect(frank).approve(nft721_0.address, 10000000);

        const askOrderE0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            0,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );
        const askOrderE1 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            1,
            1,
            englishAuction.address,
            erc20Mock.address,
            AddressZero,
            currentTime + 30,
            defaultAbiCoder.encode(["uint256", "uint256"], [50, currentTime])
        );

        const askOrderF0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            2,
            1,
            fixedPriceSale.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256"], [200, currentTime])
        );

        const askOrderD0 = await signAsk(
            ethers.provider,
            "Name",
            nft721_0.address,
            alice,
            proxy.address,
            nft721_0.address,
            4,
            1,
            dutchAuction.address,
            erc20Mock.address,
            AddressZero,
            deadline,
            defaultAbiCoder.encode(["uint256", "uint256", "uint256"], [1000, 100, currentTime])
        );

        const bidOrderE0 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderE0.hash,
            dan,
            1,
            100,
            AddressZero,
            AddressZero
        );
        const bidOrderE1 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderE1.hash,
            dan,
            1,
            101,
            AddressZero,
            AddressZero
        );

        expect(await nft721_0.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(HashZero);
        await expect(nft721_0.connect(proxy).updateApprovedBidHash(askOrderE0.hash, dan.address, bidOrderE0.hash))
            .to.emit(nft721_0, "UpdateApprovedBidHash")
            .withArgs(proxy.address, askOrderE0.hash, dan.address, bidOrderE0.hash);
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(
            bidOrderE0.hash
        );

        await expect(
            bid2(
                nft721_0,
                dan,
                askOrderE0.order,
                bidOrderE0.order.amount,
                bidOrderE0.order.price,
                bidOrderE0.order.recipient
            )
        ).to.be.revertedWith("SHOYU: FORBIDDEN");

        await mine(30);
        await expect(nft721_0.connect(carol).claim(askOrderE0.order)).to.be.revertedWith("SHOYU: FAILURE");
        await bid1(nft721_0, frank, askOrderE0.order, bidOrderE0.order); //frank can call
        await checkEvent(nft721_0, "Claim", [askOrderE0.hash, dan.address, 1, 100, dan.address, AddressZero]);
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderE0.hash, dan.address)).to.be.equal(HashZero);

        const bidOrderE1_ = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderE1.hash,
            dan,
            1,
            70,
            AddressZero,
            AddressZero
        );
        await nft721_0.connect(dan).updateApprovedBidHash(askOrderE1.hash, dan.address, bidOrderE1_.hash); //make fake hash for abusing
        expect(await nft721_0.approvedBidHash(dan.address, askOrderE1.hash, dan.address)).to.be.equal(bidOrderE1_.hash);
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(HashZero);
        await expect(bid1(nft721_0, dan, askOrderE1.order, bidOrderE1_.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        expect(await getBlockTimestamp()).to.be.gt(askOrderE1.order.deadline);
        await nft721_0.connect(proxy).updateApprovedBidHash(askOrderE1.hash, dan.address, bidOrderE1.hash); //timeover but update available
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(
            bidOrderE1.hash
        );

        const bidOrderE1__ = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderE1.hash,
            dan,
            1,
            70,
            AddressZero,
            AddressZero
        ); //change conditions after hash approved
        await expect(bid1(nft721_0, dan, askOrderE1.order, bidOrderE1__.order)).to.be.revertedWith("SHOYU: FORBIDDEN");

        await bid1(nft721_0, dan, askOrderE1.order, bidOrderE1.order);
        await checkEvent(nft721_0, "Claim", [askOrderE1.hash, dan.address, 1, 101, dan.address, AddressZero]);
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderE1.hash, dan.address)).to.be.equal(HashZero);
        expect(await nft721_0.ownerOf(1)).to.be.equal(dan.address);

        const bidOrderF0 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderF0.hash,
            erin,
            1,
            200,
            AddressZero,
            AddressZero
        );

        await mine(100);
        expect(await getBlockTimestamp()).to.be.gt(askOrderF0.order.deadline);
        await nft721_0.connect(proxy).updateApprovedBidHash(askOrderF0.hash, erin.address, bidOrderF0.hash); //timeover but update available
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(
            bidOrderF0.hash
        );

        await bid1(nft721_0, erin, askOrderF0.order, bidOrderF0.order);
        await checkEvent(nft721_0, "Claim", [askOrderF0.hash, erin.address, 1, 200, erin.address, AddressZero]);
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderF0.hash, erin.address)).to.be.equal(HashZero);
        expect(await nft721_0.ownerOf(2)).to.be.equal(erin.address);

        const bidOrderD0 = await signBid(
            ethers.provider,
            "Name",
            nft721_0.address,
            askOrderD0.hash,
            frank,
            1,
            990,
            AddressZero,
            AddressZero
        );
        expect(await getBlockTimestamp()).to.be.gt(askOrderD0.order.deadline);
        await nft721_0.connect(proxy).updateApprovedBidHash(askOrderD0.hash, frank.address, bidOrderD0.hash); //timeover but update available
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(
            bidOrderD0.hash
        );

        await bid1(nft721_0, frank, askOrderD0.order, bidOrderD0.order);
        await checkEvent(nft721_0, "Claim", [askOrderD0.hash, frank.address, 1, 990, frank.address, AddressZero]);
        expect(await nft721_0.approvedBidHash(proxy.address, askOrderD0.hash, frank.address)).to.be.equal(HashZero);
        expect(await nft721_0.ownerOf(4)).to.be.equal(frank.address);
    });
});
Example #18
Source File: fliRebalanceKeeper.spec.ts    From index-coop-smart-contracts with Apache License 2.0 4 votes vote down vote up
describe("fliRebalanceKeeper", async () => {
  let owner: Account;
  let methodologist: Account;
  let registry: Account;
  let setV2Setup: SetFixture;
  let manager: BaseManager;
  let deployer: DeployHelper;

  let setToken: SetToken;
  let fliExtension: FlexibleLeverageStrategyExtensionMock;

  let subjectKeeper: FliRebalanceKeeper;
  let performData: string;

  let customMinLeverageRatio: number;
  let customMaxLeverageRatio: number;

  const exchangeName: string = "Uniswap";
  const exchangeIndex: number = 0;

  cacheBeforeEach(async () => {
    [owner, methodologist, registry] = await getAccounts();

    deployer = new DeployHelper(owner.wallet);

    setV2Setup = getSetFixture(owner.address);
    await setV2Setup.initialize();
    const daiUnits = BigNumber.from("23252699054621733");
    setToken = await setV2Setup.createSetToken(
      [setV2Setup.dai.address],
      [daiUnits],
      [setV2Setup.issuanceModule.address, setV2Setup.streamingFeeModule.address],
    );
    await setV2Setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO);

    // Deploy BaseManager
    manager = await deployer.manager.deployBaseManager(
      setToken.address,
      owner.address,
      methodologist.address,
    );
    // Transfer ownership to BaseManager
    await setToken.setManager(manager.address);

    customMinLeverageRatio = 1;
    customMaxLeverageRatio = 2;
  });

  addSnapshotBeforeRestoreAfterEach();

  const deployFliExtension = (currentLeverageRatio: number) => {
    return deployer.mocks.deployFlexibleLeverageStrategyExtensionMock(
      manager.address,
      currentLeverageRatio,
      exchangeName,
    );
  };

  const deploySubjectKeeper = async (fliExtension: Address): Promise<FliRebalanceKeeper> => {
    return deployer.keepers.deployFliRebalanceKeeper(
      fliExtension,
      registry.address,
      exchangeIndex,
      { customMinLeverageRatio, customMaxLeverageRatio },
    );
  };

  const setup = async (leverageRatio: number) => {
    fliExtension = await deployFliExtension(leverageRatio);
    subjectKeeper = await deploySubjectKeeper(fliExtension.address);
    await fliExtension.updateCallerStatus([subjectKeeper.address], [true]);
    performData = await getPerformData(leverageRatio);
  };

  const getPerformData = async (leverageRatio: number): Promise<string> => {
    switch (leverageRatio) {
      case 1:
        return defaultAbiCoder.encode(["uint256", "string"], [1, exchangeName]);
      case 2:
        return defaultAbiCoder.encode(["uint256", "string"], [2, exchangeName]);
      case 3:
        return defaultAbiCoder.encode(["uint256", "string"], [3, exchangeName]);
      default:
        return defaultAbiCoder.encode(["uint256", "string"], [0, exchangeName]);
    }
  };

  describe("#constructor", async () => {
    let subjectKeeper: FliRebalanceKeeper;

    async function subject(): Promise<FliRebalanceKeeper> {
      fliExtension = await deployFliExtension(1);
      return deployer.keepers.deployFliRebalanceKeeper(
        fliExtension.address,
        registry.address,
        exchangeIndex,
        { customMinLeverageRatio, customMaxLeverageRatio },
      );
    }

    it("should have the correct fliExtension address", async () => {
      subjectKeeper = await subject();
      expect(await subjectKeeper.fliExtension()).to.eq(fliExtension.address);
    });

    it("should have the correct registry address", async () => {
      subjectKeeper = await subject();
      expect(await subjectKeeper.registryAddress()).to.eq(registry.address);
    });

    it("should have the correct exchange index", async () => {
      subjectKeeper = await subject();
      expect(await subjectKeeper.exchangeIndex()).to.eq(exchangeIndex);
    });

    it("should have the correct leverage settings", async () => {
      subjectKeeper = await subject();
      const leverageSettings = await subjectKeeper.leverageSettings();
      expect(leverageSettings.customMinLeverageRatio).to.eq(customMinLeverageRatio);
      expect(leverageSettings.customMaxLeverageRatio).to.eq(customMaxLeverageRatio);
    });
  });

  describe("#checkUpkeep", async () => {
    context("when leverage ratio is 0", async () => {
      beforeEach(async () => {
        const leverageRatio = 0;
        await setup(leverageRatio);
      });

      async function subject(): Promise<[boolean, string]> {
        return subjectKeeper.connect(registry.wallet).callStatic.checkUpkeep(ZERO_BYTES);
      }

      it("should return false, encoded shouldRebalance and exchangeName", async () => {
        const expectedPerformData = defaultAbiCoder.encode(
          ["uint256", "string"],
          [0, exchangeName],
        );

        const response = await subject();

        expect(response[0]).to.be.false;
        expect(response[1]).to.eq(expectedPerformData);
      });
    });

    context("when leverage ratio is 1", async () => {
      beforeEach(async () => {
        const leverageRatio = 1;
        await setup(leverageRatio);
      });

      async function subject(): Promise<[boolean, string]> {
        return subjectKeeper.connect(registry.wallet).callStatic.checkUpkeep(ZERO_BYTES);
      }

      it("should return true, encoded shouldRebalance and exchangeName", async () => {
        const expectedPerformData = defaultAbiCoder.encode(
          ["uint256", "string"],
          [1, exchangeName],
        );

        const response = await subject();

        expect(response[0]).to.be.true;
        expect(response[1]).to.eq(expectedPerformData);
      });
    });

    context("when leverage ratio is 2", async () => {
      beforeEach(async () => {
        const leverageRatio = 2;
        await setup(leverageRatio);
      });

      async function subject(): Promise<[boolean, string]> {
        return subjectKeeper.connect(registry.wallet).callStatic.checkUpkeep(ZERO_BYTES);
      }

      it("should return true, encoded shouldRebalance and exchangeName", async () => {
        const expectedPerformData = defaultAbiCoder.encode(
          ["uint256", "string"],
          [2, exchangeName],
        );

        const response = await subject();

        expect(response[0]).to.be.true;
        expect(response[1]).to.eq(expectedPerformData);
      });
    });

    context("when leverage ratio is 3", async () => {
      beforeEach(async () => {
        const leverageRatio = 3;
        await setup(leverageRatio);
      });

      async function subject(): Promise<[boolean, string]> {
        return subjectKeeper.connect(registry.wallet).callStatic.checkUpkeep(ZERO_BYTES);
      }

      it("should return true, encoded shouldRebalance and exchangeName", async () => {
        const expectedPerformData = defaultAbiCoder.encode(
          ["uint256", "string"],
          [3, exchangeName],
        );

        const response = await subject();

        expect(response[0]).to.be.true;
        expect(response[1]).to.eq(expectedPerformData);
      });
    });
  });

  describe("#performUpkeep", async () => {
    context("when leverage ratio is 0", async () => {
      beforeEach(async () => {
        const leverageRatio = 0;
        await setup(leverageRatio);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper.connect(registry.wallet).performUpkeep(performData);
      }

      it("should revert", async () => {
        await expect(subject()).to.be.revertedWith(
          "FliRebalanceKeeper: invalid shouldRebalance or no rebalance required",
        );
      });
    });

    context("when leverage ratio is 1", async () => {
      beforeEach(async () => {
        const leverageRatio = 1;
        await setup(leverageRatio);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper.connect(registry.wallet).performUpkeep(performData);
      }

      it("should call rebalance on fliExtension and emit RebalanceEvent with arg of 1", async () => {
        await expect(subject()).to.emit(fliExtension, "RebalanceEvent");
      });
    });

    context("when leverage ratio is 2", async () => {
      beforeEach(async () => {
        const leverageRatio = 2;
        await setup(leverageRatio);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper.connect(registry.wallet).performUpkeep(performData);
      }

      it("should call iterateRebalance on fliExtension and emit RebalanceEvent with arg of 2", async () => {
        await expect(subject())
          .to.emit(fliExtension, "RebalanceEvent")
          .withArgs(2);
      });
    });

    context("when leverage ratio is 3", async () => {
      beforeEach(async () => {
        const leverageRatio = 3;
        await setup(leverageRatio);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper.connect(registry.wallet).performUpkeep(performData);
      }

      it("should call ripcord on fliExtension and emit RebalanceEvent with arg of 3", async () => {
        await expect(subject())
          .to.emit(fliExtension, "RebalanceEvent")
          .withArgs(3);
      });
    });
  });

  describe("#setExchangeIndex", async () => {
    context("when changing the exchange index to 1", async () => {
      beforeEach(async () => {
        await setup(1);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper.connect(owner.wallet).setExchangeIndex(1);
      }

      it("should set the exchange index", async () => {
        const beforeIndex = await subjectKeeper.exchangeIndex();
        expect(beforeIndex).to.eq(0);

        await subject();

        const afterIndex = await subjectKeeper.exchangeIndex();
        expect(afterIndex).to.eq(1);
      });
    });

    context("when caller is not the owner address", async () => {
      beforeEach(async () => {
        await setup(1);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper.connect(registry.wallet).setExchangeIndex(1);
      }

      it("should revert", async () => {
        await expect(subject()).to.be.revertedWith("Ownable: caller is not the owner");
      });
    });
  });

  describe("#setLeverageSettings", async () => {
    context("when setting LeverageSettings bounds to min = 2 and max = 3", async () => {
      beforeEach(async () => {
        await setup(1);
        customMinLeverageRatio = 2;
        customMaxLeverageRatio = 3;
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper
          .connect(owner.wallet)
          .setLeverageSettings({ customMinLeverageRatio, customMaxLeverageRatio });
      }

      it("should set the leverage settings", async () => {
        const beforeLeverageSettings = await subjectKeeper.leverageSettings();
        expect(beforeLeverageSettings.customMinLeverageRatio).to.eq(1);
        expect(beforeLeverageSettings.customMaxLeverageRatio).to.eq(2);

        await subject();

        const afterLeverageSettings = await subjectKeeper.leverageSettings();
        expect(afterLeverageSettings.customMinLeverageRatio).to.eq(customMinLeverageRatio);
        expect(afterLeverageSettings.customMaxLeverageRatio).to.eq(customMaxLeverageRatio);
      });
    });

    context("when caller is not the owner address", async () => {
      beforeEach(async () => {
        await setup(1);
      });

      async function subject(): Promise<ContractTransaction> {
        return subjectKeeper
          .connect(registry.wallet)
          .setLeverageSettings({ customMinLeverageRatio, customMaxLeverageRatio });
      }

      it("should revert", async () => {
        await expect(subject()).to.be.revertedWith("Ownable: caller is not the owner");
      });
    });
  });
});
Example #19
Source File: fliIntegration.spec.ts    From index-coop-smart-contracts with Apache License 2.0 4 votes vote down vote up
describe("FlexibleLeverageStrategyExtension", () => {
  let owner: Account;
  let methodologist: Account;
  let setV2Setup: SetFixture;
  let compoundSetup: CompoundFixture;
  let uniswapSetup: UniswapFixture;
  let sushiswapSetup: UniswapFixture;

  let deployer: DeployHelper;
  let setToken: SetToken;
  let cEther: CEther;
  let cUSDC: CERc20;
  let cWBTC: CERc20;

  let strategy: ContractSettings;
  let methodology: MethodologySettings;
  let execution: ExecutionSettings;
  let incentive: IncentiveSettings;

  let flexibleLeverageStrategyExtension: FlexibleLeverageStrategyExtension;
  let compoundLeverageModule: CompoundLeverageModule;
  let baseManager: BaseManagerV2;

  let chainlinkETH: ChainlinkAggregatorV3Mock;
  let chainlinkWBTC: ChainlinkAggregatorV3Mock;
  let chainlinkUSDC: ChainlinkAggregatorV3Mock;

  let wethUsdcPoolUni: UniswapV2Pair;
  let wethWbtcPoolUni: UniswapV2Pair;
  let wethUsdcPoolSushi: UniswapV2Pair;
  let wethWbtcPoolSushi: UniswapV2Pair;

  let scenarios: FLISettings[];

  before(async () => {
    [
      owner,
      methodologist,
    ] = await getAccounts();

    console.log("Deploying Base Protocols...");

    deployer = new DeployHelper(owner.wallet);

    setV2Setup = getSetFixture(owner.address);
    await setV2Setup.initialize();

    compoundSetup = getCompoundFixture(owner.address);
    await compoundSetup.initialize();

    uniswapSetup = getUniswapFixture(owner.address);
    await uniswapSetup.initialize(
      owner,
      setV2Setup.weth.address,
      setV2Setup.wbtc.address,
      setV2Setup.usdc.address,
      minimumInit
    );

    sushiswapSetup = getUniswapFixture(owner.address);
    await sushiswapSetup.initialize(
      owner,
      setV2Setup.weth.address,
      setV2Setup.wbtc.address,
      setV2Setup.usdc.address,
      minimumInit
    );

    wethUsdcPoolUni = await uniswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.usdc.address);
    wethWbtcPoolUni = await uniswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.wbtc.address);

    wethUsdcPoolSushi = await sushiswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.usdc.address);
    wethWbtcPoolSushi = await sushiswapSetup.createNewPair(setV2Setup.weth.address, setV2Setup.wbtc.address);

    await setV2Setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.usdc.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await uniswapSetup.router.addLiquidity(
      setV2Setup.weth.address,
      setV2Setup.usdc.address,
      ether(10000),
      usdc(10000000),
      ether(9999),
      usdc(9990000),
      owner.address,
      MAX_UINT_256
    );

    await setV2Setup.wbtc.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.weth.connect(owner.wallet).approve(uniswapSetup.router.address, MAX_UINT_256);
    await uniswapSetup.router.addLiquidity(
      setV2Setup.wbtc.address,
      setV2Setup.weth.address,
      bitcoin(100),
      ether(4000),
      bitcoin(99),
      ether(3900),
      owner.address,
      MAX_UINT_256
    );

    await setV2Setup.weth.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.usdc.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await sushiswapSetup.router.addLiquidity(
      setV2Setup.weth.address,
      setV2Setup.usdc.address,
      ether(4000),
      usdc(4000000),
      ether(399),
      usdc(499000),
      owner.address,
      MAX_UINT_256
    );

    await setV2Setup.wbtc.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await setV2Setup.weth.connect(owner.wallet).approve(sushiswapSetup.router.address, MAX_UINT_256);
    await sushiswapSetup.router.addLiquidity(
      setV2Setup.wbtc.address,
      setV2Setup.weth.address,
      bitcoin(50),
      ether(2000),
      bitcoin(49),
      ether(1900),
      owner.address,
      MAX_UINT_256
    );

    cEther = await compoundSetup.createAndEnableCEther(
      ether(200000000),
      compoundSetup.comptroller.address,
      compoundSetup.interestRateModel.address,
      "Compound ether",
      "cETH",
      8,
      ether(0.75), // 75% collateral factor
      ether(1000)   // $1000
    );

    cUSDC = await compoundSetup.createAndEnableCToken(
      setV2Setup.usdc.address,
      200000000000000,
      compoundSetup.comptroller.address,
      compoundSetup.interestRateModel.address,
      "Compound USDC",
      "cUSDC",
      8,
      ether(0.75), // 75% collateral factor
      ether(1000000000000) // IMPORTANT: Compound oracles account for decimals scaled by 10e18. For USDC, this is $1 * 10^18 * 10^18 / 10^6 = 10^30
    );

    cWBTC = await compoundSetup.createAndEnableCToken(
      setV2Setup.wbtc.address,
      ether(0.02),
      compoundSetup.comptroller.address,
      compoundSetup.interestRateModel.address,
      "Compound WBTC",
      "cWBTC",
      8,
      ether(0.75),
      ether(500000000000000) // $50,000
    );

    await compoundSetup.comptroller._setCompRate(ether(1));
    await compoundSetup.comptroller._addCompMarkets([cEther.address, cUSDC.address, cWBTC.address]);

    // Mint cTokens
    await setV2Setup.usdc.approve(cUSDC.address, ether(100000));
    await setV2Setup.wbtc.approve(cWBTC.address, ether(100000));
    await cUSDC.mint(ether(1));
    await cWBTC.mint(ether(1));
    await cEther.mint({value: ether(1000)});

    // Deploy Compound leverage module and add to controller
    compoundLeverageModule = await deployer.setV2.deployCompoundLeverageModule(
      setV2Setup.controller.address,
      compoundSetup.comp.address,
      compoundSetup.comptroller.address,
      cEther.address,
      setV2Setup.weth.address
    );
    await setV2Setup.controller.addModule(compoundLeverageModule.address);

    // Set integrations for CompoundLeverageModule
    await setV2Setup.integrationRegistry.addIntegration(
      compoundLeverageModule.address,
      "UniswapTradeAdapter",
      uniswapSetup.uniswapTradeAdapter.address,
    );

    await setV2Setup.integrationRegistry.addIntegration(
      compoundLeverageModule.address,
      "SushiswapTradeAdapter",
      sushiswapSetup.uniswapTradeAdapter.address,
    );

    await setV2Setup.integrationRegistry.addIntegration(
      compoundLeverageModule.address,
      "DefaultIssuanceModule",
      setV2Setup.debtIssuanceModule.address,
    );

    // Deploy Chainlink mocks
    chainlinkETH = await deployer.mocks.deployChainlinkAggregatorMock();
    await chainlinkETH.setPrice(BigNumber.from(1000).mul(10 ** 8));
    chainlinkUSDC = await deployer.mocks.deployChainlinkAggregatorMock();
    await chainlinkUSDC.setPrice(10 ** 8);
    chainlinkWBTC = await deployer.mocks.deployChainlinkAggregatorMock();
    await chainlinkWBTC.setPrice(BigNumber.from(50000).mul(10 ** 8));
  });

  beforeEach(async () => {
    scenarios = [
      {
        name: "ETH/USDC 2x",
        collateralAsset: setV2Setup.weth,
        borrowAsset: setV2Setup.usdc,
        collateralCToken: cEther,
        borrowCToken: cUSDC,
        chainlinkCollateral: chainlinkETH,
        chainlinkBorrow: chainlinkUSDC,
        targetLeverageRatio: ether(2),
        collateralPerSet: ether(1),
        exchangeNames: [ "UniswapTradeAdapter", "SushiswapTradeAdapter" ],
        exchanges: [
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: ether(5),
            incentivizedTwapMaxTradeSize: ether(10),
            leverExchangeData: EMPTY_BYTES,
            deleverExchangeData: EMPTY_BYTES,
          },
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: ether(5),
            incentivizedTwapMaxTradeSize: ether(10),
            leverExchangeData: EMPTY_BYTES,
            deleverExchangeData: EMPTY_BYTES,
          },
        ],
        checkpoints: [
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1000),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1100),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1100),
            exchangeName: "SushiswapTradeAdapter",
            exchangePools: [wethUsdcPoolSushi],
            router: sushiswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(800),
            borrowPrice: ether(1),
            elapsedTime: ONE_HOUR_IN_SECONDS.mul(12),
            wethPrice: ether(800),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
        ],
      } as FLISettings,
      {
        name: "ETH/USDC Inverse",
        collateralAsset: setV2Setup.usdc,
        borrowAsset: setV2Setup.weth,
        collateralCToken: cUSDC,
        borrowCToken: cEther,
        chainlinkCollateral: chainlinkUSDC,
        chainlinkBorrow: chainlinkETH,
        targetLeverageRatio: ether(2),
        collateralPerSet: ether(100),
        exchangeNames: [ "UniswapTradeAdapter" ],
        exchanges: [
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: ether(1000),
            incentivizedTwapMaxTradeSize: ether(100000),
            leverExchangeData: EMPTY_BYTES,
            deleverExchangeData: EMPTY_BYTES,
          },
        ],
        checkpoints: [
          {
            issueAmount: ether(500),
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1300),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1300),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ether(10000),
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1300),
            elapsedTime: ONE_HOUR_IN_SECONDS,
            wethPrice: ether(1300),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1300),
            elapsedTime: ONE_HOUR_IN_SECONDS,
            wethPrice: ether(1300),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1700),
            elapsedTime: ONE_HOUR_IN_SECONDS,
            wethPrice: ether(1700),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ZERO,
            collateralPrice: ether(1),
            borrowPrice: ether(1100),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1100),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
        ],
      } as FLISettings,
      {
        name: "BTC/USDC 2x",
        collateralAsset: setV2Setup.wbtc,
        borrowAsset: setV2Setup.usdc,
        collateralCToken: cWBTC,
        borrowCToken: cUSDC,
        chainlinkCollateral: chainlinkWBTC,
        chainlinkBorrow: chainlinkUSDC,
        targetLeverageRatio: ether(2),
        collateralPerSet: ether(.1),
        exchangeNames: [ "UniswapTradeAdapter", "SushiswapTradeAdapter" ],
        exchanges: [
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: bitcoin(3),
            incentivizedTwapMaxTradeSize: bitcoin(5),
            leverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.usdc.address, setV2Setup.weth.address, setV2Setup.wbtc.address]]),
            deleverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.wbtc.address, setV2Setup.weth.address, setV2Setup.usdc.address]]),
          },
          {
            exchangeLastTradeTimestamp: BigNumber.from(0),
            twapMaxTradeSize: bitcoin(3),
            incentivizedTwapMaxTradeSize: bitcoin(5),
            leverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.usdc.address, setV2Setup.weth.address, setV2Setup.wbtc.address]]),
            deleverExchangeData: defaultAbiCoder.encode(["address[]"], [[setV2Setup.wbtc.address, setV2Setup.weth.address, setV2Setup.usdc.address]]),
          },
        ],
        checkpoints: [
          {
            issueAmount: ether(.5),
            redeemAmount: ZERO,
            collateralPrice: ether(55000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1375),
            exchangeName: "SushiswapTradeAdapter",
            exchangePools: [wethWbtcPoolSushi, wethUsdcPoolSushi],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ether(2),
            redeemAmount: ZERO,
            collateralPrice: ether(49000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(1225),
            exchangeName: "SushiswapTradeAdapter",
            exchangePools: [wethWbtcPoolSushi, wethUsdcPoolSushi],
            router: uniswapSetup.router,
          },
          {
            issueAmount: ZERO,
            redeemAmount: ether(5),
            collateralPrice: ether(35000),
            borrowPrice: ether(1),
            elapsedTime: ONE_DAY_IN_SECONDS,
            wethPrice: ether(875),
            exchangeName: "UniswapTradeAdapter",
            exchangePools: [wethWbtcPoolUni, wethUsdcPoolUni],
            router: uniswapSetup.router,
          },
        ],
      } as FLISettings,
    ];
  });

  addSnapshotBeforeRestoreAfterEach();

  describe("#scenario1", async () => {
    let subjectScenario: FLISettings;

    beforeEach(async () => {
      subjectScenario = scenarios[0];

      await deployFLISetup(subjectScenario);

      await issueFLITokens(subjectScenario.collateralCToken, ether(10));

      await engageFLI(subjectScenario.checkpoints[0].exchangeName);
    });

    async function subject(): Promise<any> {
      return runScenarios(subjectScenario, false);
    }

    it("validate state", async () => {
      const [preRebalanceLeverageRatios, postRebalanceLeverageRatios] = await subject();

      const lastTradeTimestamp = await flexibleLeverageStrategyExtension.globalLastTradeTimestamp();

      expect(lastTradeTimestamp).to.eq(await getLastBlockTimestamp());
      expect(postRebalanceLeverageRatios[0]).to.lt(preRebalanceLeverageRatios[0]);
      expect(postRebalanceLeverageRatios[1]).to.gt(preRebalanceLeverageRatios[1]);
      expect(postRebalanceLeverageRatios[2]).to.lt(preRebalanceLeverageRatios[2]);
    });
  });

  describe("#scenario2", async () => {
    let subjectScenario: FLISettings;

    beforeEach(async () => {
      subjectScenario = scenarios[1];

      await deployFLISetup(subjectScenario);

      await issueFLITokens(subjectScenario.collateralCToken, ether(10));

      await engageFLI(subjectScenario.checkpoints[0].exchangeName);
    });

    async function subject(): Promise<any> {
      return runScenarios(subjectScenario, false);
    }

    it("validate state", async () => {
      const [preRebalanceLeverageRatios, postRebalanceLeverageRatios] = await subject();

      const lastTradeTimestamp = await flexibleLeverageStrategyExtension.globalLastTradeTimestamp();

      expect(lastTradeTimestamp).to.eq(await getLastBlockTimestamp());
      expect(postRebalanceLeverageRatios[0]).to.lt(preRebalanceLeverageRatios[0]);
      expect(postRebalanceLeverageRatios[1]).to.lt(preRebalanceLeverageRatios[1]);
      expect(postRebalanceLeverageRatios[2]).to.lt(preRebalanceLeverageRatios[2]);
      expect(postRebalanceLeverageRatios[3]).to.lt(preRebalanceLeverageRatios[3]);
      expect(postRebalanceLeverageRatios[4]).to.gt(preRebalanceLeverageRatios[4]);
    });
  });

  describe("#scenario3", async () => {
    let subjectScenario: FLISettings;

    beforeEach(async () => {
      subjectScenario = scenarios[2];

      await deployFLISetup(subjectScenario);

      await issueFLITokens(subjectScenario.collateralCToken, ether(10));

      await engageFLI(subjectScenario.checkpoints[0].exchangeName);
    });

    async function subject(): Promise<any> {
      return runScenarios(subjectScenario, true);
    }

    it("validate state", async () => {
      const [preRebalanceLeverageRatios, postRebalanceLeverageRatios] = await subject();

      const lastTradeTimestamp = await flexibleLeverageStrategyExtension.globalLastTradeTimestamp();

      expect(lastTradeTimestamp).to.eq(await getLastBlockTimestamp());
      expect(postRebalanceLeverageRatios[0]).to.gt(preRebalanceLeverageRatios[0]);
      expect(postRebalanceLeverageRatios[1]).to.gt(preRebalanceLeverageRatios[1]);
      expect(postRebalanceLeverageRatios[2]).to.lt(preRebalanceLeverageRatios[2]);
    });
  });

  async function deployFLISetup(fliSettings: FLISettings): Promise<void> {
    console.log("Deploying FLI Strategy and SetToken...");

    const unit = preciseMul(bitcoin(50), fliSettings.collateralPerSet); // User bitcoin(50) because a full unit of underlying is 50*10^8
    setToken = await setV2Setup.createSetToken(
      [fliSettings.collateralCToken.address],
      [unit],
      [
        setV2Setup.streamingFeeModule.address,
        compoundLeverageModule.address,
        setV2Setup.debtIssuanceModule.address,
      ]
    );
    await compoundLeverageModule.updateAnySetAllowed(true);

    // Initialize modules
    await setV2Setup.debtIssuanceModule.initialize(
      setToken.address,
      ether(1),
      ZERO,
      ZERO,
      owner.address,
      ADDRESS_ZERO
    );
    const feeRecipient = owner.address;
    const maxStreamingFeePercentage = ether(.1);
    const streamingFeePercentage = ether(.02);
    const streamingFeeSettings = {
      feeRecipient,
      maxStreamingFeePercentage,
      streamingFeePercentage,
      lastStreamingFeeTimestamp: ZERO,
    };
    await setV2Setup.streamingFeeModule.initialize(setToken.address, streamingFeeSettings);
    await compoundLeverageModule.initialize(
      setToken.address,
      [fliSettings.collateralAsset.address],
      [fliSettings.borrowAsset.address]
    );

    baseManager = await deployer.manager.deployBaseManagerV2(
      setToken.address,
      owner.address,
      methodologist.address
    );
    await baseManager.connect(methodologist.wallet).authorizeInitialization();

    // Transfer ownership to ic manager
    await setToken.setManager(baseManager.address);

    strategy = {
      setToken: setToken.address,
      leverageModule: compoundLeverageModule.address,
      comptroller: compoundSetup.comptroller.address,
      collateralPriceOracle: fliSettings.chainlinkCollateral.address,
      borrowPriceOracle: fliSettings.chainlinkBorrow.address,
      targetCollateralCToken: fliSettings.collateralCToken.address,
      targetBorrowCToken: fliSettings.borrowCToken.address,
      collateralAsset: fliSettings.collateralAsset.address,
      borrowAsset: fliSettings.borrowAsset.address,
      collateralDecimalAdjustment: BigNumber.from(28 - await fliSettings.collateralAsset.decimals()),
      borrowDecimalAdjustment: BigNumber.from(28 - await fliSettings.borrowAsset.decimals()),
    };
    methodology = {
      targetLeverageRatio: fliSettings.targetLeverageRatio,
      minLeverageRatio: preciseMul(fliSettings.targetLeverageRatio, PRECISE_UNIT.sub(minLeverageBuffer)),
      maxLeverageRatio: preciseMul(fliSettings.targetLeverageRatio, PRECISE_UNIT.add(maxLeverageBuffer)),
      recenteringSpeed: recenteringSpeed,
      rebalanceInterval: rebalanceInterval,
    };
    execution = {
      unutilizedLeveragePercentage: unutilizedLeveragePercentage,
      twapCooldownPeriod: twapCooldownPeriod,
      slippageTolerance: slippageTolerance,
    };
    incentive = {
      incentivizedTwapCooldownPeriod: incentivizedTwapCooldownPeriod,
      incentivizedSlippageTolerance: incentivizedSlippageTolerance,
      etherReward: etherReward,
      incentivizedLeverageRatio: incentivizedLeverageRatio,
    };

    flexibleLeverageStrategyExtension = await deployer.extensions.deployFlexibleLeverageStrategyExtension(
      baseManager.address,
      strategy,
      methodology,
      execution,
      incentive,
      fliSettings.exchangeNames,
      fliSettings.exchanges
    );
    await flexibleLeverageStrategyExtension.updateCallerStatus([owner.address], [true]);

    // Add extension
    await baseManager.connect(owner.wallet).addExtension(flexibleLeverageStrategyExtension.address);
  }

  async function issueFLITokens(collateralCToken: CERc20 | CEther, amount: BigNumber): Promise<void> {
    console.log(`Issuing ${amount.toString()} SetTokens`);
    if (amount.gt(ZERO)) {
      await collateralCToken.approve(setV2Setup.debtIssuanceModule.address, MAX_UINT_256);
      await setV2Setup.debtIssuanceModule.issue(setToken.address, amount, owner.address);
    }
  }

  async function redeemFLITokens(amount: BigNumber): Promise<void> {
    console.log(`Redeeming ${amount.toString()} SetTokens`);
    if (amount.gt(ZERO)) {
      await setV2Setup.debtIssuanceModule.issue(setToken.address, amount, owner.address);
    }
  }

  async function engageFLI(exchangeName: string): Promise<void> {
    console.log("Engaging FLI...");
    await flexibleLeverageStrategyExtension.engage(exchangeName);
    await increaseTimeAsync(twapCooldownPeriod);
    await flexibleLeverageStrategyExtension.iterateRebalance(exchangeName);
  }

  async function runScenarios(fliSettings: FLISettings, isMultihop: boolean): Promise<[BigNumber[], BigNumber[]]> {
    console.log(`Running Scenarios ${fliSettings.name}`);
    await increaseTimeAsync(rebalanceInterval);

    await flexibleLeverageStrategyExtension.rebalance(fliSettings.checkpoints[0].exchangeName);

    const preRebalanceLeverageRatios = [];
    const postRebalanceLeverageRatios = [];
    for (let i = 0; i < fliSettings.checkpoints.length; i++) {
      console.log("----------------------");

      await setPricesAndUniswapPool(fliSettings, i, isMultihop);

      await liquidateIfLiquidatable(fliSettings);

      await issueFLITokens(fliSettings.collateralCToken, fliSettings.checkpoints[i].issueAmount);
      await redeemFLITokens(fliSettings.checkpoints[i].redeemAmount);

      await increaseTimeAsync(fliSettings.checkpoints[i].elapsedTime);

      const rebalanceInfo = await flexibleLeverageStrategyExtension.shouldRebalance();

      const preRebalanceLeverageRatio = await flexibleLeverageStrategyExtension.getCurrentLeverageRatio();
      preRebalanceLeverageRatios.push(preRebalanceLeverageRatio);
      console.log("Pre-Rebalance Leverage Ratio:", preRebalanceLeverageRatio.toString());

      const rebalanceInfoIndex = rebalanceInfo[0].indexOf(fliSettings.checkpoints[i].exchangeName);
      const rebalanceType = rebalanceInfo[1][rebalanceInfoIndex];
      if (rebalanceType != 0) {
        await executeTrade(rebalanceType, fliSettings.checkpoints[i].exchangeName);
      }
      console.log("RebalanceType:", rebalanceType);
      const postRebalanceLeverageRatio = await flexibleLeverageStrategyExtension.getCurrentLeverageRatio();
      postRebalanceLeverageRatios.push(postRebalanceLeverageRatio);
      console.log("Leverage Ratio:", postRebalanceLeverageRatio.toString());
      console.log(
        "Debt Position:",
        (await setToken.getExternalPositionRealUnit(
          fliSettings.borrowAsset.address,
          compoundLeverageModule.address
        )).toString()
      );
      console.log("Collateral Position:", (await setToken.getDefaultPositionRealUnit(fliSettings.collateralCToken.address)).toString());
      console.log("Borrow Asset Price:", fliSettings.checkpoints[i].borrowPrice.toString());
      console.log("Collateral Asset Price:", fliSettings.checkpoints[i].collateralPrice.toString());
      console.log("Set Value:", (await calculateSetValue(fliSettings, i)).toString());
    }

    return [preRebalanceLeverageRatios, postRebalanceLeverageRatios];
  }

  async function setPricesAndUniswapPool(
    fliSettings: FLISettings,
    checkpoint: number,
    isMultihop: boolean
  ): Promise<void> {
    const collateralDecimals = BigNumber.from(10).pow((await fliSettings.collateralAsset.decimals()));
    const borrowDecimals = BigNumber.from(10).pow((await fliSettings.borrowAsset.decimals()));
    const scaledCollateralPrice = preciseDiv(fliSettings.checkpoints[checkpoint].collateralPrice, collateralDecimals);
    const scaledBorrowPrice = preciseDiv(fliSettings.checkpoints[checkpoint].borrowPrice, borrowDecimals);

    await compoundSetup.priceOracle.setUnderlyingPrice(fliSettings.collateralCToken.address, scaledCollateralPrice);
    await fliSettings.chainlinkCollateral.setPrice(fliSettings.checkpoints[checkpoint].collateralPrice.div(10 ** 10));
    await compoundSetup.priceOracle.setUnderlyingPrice(fliSettings.borrowCToken.address, scaledBorrowPrice);
    await fliSettings.chainlinkBorrow.setPrice(fliSettings.checkpoints[checkpoint].borrowPrice.div(10 ** 10));

    const collateralPrice = fliSettings.checkpoints[checkpoint].collateralPrice;
    const borrowPrice = fliSettings.checkpoints[checkpoint].borrowPrice;
    const wethPrice = fliSettings.checkpoints[checkpoint].wethPrice;
    if (isMultihop) {
      // Set collateral asset <> WETH pool
      await calculateAndSetUniswapPool(
        fliSettings,
        fliSettings.checkpoints[checkpoint].router,
        fliSettings.collateralAsset,
        setV2Setup.weth,
        collateralPrice,
        wethPrice,
        fliSettings.checkpoints[checkpoint].exchangePools[0],
      );

      // Set WETH <> borrow asset pool
      await calculateAndSetUniswapPool(
        fliSettings,
        fliSettings.checkpoints[checkpoint].router,
        setV2Setup.weth,
        fliSettings.borrowAsset,
        wethPrice,
        borrowPrice,
        fliSettings.checkpoints[checkpoint].exchangePools[1],
      );
    } else {
      await calculateAndSetUniswapPool(
        fliSettings,
        fliSettings.checkpoints[checkpoint].router,
        fliSettings.collateralAsset,
        fliSettings.borrowAsset,
        collateralPrice,
        borrowPrice,
        fliSettings.checkpoints[checkpoint].exchangePools[0],
      );
    }
  }

  async function executeTrade(shouldRebalance: number, exchangeName: string): Promise<void> {
    switch (shouldRebalance) {
      case 1: {
        await flexibleLeverageStrategyExtension.rebalance(exchangeName);
        break;
      }
      case 2: {
        await flexibleLeverageStrategyExtension.iterateRebalance(exchangeName);
        break;
    }
      case 3: {
        await flexibleLeverageStrategyExtension.ripcord(exchangeName);
        break;
      }
    }
  }

  async function calculateAndSetUniswapPool(
    fliSettings: FLISettings,
    router: UniswapV2Router02,
    assetOne: StandardTokenMock | WETH9,
    assetTwo: StandardTokenMock | WETH9,
    assetOnePrice: BigNumber,
    assetTwoPrice: BigNumber,
    uniswapPool: UniswapV2Pair
  ): Promise<void> {
    const [ assetOneAmount, buyAssetOne ] = await calculateUniswapTradeAmount(
      fliSettings,
      assetOne,
      assetTwo,
      assetOnePrice,
      assetTwoPrice,
      uniswapPool,
    );

    if (buyAssetOne) {
      await router.swapTokensForExactTokens(
        assetOneAmount,
        MAX_UINT_256,
        [assetTwo.address, assetOne.address],
        owner.address,
        MAX_UINT_256
      );
    } else {
      await router.swapExactTokensForTokens(
        assetOneAmount,
        ZERO,
        [assetOne.address, assetTwo.address],
        owner.address,
        MAX_UINT_256
      );
    }
  }

  async function calculateUniswapTradeAmount(
    fliSettings: FLISettings,
    assetOne: StandardTokenMock | WETH9,
    assetTwo: StandardTokenMock | WETH9,
    assetOnePrice: BigNumber,
    assetTwoPrice: BigNumber,
    uniswapPool: UniswapV2Pair
  ): Promise<[BigNumber, boolean]> {
    const assetOneDecimals = BigNumber.from(10).pow((await assetOne.decimals()));
    const assetTwoDecimals = BigNumber.from(10).pow((await assetTwo.decimals()));
    const [ assetOneReserve, assetTwoReserve ] = await getUniswapReserves(fliSettings, uniswapPool, assetOne);
    const expectedPrice = preciseDiv(assetOnePrice, assetTwoPrice);

    const currentK = assetOneReserve.mul(assetTwoReserve);
    const assetOneLeft = sqrt(currentK.div(expectedPrice.mul(assetTwoDecimals).div(assetOneDecimals))).mul(BigNumber.from(10).pow(9));

    return assetOneLeft.gt(assetOneReserve) ?
      [ assetOneLeft.sub(assetOneReserve), false ] :
      [ assetOneReserve.sub(assetOneLeft), true ];
  }

  async function getUniswapReserves(
    fliSettings: FLISettings,
    uniswapPool: UniswapV2Pair,
    assetOne: StandardTokenMock | WETH9
  ): Promise<[BigNumber, BigNumber]> {
    const [ reserveOne, reserveTwo ] = await uniswapPool.getReserves();
    const tokenOne = await uniswapPool.token0();
    return tokenOne == assetOne.address ? [reserveOne, reserveTwo] : [reserveTwo, reserveOne];
  }

  async function liquidateIfLiquidatable(fliSettings: FLISettings): Promise<void> {
    const [ , , shortfall] = await compoundSetup.comptroller.getAccountLiquidity(setToken.address);
    if (shortfall.gt(0)) {
      const debtUnits = await setToken.getExternalPositionRealUnit(fliSettings.borrowAsset.address, compoundLeverageModule.address);
      const payDownAmount = preciseMul(debtUnits, await setToken.totalSupply()).mul(-1).div(2);

      if (fliSettings.borrowAsset.address != setV2Setup.weth.address) {
        const cToken = await new CERc20__factory(owner.wallet).attach(fliSettings.borrowCToken.address);
        await cToken.liquidateBorrow(setToken.address, payDownAmount, fliSettings.collateralCToken.address);
      } else {
        const cToken: CEther = await new CEther__factory(owner.wallet).attach(fliSettings.borrowCToken.address);
        await cToken.liquidateBorrow(setToken.address, fliSettings.collateralCToken.address, { value: payDownAmount });
      }
    }
  }

  async function calculateSetValue(fliSettings: FLISettings, checkpoint: number): Promise<BigNumber> {
    const totalSupply = await setToken.totalSupply();
    const collateralCTokenUnit = (await setToken.getDefaultPositionRealUnit(fliSettings.collateralCToken.address));
    const borrowUnit = (await setToken.getExternalPositionRealUnit(fliSettings.borrowAsset.address, compoundLeverageModule.address));
    const borrowDecimals = BigNumber.from(10).pow(await fliSettings.borrowAsset.decimals());

    const collateralValue = preciseMul(collateralCTokenUnit, totalSupply).mul(fliSettings.checkpoints[checkpoint].collateralPrice).div(bitcoin(50));
    const borrowValue = preciseMul(borrowUnit, totalSupply).mul(fliSettings.checkpoints[checkpoint].borrowPrice).div(borrowDecimals);

    return collateralValue.add(borrowValue);
  }

  function sqrt(value: BigNumber) {
    let z = value.add(ONE).div(TWO);
    let y = value;
    while (z.sub(y).isNegative()) {
        y = z;
        z = value.div(z).add(z).div(TWO);
    }
    return y;
  }
});