import {
  addAssets,
  Data,
  Emulator,
  EmulatorAccount,
  fromHex,
  fromText,
  generateEmulatorAccount,
  Lucid,
  paymentCredentialOf,
  stakeCredentialOf,
} from '@lucid-evolution/lucid';
import { assert, beforeEach, describe, expect, test } from 'vitest';
import {
  IndigoTestContext,
  LucidContext,
  runAndAwaitTx,
  runAndAwaitTxBuilder,
} from '../test-helpers';
import {
  assetClassValueOf,
  isAssetsZero,
  lovelacesAmt,
  mkAssetsOf,
  mkLovelacesOf,
  negateAssets,
  adaAssetClass,
  AssetClass,
  assetClassToUnit,
} from '@3rd-eye-labs/cardano-offchain-common';
import {
  addrDetails,
  createProposal,
  createScriptAddress,
  requestSpAccountCreation,
  feedInterestOracle,
  fromSystemParamsAsset,
  liquidateCdp,
  matchSingle,
  mergeCdps,
  openCdp,
  redeemCdp,
} from '../../src';
import {
  findAllNecessaryOrefs,
  findCdp,
  findCdpCR,
  findFrozenCDPs,
  findOwnCdp,
  findPriceOracleFromCollateralAsset,
} from './cdp-queries';
import {
  getValueChangeAtAddressAfterAction,
  getValueChangeAtAddressesAfterAction,
} from '../utils';
import { feedPriceOracleTx } from '../../src/contracts/price-oracle/transactions';
import { assertValueInRange, expectScriptFailure } from '../utils/asserts';
import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
import {
  ieurInitialAssetCfg,
  iusdInitialAssetCfg,
  mkBaseCollateralAsset,
} from '../mock/assets-mock';
import { init } from '../endpoints/initialize';
import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers';
import {
  mutatedRedeemCdp,
  runCloseCdpWrongOracle,
  runTestAdjustCdpDelisted,
  runRedeemCdpWrongOracle,
  runOpenCdpDelisted,
  runOpenCdpAndUpdateOracle,
} from './transactions-mutated';
import {
  runBurnCdp,
  runCloseCdp,
  runCreateAndFreezeCdps,
  runDepositCdp,
  runFreezeCdp,
  runMintCdp,
  runOpenCdp,
  runRedeemCdp,
  runWithdrawCdp,
} from './actions';
import { calculateCdpInterest } from './cdp-helpers';
import { runFeedPriceToOracle } from '../price-oracle/actions';
import { findGov } from '../gov/governance-queries';
import { findCollateralAsset } from '../queries/iasset-queries';
import { processSuccessfulProposal } from '../gov/actions';
import {
  runOpenCdpAndCreateSPAccount,
  runProcessSpRequest,
} from '../stability-pool/actions';
import {
  createMultipleUtxosAtTreasury,
  createUtxoAtTreasury,
} from '../endpoints/treasury';
import { mkTreasuryAddr } from '../../src/contracts/treasury/helpers';
import { MAINNET_PROTOCOL_PARAMETERS } from '../indigo-test-helpers';
import {
  rationalFloor,
  rationalFromInt,
  rationalMul,
  rationalToFloat,
} from '../../src/types/rational';
import * as Core from '@evolution-sdk/evolution';

type MyContext = LucidContext<{
  admin: EmulatorAccount;
  user: EmulatorAccount;
}>;

const collateralAssetA: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dea',
  ),
  tokenName: fromHex(fromText('A')),
};
const collateralAssetB: AssetClass = {
  currencySymbol: fromHex(
    'e356735fbb1a674fec7db1e015ab31213f86320c94225577587d0197',
  ),
  tokenName: fromHex(fromText('B')),
};

describe('CDP', () => {
  beforeEach<MyContext>(async (context: MyContext) => {
    context.users = {
      admin: generateEmulatorAccount({
        lovelace: BigInt(100_000_000_000_000),
        [assetClassToUnit(collateralAssetA)]: 1_000_000_000_000n,
        [assetClassToUnit(collateralAssetB)]: 1_000_000_000_000n,
      }),
      user: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(200_000_000n),
          mkAssetsOf(collateralAssetA, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetB, 1_000_000_000_000n),
        ),
      ),
    };

    context.emulator = new Emulator(
      [context.users.admin, context.users.user],
      MAINNET_PROTOCOL_PARAMETERS,
    );
    context.lucid = await Lucid(context.emulator, 'Custom');
  });

  test<MyContext>('Open CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      mkTreasuryAddr(context.lucid, sysParams),
      async () =>
        benchmarkAndAwaitTx(
          'CDP - Open CDP; ADA collateral',
          await runOpenCdp(
            context,
            sysParams,
            'iUSD',
            adaAssetClass,
            10_000_000n,
            5_000_000n,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    assert(
      // A 0.5% of 5,000,000 minted iAsset go to treasury.
      assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n &&
        // This is still less than the minimum ADA per UTxO, but enough
        // to cover one more asset class in the treasury output.
        lovelacesAmt(addedValueInTreasury) < 700_000n,
      'Unexpected value received by the treasury',
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 199, max: 200 },
    );
  });
  test<MyContext>('Open CDP; ADA collateral - direct treasury payment', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      mkTreasuryAddr(context.lucid, sysParams),
      async () =>
        benchmarkAndAwaitTx(
          'CDP - Open CDP; ADA collateral - direct treasury payment',
          await runOpenCdp(
            context,
            sysParams,
            'iUSD',
            adaAssetClass,
            10_000_000n,
            5_000_000n,
            undefined,
            // Direct treasury payment.
            true,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    assert(
      // A 0.5% of 5,000,000 minted iAsset go to treasury.
      assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n &&
        // Extra ADA had to be paid to cover the minimum.
        lovelacesAmt(addedValueInTreasury) > 1_000_000n,
      'Unexpected value received by the treasury',
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 199, max: 200 },
    );
  });
  test<MyContext>('Open CDP; non-ADA collateral asset', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      mkTreasuryAddr(context.lucid, sysParams),
      async () =>
        benchmarkAndAwaitTx(
          'CDP - Open CDP; non-ADA collateral',
          await runOpenCdp(
            context,
            sysParams,
            'iUSD',
            collateralAssetA,
            10_000_000n,
            5_000_000n,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    assert(
      // A 0.5% of 5,000,000 minted iAsset go to treasury.
      assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n &&
        // This is still less than the minimum ADA per UTxO, but enough
        // to cover one more asset class in the treasury output.
        lovelacesAmt(addedValueInTreasury) < 700_000n,
      'Unexpected value received by the treasury',
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 199, max: 200 },
    );
  });

  test<MyContext>('Open CDP; non-ADA collateral asset - direct treasury payment', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      mkTreasuryAddr(context.lucid, sysParams),
      async () =>
        benchmarkAndAwaitTx(
          'CDP - Open CDP; non-ADA collateral - direct treasury payment',
          await runOpenCdp(
            context,
            sysParams,
            'iUSD',
            collateralAssetA,
            10_000_000n,
            5_000_000n,
            undefined,
            // Direct treasury payment.
            true,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    assert(
      // A 0.5% of 5,000,000 minted iAsset go to treasury.
      assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n &&
        // Extra ADA had to be paid to cover the minimum.
        lovelacesAmt(addedValueInTreasury) > 1_000_000n,
      'Unexpected value received by the treasury',
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 199, max: 200 },
    );
  });

  // This tests opening a CDP with a collateral asset with 8 decimals, such as xBTC.
  test<MyContext>('Open CDP; non-ADA collateral asset with more decimals', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, _] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [
            mkBaseCollateralAsset(
              collateralAssetA,
              0n,
              rationalFromInt(1n),
              2n,
            ),
          ],
        },
      ],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        // 150% CR
        1_500_000_000n,
        10_000_000n,
      ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 149, max: 151 },
    );

    await expectScriptFailure(
      'Undercollaterized',
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        // 149.9999999% CR
        1_499_999_999n,
        10_000_000n,
      ),
    );
  });

  // This tests opening a CDP with a collateral asset with 4 decimals.
  test<MyContext>('Open CDP; non-ADA collateral asset with less decimals', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, _] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [
            mkBaseCollateralAsset(
              collateralAssetA,
              0n,
              rationalFromInt(1n),
              -2n,
            ),
          ],
        },
      ],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        // 150% CR
        15_000_000n,
        1_000_000_000n,
      ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 149, max: 151 },
    );

    await expectScriptFailure(
      'Undercollaterized',
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        // 149.99999% CR
        14_999_999n,
        1_000_000_000n,
      ),
    );
  });

  test<IndigoTestContext>('Open CDP with large rational price oracle', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    const orefs = await findAllNecessaryOrefs(
      context.lucid,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

    const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
      context.lucid,
      orefs.collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const largeBigInt =
      BigInt(Number.MAX_SAFE_INTEGER) * BigInt(Number.MAX_SAFE_INTEGER);
    await runAndAwaitTx(
      context.lucid,
      feedPriceOracleTx(
        context.lucid,
        priceOracleUtxo!,
        { numerator: largeBigInt, denominator: largeBigInt },
        iusdAssetInfo.collateralAssets[0].oracleParams!,
        context.emulator.slot,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await benchmarkAndAwaitTx(
      'CDP - Open CDP with large rational price oracle',
      await runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        500_000n,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Open CDP with large auxilary data oracle', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    const orefs = await findAllNecessaryOrefs(
      context.lucid,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

    const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
      context.lucid,
      orefs.collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    // Create a 1KB (1024 bytes) string in hex
    const oneKbBuf = Buffer.alloc(1024, 'A');
    await runAndAwaitTx(
      context.lucid,
      feedPriceOracleTx(
        context.lucid,
        priceOracleUtxo!,
        { numerator: 1n, denominator: 1n },
        iusdAssetInfo.collateralAssets[0].oracleParams!,
        context.emulator.slot,
        Core.Data.fromCBORHex(
          Data.to(Data.fromJson(oneKbBuf.toString('ascii'))),
        ),
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await benchmarkAndAwaitTx(
      'CDP - Open CDP with large auxilary data oracle',
      await runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        500_000n,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Open CDP with expired oracle fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await expectScriptFailure(
      'X',
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        500_000n,
      ),
    );
  });

  test<IndigoTestContext>('Open CDP with delisted iAsset fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      'iUSD',
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await expectScriptFailure(
      'W',
      runOpenCdpDelisted(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        500_000n,
      ),
    );
  });

  test<MyContext>('Open CDP with hybrid oracle', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      mkTreasuryAddr(context.lucid, sysParams),
      async () =>
        benchmarkAndAwaitTx(
          'CDP - Open CDP with hybrid oracle',
          await runOpenCdpAndUpdateOracle(
            context,
            sysParams,
            'iUSD',
            adaAssetClass,
            10_000_000n,
            5_000_000n,
            iusdAssetInfo.collateralAssets[0].oracleParams!,
            rationalFromInt(1n),
          ),
          context.lucid,
          context.emulator,
        ),
    );

    const cdp = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    assert(
      // A 0.5% of 5,000,000 minted iAsset go to treasury.
      assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n &&
        // This is still less than the minimum ADA per UTxO, but enough
        // to cover one more asset class in the treasury output.
        lovelacesAmt(addedValueInTreasury) < 700_000n,
      'Unexpected value received by the treasury',
    );

    assertValueInRange(
      await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
      { min: 199, max: 200 },
    );
  });

  test<MyContext>('Deposit CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, _] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Deposit CDP; ADA collateral',
      await runDepositCdp(context, sysParams, 'iUSD', adaAssetClass),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Deposit CDP; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, _] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Deposit CDP',
      await runDepositCdp(context, sysParams, 'iUSD', collateralAssetA),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Deposit CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);

    const iasset = 'iUSD';
    const initialCollateral = 10_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        adaAssetClass,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Deposit CDP w/ interest; ADA collateral',
            await runDepositCdp(context, sysParams, 'iUSD', adaAssetClass),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    });

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid);
    expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe(
      initialCollateral + 1_000_000n,
    );
  });

  test<MyContext>('Deposit CDP w/ interest; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);

    const iasset = 'iUSD';
    const initialCollateral = 10_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        collateralAssetA,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Deposit CDP w/ interest; non-ADA collateral',
            await runDepositCdp(context, sysParams, iasset, collateralAssetA),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    });

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid);

    expect(
      assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA),
    ).toBe(initialCollateral + 1_000_000n);
  });

  test<IndigoTestContext>('Deposit CDP with expired oracle succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        11_000_000n,
        500_000n,
      ),
    );

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await runAndAwaitTx(
      context.lucid,
      runDepositCdp(context, sysParams, 'iUSD', adaAssetClass),
    );
  });

  test<IndigoTestContext>('Deposit CDP with delisted iAsset fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        500_000n,
      ),
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iUsdInfo.iassetTokenNameAscii,
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await expectScriptFailure(
      'G',
      runTestAdjustCdpDelisted(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        1_000_000n,
        0n,
      ),
    );
  });

  test<MyContext>('Withdraw CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, _] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Withdraw CDP; ADA collateral',
      await runWithdrawCdp(context, sysParams, 'iUSD', adaAssetClass),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Withdraw CDP; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, _] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Withdraw CDP; non-ADA collateral',
      await runWithdrawCdp(context, sysParams, 'iUSD', collateralAssetA),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Withdraw CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        adaAssetClass,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Withdraw CDP w/ interest; ADA collateral',
            await runWithdrawCdp(context, sysParams, 'iUSD', adaAssetClass),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    });

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid);
    expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe(
      initialCollateral - 1_000_000n,
    );
  });

  test<MyContext>('Withdraw CDP w/ interest; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Withdraw CDP w/ interest; non-ADA collateral',
            await runWithdrawCdp(context, sysParams, iasset, collateralAssetA),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    });

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid);
    expect(
      assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA),
    ).toBe(initialCollateral - 1_000_000n);
  });

  test<IndigoTestContext>('Withdraw CDP with expired oracle fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await expectScriptFailure(
      'X',
      runWithdrawCdp(context, sysParams, 'iUSD', adaAssetClass),
    );
  });

  test<IndigoTestContext>('Withdraw from CDP with delisted iAsset succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        11_000_000n,
        500_000n,
      ),
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iUsdInfo.iassetTokenNameAscii,
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runWithdrawCdp(context, sysParams, 'iUSD', collateralAssetA),
    );
  });

  test<MyContext>('Mint CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, _] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Mint CDP; ADA collateral',
      await runMintCdp(context, sysParams, 'iUSD', adaAssetClass),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Mint CDP; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, _] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Mint CDP; non-ADA collateral',
      await runMintCdp(context, sysParams, 'iUSD', collateralAssetA),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Mint CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        adaAssetClass,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(60500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const debtAdjustment = 100_000n;

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, [interestCollectorValChange, treasuryValChange]] =
      await getValueChangeAtAddressesAfterAction(
        context.lucid,
        [
          createScriptAddress(
            context.lucid.config().network!,
            sysParams.validatorHashes.interestCollectionHash,
          ),
          mkTreasuryAddr(context.lucid, sysParams),
        ],
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Mint CDP w/ interest; ADA collateral',
            await runMintCdp(
              context,
              sysParams,
              'iUSD',
              adaAssetClass,
              debtAdjustment,
            ),
            context.lucid,
            context.emulator,
          ),
      );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const interestPaid = assetClassValueOf(
      interestCollectorValChange,
      iAssetAc,
    );

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    assert(
      assetClassValueOf(treasuryValChange, iAssetAc) ==
        rationalFloor(
          rationalMul(
            iusdInitialAssetCfg().debtMintingFeeRatio,
            rationalFromInt(debtAdjustment),
          ),
        ) && lovelacesAmt(treasuryValChange) < 700_000n,
      'Unexpected value received by the treasury',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(
      initialMint + 100_000n + interestPaid,
    );
    expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe(initialCollateral);
  });

  test<IndigoTestContext>('Mint CDP w/ interest; ADA collateral - direct treasury payment', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        adaAssetClass,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(60500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const debtAdjustment = 100_000n;

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, [interestCollectorValChange, treasuryValChange]] =
      await getValueChangeAtAddressesAfterAction(
        context.lucid,
        [
          createScriptAddress(
            context.lucid.config().network!,
            sysParams.validatorHashes.interestCollectionHash,
          ),
          mkTreasuryAddr(context.lucid, sysParams),
        ],
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Mint CDP w/ interest; ADA collateral - direct treasury payment',
            await runMintCdp(
              context,
              sysParams,
              'iUSD',
              adaAssetClass,
              debtAdjustment,
              undefined,
              true,
            ),
            context.lucid,
            context.emulator,
          ),
      );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const interestPaid = assetClassValueOf(
      interestCollectorValChange,
      iAssetAc,
    );

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    assert(
      assetClassValueOf(treasuryValChange, iAssetAc) ==
        rationalFloor(
          rationalMul(
            iusdInitialAssetCfg().debtMintingFeeRatio,
            rationalFromInt(debtAdjustment),
          ),
        ) && lovelacesAmt(treasuryValChange) > 1_000_000n,
      'Unexpected value received by the treasury',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(
      initialMint + 100_000n + interestPaid,
    );
    expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe(initialCollateral);
  });

  test<MyContext>('Mint CDP w/ interest; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        collateralAssetA,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(60500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const debtAdjustment = 100_000n;

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, [interestCollectorValChange, treasuryValChange]] =
      await getValueChangeAtAddressesAfterAction(
        context.lucid,
        [
          createScriptAddress(
            context.lucid.config().network!,
            sysParams.validatorHashes.interestCollectionHash,
          ),
          mkTreasuryAddr(context.lucid, sysParams),
        ],
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Mint CDP w/ interest; non-ADA collateral',
            await runMintCdp(
              context,
              sysParams,
              'iUSD',
              collateralAssetA,
              debtAdjustment,
            ),
            context.lucid,
            context.emulator,
          ),
      );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const interestPaid = assetClassValueOf(
      interestCollectorValChange,
      iAssetAc,
    );

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    assert(
      assetClassValueOf(treasuryValChange, iAssetAc) ==
        rationalFloor(
          rationalMul(
            iusdInitialAssetCfg().debtMintingFeeRatio,
            rationalFromInt(debtAdjustment),
          ),
        ) && lovelacesAmt(treasuryValChange) < 700_000n,
      'Unexpected value received by the treasury',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(
      initialMint + 100_000n + interestPaid,
    );
    expect(
      assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA),
    ).toBe(initialCollateral);
  });

  test<MyContext>('Mint CDP w/ interest; non-ADA collateral - direct treasury payment', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        collateralAssetA,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(60500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const debtAdjustment = 100_000n;

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, [interestCollectorValChange, treasuryValChange]] =
      await getValueChangeAtAddressesAfterAction(
        context.lucid,
        [
          createScriptAddress(
            context.lucid.config().network!,
            sysParams.validatorHashes.interestCollectionHash,
          ),
          mkTreasuryAddr(context.lucid, sysParams),
        ],
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Mint CDP w/ interest; non-ADA collateral - direct treasury payment',
            await runMintCdp(
              context,
              sysParams,
              'iUSD',
              collateralAssetA,
              debtAdjustment,
              undefined,
              true,
            ),
            context.lucid,
            context.emulator,
          ),
      );

    const iAssetAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const interestPaid = assetClassValueOf(
      interestCollectorValChange,
      iAssetAc,
    );

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    assert(
      assetClassValueOf(treasuryValChange, iAssetAc) ==
        rationalFloor(
          rationalMul(
            iusdInitialAssetCfg().debtMintingFeeRatio,
            rationalFromInt(debtAdjustment),
          ),
        ) && lovelacesAmt(treasuryValChange) > 1_000_000n,
      'Unexpected value received by the treasury',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(
      initialMint + 100_000n + interestPaid,
    );
    expect(
      assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA),
    ).toBe(initialCollateral);
  });

  test<IndigoTestContext>('Mint CDP with expired oracle fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await expectScriptFailure(
      'X',
      runMintCdp(context, sysParams, 'iUSD', adaAssetClass),
    );
  });

  test<IndigoTestContext>('Mint CDP with delisted iAsset fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iUsdInfo.iassetTokenNameAscii,
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await expectScriptFailure(
      'W',
      runTestAdjustCdpDelisted(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        0n,
        10_000n,
      ),
    );
  });

  test<MyContext>('Burn CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, _] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Burn CDP; ADA collateral',
      await runBurnCdp(context, sysParams, 'iUSD', adaAssetClass),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Burn CDP; non ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, _] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Burn CDP; non ADA collateral',
      await runBurnCdp(context, sysParams, 'iUSD', collateralAssetA),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Burn CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        adaAssetClass,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Burn CDP w/ interest; ADA collateral',
            await runBurnCdp(context, sysParams, 'iUSD', adaAssetClass),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    });

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(
      initialMint - 100_000n + interestPaid,
    );
    expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe(initialCollateral);
  });

  test<MyContext>('Burn CDP w/ interest; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const iasset = 'iUSD';
    const initialCollateral = 12_000_000n;
    const initialMint = 500_000n;

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iasset,
        collateralAssetA,
        initialCollateral,
        initialMint,
      ),
    );

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      rationalFromInt(1n),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdpBeforeAction = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeAction.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Burn CDP w/ interest; non-ADA collateral',
            await runBurnCdp(context, sysParams, iasset, collateralAssetA),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    });

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterAction = await findOwnCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
    );

    expect(cdpAfterAction.datum.mintedAmt).toBe(
      initialMint - 100_000n + interestPaid,
    );
    expect(
      assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA),
    ).toBe(initialCollateral);
  });

  test<IndigoTestContext>('Burn CDP with expired oracle succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await runAndAwaitTx(
      context.lucid,
      runBurnCdp(context, sysParams, 'iUSD', adaAssetClass),
    );
  });

  test<IndigoTestContext>('Burn delisted iAsset succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        500_000n,
      ),
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iUsdInfo.iassetTokenNameAscii,
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runBurnCdp(context, sysParams, 'iUSD', collateralAssetA),
    );
  });

  test<MyContext>('Close CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    // Admin mints some iAsset to send to the user so the user can successfully
    // close the CDP (the user has less iAsset than minted, as some was used to
    // pay for debt minting fees).
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.user.address, mkAssetsOf(iUsdAc, 2_500n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    // The user mints 500_000 iAsset, but the minting fee is deducted from that amount.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Close CDP; ADA collateral',
      await runCloseCdp(context, sysParams, 'iUSD', adaAssetClass),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Close CDP; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    // Admin mints some iAsset to send to the user so the user can successfully
    // close the CDP (the user has less iAsset than minted, as some was used to
    // pay for debt minting fees).
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.user.address, mkAssetsOf(iUsdAc, 2_500n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    // The user mints 500_000 iAsset, but the minting fee is deducted from that amount.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'CDP - Close CDP; non-ADA collateral',
      await runCloseCdp(context, sysParams, 'iUSD', collateralAssetA),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Close CDP w/ interest, ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        24_000_000n,
        1_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.admin.address, mkAssetsOf(iUsdAc, 100_000n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdp = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdp.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Close CDP w/ interest, ADA collateral',
            await runCloseCdp(context, sysParams, 'iUSD', adaAssetClass),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, iUsdAc);

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );
  });

  test<MyContext>('Close CDP w/ interest; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.admin.address, mkAssetsOf(iUsdAc, 100_000n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      rationalFromInt(1n),
    );

    const [pkh, skh] = await addrDetails(context.lucid);

    const cdp = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdp.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Close CDP w/ interest; non-ADA collateral',
            await runCloseCdp(context, sysParams, 'iUSD', collateralAssetA),
            context.lucid,
            context.emulator,
          ),
      );

    const interestPaid = assetClassValueOf(interestCollectorValChange, iUsdAc);

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );
  });

  test<IndigoTestContext>('Close CDP with expired oracle succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Admin mints some iAsset to send to the user so the user can successfully
    // close the CDP (the user has less iAsset than minted, as some was used to
    // pay for debt minting fees).
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.user.address, mkAssetsOf(iUsdAc, 2_500n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    // The user mints 500_000 iAsset, but the minting fee is deducted from that amount.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await runAndAwaitTx(
      context.lucid,
      runCloseCdp(context, sysParams, 'iUSD', adaAssetClass),
    );
  });

  test<IndigoTestContext>('Close CDP with delisted iAsset succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        24_000_000n,
        1_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex('69555344'),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.admin.address, mkAssetsOf(iUsdAc, 100_000n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iUsdInfo.iassetTokenNameAscii,
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    await runAndAwaitTx(
      context.lucid,
      runCloseCdp(context, sysParams, 'iUSD', collateralAssetA),
    );
  });

  test<IndigoTestContext>('Close CDP w/ interest, wrong interest oracle', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo, ieurAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg(), ieurInitialAssetCfg()],
      context.emulator.slot,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        24_000_000n,
        1_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        12_000_000n,
        500_000n,
      ),
    );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const sendFundsTx = await context.lucid
      .newTx()
      .pay.ToAddress(context.users.admin.address, mkAssetsOf(iUsdAc, 100_000n))
      .complete();
    const signedSendFundsTx = await sendFundsTx.sign.withWallet().complete();
    await signedSendFundsTx.submit();

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    context.emulator.awaitSlot(6500);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    await runFeedPriceToOracle(
      context,
      sysParams,
      ieurAssetInfo,
      adaAssetClass,
      rationalFromInt(1n),
    );

    await expectScriptFailure(
      'C',
      runCloseCdpWrongOracle(
        context.lucid,
        context.emulator.slot,
        sysParams,
        'iUSD',
        'iEUR',
        adaAssetClass,
      ),
    );
  });

  test<IndigoTestContext>('Redeem CDP; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await benchmarkAndAwaitTx(
      'CDP - Redeem CDP; ADA collateral',
      await runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        adaAssetClass,
        pkh.hash,
        skh,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Redeem CDP; ADA collateral - direct treasury payment', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await benchmarkAndAwaitTx(
      'CDP - Redeem CDP; ADA collateral - direct treasury payment',
      await runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        adaAssetClass,
        pkh.hash,
        skh,
        // Direct treasury payment
        true,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Redeem CDP; non-ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await benchmarkAndAwaitTx(
      'CDP - Redeem CDP; non-ADA collateral',
      await runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
        pkh.hash,
        skh,
      ),
      context.lucid,
      context.emulator,
    );

    const cdp = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(cdp.datum.mintedAmt).toBe(6_000_000n);
    expect(assetClassValueOf(cdp.utxo.assets, collateralAssetA)).toBe(
      15_050_000n,
    );
  });

  test<IndigoTestContext>('Redeem CDP; non-ADA collateral - direct treasury payment', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await benchmarkAndAwaitTx(
      'CDP - Redeem CDP; non-ADA collateral - direct treasury payment',
      await runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
        pkh.hash,
        skh,
        // Direct treasury payment
        true,
      ),
      context.lucid,
      context.emulator,
    );

    const cdp = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(cdp.datum.mintedAmt).toBe(6_000_000n);
    expect(assetClassValueOf(cdp.utxo.assets, collateralAssetA)).toBe(
      15_050_000n,
    );
  });

  test<IndigoTestContext>('Redeem CDP; non-ADA collateral with more decimals', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [
            mkBaseCollateralAsset(
              collateralAssetA,
              0n,
              rationalFromInt(1n),
              2n,
            ),
          ],
        },
      ],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        5_000_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        2_000_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
        pkh.hash,
        skh,
      ),
    );

    const cdp = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(cdp.datum.mintedAmt).toBe(6_000_000n);
    expect(assetClassValueOf(cdp.utxo.assets, collateralAssetA)).toBe(
      1_505_000_000n,
    );
  });

  test<IndigoTestContext>('Redeem CDP; non-ADA collateral with less decimals', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [
            mkBaseCollateralAsset(
              collateralAssetA,
              0n,
              rationalFromInt(1n),
              -2n,
            ),
          ],
        },
      ],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        50_000_000n,
        1_000_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        1_000_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
        pkh.hash,
        skh,
      ),
    );

    const cdp = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(cdp.datum.mintedAmt).toBe(600_000_000n);
    expect(assetClassValueOf(cdp.utxo.assets, collateralAssetA)).toBe(
      15_050_000n,
    );
  });

  test<IndigoTestContext>('Redeem CDP by unauthorized user fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    const [pkh, skh] = await addrDetails(context.lucid);

    // Create redeemed CDP from Admin wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Add iAssets to user's wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let user do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    {
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      await expectScriptFailure(
        'Sign',
        mutatedRedeemCdp(
          cdp.datum.mintedAmt,
          cdp.utxo,
          orefs.iasset.utxo,
          orefs.collateralAsset.utxo,
          priceOracleUtxo,
          orefs.interestOracleUtxo,
          orefs.interestCollectorUtxo,
          orefs.govUtxo,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
      );
    }
  });

  test<IndigoTestContext>('Redeem CDP by anyone succeeds when required signer in protocol params is none', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    const [pkh, skh] = await addrDetails(context.lucid);

    // Create redeemed CDP from Admin wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Add iAssets to user's wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let user do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        adaAssetClass,
        pkh.hash,
        skh,
      ),
    );
  });

  test<IndigoTestContext>('Partial redeem CDP by anyone succeeds when required signer in protocol params is none; partial redemption is paid; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    const [ownerPkh, ownerSkh] = await addrDetails(context.lucid);

    // Create redeemed CDP from Admin wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Add iAssets to user's wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        // With a 0.005% debt minting fee, 2,000,000 iAssets go to the user.
        2_010_051n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // TODO: implement helper function to assert CDP's CR belonging to owner
    {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      expect(
        rationalToFloat(
          iusdInitialAssetCfg().collateralAssets[0].redemptionRatio,
        ),
      ).toBe(2.0);

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),

        { min: 160, max: 161 },
      );
    }

    {
      // Let user do the redemption (i.e. not the CDP's owner)
      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

      const redemptionCdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        ownerPkh.hash,
        ownerSkh,
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      const [_, addedValueInTreasury] =
        await getValueChangeAtAddressAfterAction(
          context.lucid,
          mkTreasuryAddr(context.lucid, sysParams),
          () =>
            runAndAwaitTx(
              context.lucid,
              redeemCdp(
                // All the iAsset received from creating the CDP.
                2_000_000n,
                redemptionCdp.utxo,
                orefs.iasset.utxo,
                orefs.collateralAsset.utxo,
                priceOracleUtxo,
                orefs.interestOracleUtxo,
                orefs.interestCollectorUtxo,
                orefs.treasuryUtxo,
                orefs.govUtxo,
                sysParams,
                context.lucid,
                context.emulator.slot,
              ),
            ),
        );

      expect(
        isAssetsZero(
          addAssets(
            addedValueInTreasury,
            negateAssets(
              mkLovelacesOf(
                // Since the redemption is public, partial redemption is applied.
                BigInt(
                  sysParams.cdpRedeemParams.partialRedemptionExtraFeeLovelace,
                ) +
                  calculateFeeFromRatio(
                    orefs.iasset.datum.redemptionProcessingFeeRatio,
                    rationalFloor(
                      rationalMul(
                        { numerator: 125n, denominator: 100n },
                        rationalFromInt(2_000_000n),
                      ),
                    ),
                  ),
              ),
            ),
          ),
        ),
      ).toBeTruthy();
    }

    {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      expect(
        rationalToFloat(
          iusdInitialAssetCfg().collateralAssets[0].redemptionRatio,
        ),
      ).toBe(2.0);

      // Since partial redemption has happened, the CR is not the redemption ratio.
      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),

        { min: 170, max: 190 },
      );
    }
  });

  test<IndigoTestContext>('Partial redeem CDP by anyone succeeds when required signer in protocol params is none; partial redemption is paid; non-ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    const [ownerPkh, ownerSkh] = await addrDetails(context.lucid);

    // Create redeemed CDP from Admin wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Add iAssets to user's wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        50_000_000n,
        // With a 0.005% debt minting fee, 2,000,000 iAssets go to the user.
        2_010_051n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 125n, denominator: 100n },
    );

    // TODO: implement helper function to assert CDP's CR belonging to owner
    {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      expect(
        rationalToFloat(
          iusdInitialAssetCfg().collateralAssets[0].redemptionRatio,
        ),
      ).toBe(2.0);

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),

        { min: 160, max: 161 },
      );
    }

    {
      // Let user do the redemption (i.e. not the CDP's owner)
      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

      const redemptionCdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        ownerPkh.hash,
        ownerSkh,
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      const [_, addedValueInTreasury] =
        await getValueChangeAtAddressAfterAction(
          context.lucid,
          mkTreasuryAddr(context.lucid, sysParams),
          () =>
            runAndAwaitTx(
              context.lucid,
              redeemCdp(
                // All the iAsset received from creating the CDP.
                2_000_000n,
                redemptionCdp.utxo,
                orefs.iasset.utxo,
                orefs.collateralAsset.utxo,
                priceOracleUtxo,
                orefs.interestOracleUtxo,
                orefs.interestCollectorUtxo,
                orefs.treasuryUtxo,
                orefs.govUtxo,
                sysParams,
                context.lucid,
                context.emulator.slot,
              ),
            ),
        );

      expect(
        isAssetsZero(
          addAssets(
            addedValueInTreasury,
            negateAssets(
              mkLovelacesOf(
                // Since the redemption is public, partial redemption is applied.
                BigInt(
                  sysParams.cdpRedeemParams.partialRedemptionExtraFeeLovelace,
                ),
              ),
            ),
            negateAssets(
              mkAssetsOf(
                collateralAssetA,
                calculateFeeFromRatio(
                  orefs.iasset.datum.redemptionProcessingFeeRatio,
                  rationalFloor(
                    rationalMul(
                      { numerator: 125n, denominator: 100n },
                      rationalFromInt(2_000_000n),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ).toBeTruthy();
    }

    {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      expect(
        rationalToFloat(
          iusdInitialAssetCfg().collateralAssets[0].redemptionRatio,
        ),
      ).toBe(2.0);

      // Since partial redemption has happened, the CR is not the redemption ratio.
      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),

        { min: 170, max: 190 },
      );
    }
  });

  test<IndigoTestContext>('Redeem CDP w/ interest, redemption larger than interest', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    context.emulator.awaitSlot(6500);

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const cdpBeforeRedemption = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeRedemption.datum,
      context,
      sysParams,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          benchmarkAndAwaitTx(
            'CDP - Redeem CDP w/ interest',
            await runRedeemCdp(
              context,
              sysParams,
              iusdAssetInfo,
              adaAssetClass,
              pkh.hash,
              skh,
            ),
            context.lucid,
            context.emulator,
          ),
      );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const interestPaid = assetClassValueOf(interestCollectorValChange, iUsdAc);

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterRedemption = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(
      cdpBeforeRedemption.datum.mintedAmt > cdpAfterRedemption.datum.mintedAmt,
    ).toBeTruthy();
  });

  test<IndigoTestContext>('Redeem CDP w/ interest, redemption smaller than interest', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    context.emulator.awaitSlot(6500);

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const cdpBeforeRedemption = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      cdpBeforeRedemption.datum,
      context,
      sysParams,
    );

    const orefs = await findAllNecessaryOrefs(
      context.lucid,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

    const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
      context.lucid,
      orefs.collateralAsset,
    );

    const [__, interestCollectorValChange] =
      await getValueChangeAtAddressAfterAction(
        context.lucid,
        createScriptAddress(
          context.lucid.config().network!,
          sysParams.validatorHashes.interestCollectionHash,
        ),
        async () =>
          runAndAwaitTx(
            context.lucid,
            redeemCdp(
              1000n,
              cdpBeforeRedemption.utxo,
              orefs.iasset.utxo,
              orefs.collateralAsset.utxo,
              priceOracleUtxo,
              orefs.interestOracleUtxo,
              orefs.interestCollectorUtxo,
              orefs.treasuryUtxo,
              orefs.govUtxo,
              sysParams,
              context.lucid,
              context.emulator.slot,
            ),
          ),
      );

    const iUsdAc = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const interestPaid = assetClassValueOf(interestCollectorValChange, iUsdAc);

    assert(
      interestPaid > 0 && interestPaid == cdpOwedInterest,
      'Expected interest paid to collector',
    );

    const cdpAfterRedemption = await findCdp(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      pkh.hash,
      skh,
    );

    expect(
      cdpBeforeRedemption.datum.mintedAmt < cdpAfterRedemption.datum.mintedAmt,
    ).toBeTruthy();
  });

  test<IndigoTestContext>('Redeem CDP 1', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [ownerPkh, ownerSkh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    {
      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 199, max: 200 },
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      await runAndAwaitTx(
        context.lucid,
        feedPriceOracleTx(
          context.lucid,
          priceOracleUtxo!,
          { numerator: 125n, denominator: 100n },
          iusdAssetInfo.collateralAssets[0].oracleParams!,
          context.emulator.slot,
        ),
      );

      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 159, max: 160 },
      );
    }

    {
      // Let admin do the redemption (i.e. not the CDP's owner)
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const ownCdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );
      const redemptionCdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        ownerPkh.hash,
        ownerSkh,
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      await runAndAwaitTx(
        context.lucid,
        redeemCdp(
          ownCdp.datum.mintedAmt,
          redemptionCdp.utxo,
          orefs.iasset.utxo,
          orefs.collateralAsset.utxo,
          priceOracleUtxo,
          orefs.interestOracleUtxo,
          orefs.interestCollectorUtxo,
          orefs.treasuryUtxo,
          orefs.govUtxo,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
      );
    }

    {
      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 199, max: 201 },
      );
    }
  });

  test<IndigoTestContext>('Redeem CDP with 100% CR', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        500_000_000n,
        200_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [ownerPkh, ownerSkh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        100_000_000n,
        50_000_000n,
      ),
    );

    {
      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 199, max: 200 },
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      await runAndAwaitTx(
        context.lucid,
        feedPriceOracleTx(
          context.lucid,
          priceOracleUtxo!,
          { numerator: 1_999_990n, denominator: 1_000_000n },
          iusdAssetInfo.collateralAssets[0].oracleParams!,
          context.emulator.slot,
        ),
      );

      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 100, max: 101 },
      );
    }

    {
      // Let admin do the redemption (i.e. not the CDP's owner)
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const redemptionCdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        ownerPkh.hash,
        ownerSkh,
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      await runAndAwaitTx(
        context.lucid,
        redeemCdp(
          redemptionCdp.datum.mintedAmt,
          redemptionCdp.utxo,
          orefs.iasset.utxo,
          orefs.collateralAsset.utxo,
          priceOracleUtxo,
          orefs.interestOracleUtxo,
          orefs.interestCollectorUtxo,
          orefs.treasuryUtxo,
          orefs.govUtxo,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
      );
    }
  });

  test<IndigoTestContext>('Partial redeem CDP when redemptions privatized; partial redemption is NOT paid', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        // With a 0.005% debt minting fee, from 1,005,026 iAsset,
        // 1,000,000 go to admin wallet.
        1_005_026n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [ownerPkh, ownerSkh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    {
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 199, max: 200 },
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      await runAndAwaitTx(
        context.lucid,
        feedPriceOracleTx(
          context.lucid,
          priceOracleUtxo!,
          { numerator: 125n, denominator: 100n },
          iusdAssetInfo.collateralAssets[0].oracleParams!,
          context.emulator.slot,
        ),
      );

      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 159, max: 160 },
      );
    }

    {
      // Let admin do the redemption (i.e. not the CDP's owner)
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const redemptionCdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        ownerPkh.hash,
        ownerSkh,
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      const [_, addedValueInTreasury] =
        await getValueChangeAtAddressAfterAction(
          context.lucid,
          mkTreasuryAddr(context.lucid, sysParams),
          async () =>
            benchmarkAndAwaitTx(
              'CDP - Partial redeem CDP',
              await redeemCdp(
                // All the iAsset received from creating the CDP.
                1_000_000n,
                redemptionCdp.utxo,
                orefs.iasset.utxo,
                orefs.collateralAsset.utxo,
                priceOracleUtxo,
                orefs.interestOracleUtxo,
                orefs.interestCollectorUtxo,
                orefs.treasuryUtxo,
                orefs.govUtxo,
                sysParams,
                context.lucid,
                context.emulator.slot,
              ),
              context.lucid,
              context.emulator,
            ),
        );

      expect(
        lovelacesAmt(addedValueInTreasury) <
          BigInt(sysParams.cdpRedeemParams.partialRedemptionExtraFeeLovelace),
      ).toBeTruthy();
    }

    {
      context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      expect(
        rationalToFloat(
          iusdInitialAssetCfg().collateralAssets[0].redemptionRatio,
        ),
      ).toBe(2.0);

      // Since partial redemption has happened, the CR is not the redemption ratio.
      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 160, max: 190 },
      );
    }
  });

  test<IndigoTestContext>('Redeem CDP with expired oracle fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    await runAndAwaitTx(
      context.lucid,
      feedInterestOracle(
        iusdAssetInfo.collateralAssets[0].interestOracleParams,
        0n,
        context.lucid,
        context.emulator.slot,
        iusdAssetInfo.collateralAssets[0].interestOracleNft,
      ),
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await expectScriptFailure(
      'X',
      runRedeemCdp(
        context,
        sysParams,
        iusdAssetInfo,
        adaAssetClass,
        pkh.hash,
        skh,
      ),
    );
  });

  test<IndigoTestContext>('Redeem CDP with delisted iAsset succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iUsdInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        50_000_000n,
        20_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        10_000_000n,
      ),
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iUsdInfo.iassetTokenNameAscii,
      iUsdInfo.collateralAssets[0].collateralAsset,
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: rationalFromInt(1n) },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    await runAndAwaitTx(
      context.lucid,
      runRedeemCdp(
        context,
        sysParams,
        iUsdInfo,
        collateralAssetA,
        pkh.hash,
        skh,
      ),
    );
  });

  test<IndigoTestContext>('Redeem CDP w/ interest, wrong oracles', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo, ieurAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg(), ieurInitialAssetCfg()],
      context.emulator.slot,
      () => [],
      {
        cdpRedemptionRequiredSignature: fromHex(
          paymentCredentialOf(context.users.admin.address).hash,
        ),
      },
    );

    // Add iAssets to admin's wallet
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        50_000_000n,
        10_000_000n,
      ),
    );

    // Create redeemed CDP from user wallet
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const [pkh, skh] = await addrDetails(context.lucid);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    context.emulator.awaitSlot(6500);

    // Feed price oracle
    await runFeedPriceToOracle(
      context,
      sysParams,
      ieurAssetInfo,
      adaAssetClass,
      { numerator: 125n, denominator: 100n },
    );

    // Let admin do the redemption (i.e. not the CDP's owner)
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await expectScriptFailure(
      'iasset',
      runRedeemCdpWrongOracle(
        context,
        sysParams,
        iusdAssetInfo,
        ieurAssetInfo,
        adaAssetClass,
        pkh.hash,
        skh,
      ),
    );
  });

  test<MyContext>('Freeze CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );
    const [pkh, skh] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    context.emulator.awaitSlot(1000);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    await benchmarkAndAwaitTx(
      'CDP - Freeze CDP; ADA collateral',
      await runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        pkh.hash,
        skh,
      ),
      context.lucid,
      context.emulator,
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      expect(
        frozenCdp.datum.mintedAmt === 10_000_000n &&
          frozenCdp.datum.cdpOwner == null,
        'Expected frozen certain frozen CDP',
      ).toBeTruthy();
    }
  });

  test<MyContext>('Freeze CDP; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );
    const [pkh, skh] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        10_000_000n,
      ),
    );

    context.emulator.awaitSlot(1000);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    await benchmarkAndAwaitTx(
      'CDP - Freeze CDP; non-ADA collateral',
      await runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        pkh.hash,
        skh,
      ),
      context.lucid,
      context.emulator,
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      expect(
        frozenCdp.datum.mintedAmt === 10_000_000n &&
          frozenCdp.datum.cdpOwner == null,
        'Expected frozen certain frozen CDP',
      ).toBeTruthy();
    }
  });

  test<IndigoTestContext>('Freeze CDP with expired oracle fails', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );
    const [pkh, skh] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        10_000_000n,
      ),
    );

    context.emulator.awaitSlot(1000);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    //Await until oracle is expired
    context.emulator.awaitSlot(
      Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) /
        1000,
    );

    await expectScriptFailure(
      'X',
      runFreezeCdp(context, sysParams, 'iUSD', adaAssetClass, pkh.hash, skh),
    );
  });

  test<IndigoTestContext>('Freeze CDP with delisted iAsset succeeds', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    const [pkh, skh] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        10_000_000n,
      ),
    );

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );

    const collateralAssetToUpdate = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
      iusdAssetInfo.collateralAssets[0].collateralAsset,
    );

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(fromText('iUSD')),
          collateralAsset: collateralAssetA,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: {
            Delisted: { price: { numerator: 201n, denominator: 100n } },
          },
          newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft,
          newLiquidationRatio: rationalFromInt(1n),
          newMaintenanceRatio: rationalFromInt(1n),
          newRedemptionRatio: rationalFromInt(100n),
          newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await processSuccessfulProposal(
      pollId,
      null,
      null,
      null,
      null,
      collateralAssetToUpdate.utxo,
      null,
      sysParams,
      context,
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const cdp = await findOwnCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 99, max: 100 },
      );
    }

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(context, sysParams, 'iUSD', collateralAssetA, pkh.hash, skh),
    );
  });

  test<MyContext>('Liquidate CDP; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    // Open CDP
    // This is the position that will get liquidated.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        5_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    // Create Stability Pool account
    await runAndAwaitTx(
      context.lucid,
      runOpenCdpAndCreateSPAccount(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
      ),
    );

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const [pkh, skh] = await addrDetails(context.lucid);
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    // We want user to do the freeze of admin's CDP
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        paymentCredentialOf(context.users.admin.address).hash,
        stakeCredentialOf(context.users.admin.address),
      ),
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      await benchmarkAndAwaitTx(
        'CDP - Liquidate CDP; ADA collateral',
        await liquidateCdp(
          frozenCdp.utxo,
          orefs.stabilityPoolUtxo,
          orefs.interestCollectorUtxo,
          orefs.treasuryUtxo,
          sysParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    // Validate that stability pool iassets amount is 4,999,985
    // (5,000,000 used to burn principal and 15 used to pay interest).
    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
        }) === 4_999_985n,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });

  test<MyContext>('Liquidate CDP; ADA collateral - direct treasury payment', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    // Open CDP
    // This is the position that will get liquidated.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        5_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    // Create Stability Pool account
    await runAndAwaitTx(
      context.lucid,
      runOpenCdpAndCreateSPAccount(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
      ),
    );

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const [pkh, skh] = await addrDetails(context.lucid);
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    // We want user to do the freeze of admin's CDP
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        paymentCredentialOf(context.users.admin.address).hash,
        stakeCredentialOf(context.users.admin.address),
      ),
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      await benchmarkAndAwaitTx(
        'CDP - Liquidate CDP; ADA collateral - direct treasury payment',
        await liquidateCdp(
          frozenCdp.utxo,
          orefs.stabilityPoolUtxo,
          orefs.interestCollectorUtxo,
          // Direct treasury payment
          undefined,
          sysParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    // Validate that stability pool iassets amount is 4,999,985
    // (5,000,000 used to burn principal and 15 used to pay interest).
    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
        }) === 4_999_985n,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });

  test<MyContext>('Liquidate CDP; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    // Open CDP
    // This is the position that will get liquidated.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        5_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    // Create Stability Pool account
    await runAndAwaitTx(
      context.lucid,
      runOpenCdpAndCreateSPAccount(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
      ),
    );

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const [pkh, skh] = await addrDetails(context.lucid);
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    // We want user to do the freeze of admin's CDP
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        paymentCredentialOf(context.users.admin.address).hash,
        stakeCredentialOf(context.users.admin.address),
      ),
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      await benchmarkAndAwaitTx(
        'CDP - Liquidate CDP; non ADA collateral',
        await liquidateCdp(
          frozenCdp.utxo,
          orefs.stabilityPoolUtxo,
          orefs.interestCollectorUtxo,
          orefs.treasuryUtxo,
          sysParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    // Validate that stability pool iassets amount is 4,999,985
    // (5,000,000 used to burn principal and 15 used to pay interest).
    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
        }) === 4_999_985n,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });

  test<MyContext>('Liquidate CDP; non-ADA collateral - direct treasury payment', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    // Open CDP
    // This is the position that will get liquidated.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        5_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    // Create Stability Pool account
    await runAndAwaitTx(
      context.lucid,
      runOpenCdpAndCreateSPAccount(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
      ),
    );

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const [pkh, skh] = await addrDetails(context.lucid);
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    // We want user to do the freeze of admin's CDP
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        paymentCredentialOf(context.users.admin.address).hash,
        stakeCredentialOf(context.users.admin.address),
      ),
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      await benchmarkAndAwaitTx(
        'CDP - Liquidate CDP; non ADA collateral - direct treasury payment',
        await liquidateCdp(
          frozenCdp.utxo,
          orefs.stabilityPoolUtxo,
          orefs.interestCollectorUtxo,
          // Direct treasury payment
          undefined,
          sysParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    // Validate that stability pool iassets amount is 4,999,985
    // (5,000,000 used to burn principal and 15 used to pay interest).
    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
        }) === 4_999_985n,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });

  test<IndigoTestContext>('Partially liquidate CDP; ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    // Open CDP
    // This is the position that will get liquidated.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        10_000_000n,
        5_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        20_000_000n,
        3_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdpAndCreateSPAccount(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        3_000_000n,
      ),
    );

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const [pkh, skh] = await addrDetails(context.lucid);
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    // We want user to do the freeze of admin's CDP
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        adaAssetClass,
        paymentCredentialOf(context.users.admin.address).hash,
        stakeCredentialOf(context.users.admin.address),
      ),
    );

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      await benchmarkAndAwaitTx(
        'CDP - Partially liquidate CDP',
        await liquidateCdp(
          frozenCdp.utxo,
          orefs.stabilityPoolUtxo,
          orefs.interestCollectorUtxo,
          orefs.treasuryUtxo,
          sysParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    // Validate that stability pool is empty.
    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
        }) === 0n,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });

  test<IndigoTestContext>('Partially liquidate CDP; non-ADA collateral', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    // Open CDP
    // This is the position that will get liquidated.
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        10_000_000n,
        5_000_000n,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        20_000_000n,
        3_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      runOpenCdpAndCreateSPAccount(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        3_000_000n,
      ),
    );

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      collateralAssetA,
      { numerator: 18n, denominator: 10n },
    );

    // Validate that CDP CR is in Liquidation Range
    {
      const [pkh, skh] = await addrDetails(context.lucid);
      const cdp = await findCdp(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        pkh.hash,
        skh,
      );

      assertValueInRange(
        await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot),
        { min: 111, max: 112 },
      );
    }

    // We want user to do the freeze of admin's CDP
    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    await runAndAwaitTx(
      context.lucid,
      runFreezeCdp(
        context,
        sysParams,
        'iUSD',
        collateralAssetA,
        paymentCredentialOf(context.users.admin.address).hash,
        stakeCredentialOf(context.users.admin.address),
      ),
    );

    const iassetClass = {
      currencySymbol: fromHex(
        sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    {
      const frozenCdp = matchSingle(
        await findFrozenCDPs(
          context.lucid,
          sysParams.validatorHashes.cdpHash,
          fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        (_) => new Error('Expected only single frozen CDP'),
      );

      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      await benchmarkAndAwaitTx(
        'CDP - Partially liquidate CDP; non-ADA collateral',
        await liquidateCdp(
          frozenCdp.utxo,
          orefs.stabilityPoolUtxo,
          orefs.interestCollectorUtxo,
          orefs.treasuryUtxo,
          sysParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
      );

      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, iassetClass) === 0n,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });

  test<MyContext>('Merge 3 CDPs; ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await runCreateAndFreezeCdps(
      context,
      sysParams,
      iusdAssetInfo,
      3,
      'iUSD',
      adaAssetClass,
    );

    const frozenCdps = await findFrozenCDPs(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await benchmarkAndAwaitTx(
      'CDP - Merge 3 CDPs; ADA collateral',
      await mergeCdps(
        frozenCdps.map((cdp) => cdp.utxo),
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Merge 3 CDPs; non-ADA collateral', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [
        {
          ...iusdInitialAssetCfg(),
          collateralAssets: [mkBaseCollateralAsset(collateralAssetA)],
        },
      ],
      context.emulator.slot,
    );

    await runCreateAndFreezeCdps(
      context,
      sysParams,
      iusdAssetInfo,
      3,
      'iUSD',
      collateralAssetA,
    );

    const frozenCdps = await findFrozenCDPs(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await benchmarkAndAwaitTx(
      'CDP - Merge 3 CDPs; non-ADA collateral',
      await mergeCdps(
        frozenCdps.map((cdp) => cdp.utxo),
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Merge 5 CDPs', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await createUtxoAtTreasury(mkLovelacesOf(2_000_000n), sysParams, context);

    await runCreateAndFreezeCdps(
      context,
      sysParams,
      iusdAssetInfo,
      5,
      'iUSD',
      adaAssetClass,
    );

    const frozenCdps = await findFrozenCDPs(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await benchmarkAndAwaitTx(
      'CDP - Merge 5 CDPs',
      await mergeCdps(
        frozenCdps.map((cdp) => cdp.utxo),
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Merge 7 CDPs', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await createMultipleUtxosAtTreasury(
      mkLovelacesOf(2_000_000n),
      3n,
      sysParams,
      context,
    );

    await runCreateAndFreezeCdps(
      context,
      sysParams,
      iusdAssetInfo,
      7,
      'iUSD',
      adaAssetClass,
    );

    const frozenCdps = await findFrozenCDPs(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await benchmarkAndAwaitTx(
      'CDP - Merge 7 CDPs',
      await mergeCdps(
        frozenCdps.map((cdp) => cdp.utxo),
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Merge 9 CDPs', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await createMultipleUtxosAtTreasury(
      mkLovelacesOf(2_000_000n),
      5n,
      sysParams,
      context,
    );

    await runCreateAndFreezeCdps(
      context,
      sysParams,
      iusdAssetInfo,
      9,
      'iUSD',
      adaAssetClass,
    );

    const frozenCdps = await findFrozenCDPs(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await benchmarkAndAwaitTx(
      'CDP - Merge 9 CDPs',
      await mergeCdps(
        frozenCdps.map((cdp) => cdp.utxo),
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<IndigoTestContext>('Merge CDPs and liquidate merged', async (context: IndigoTestContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

    await runCreateAndFreezeCdps(
      context,
      sysParams,
      iusdAssetInfo,
      3,
      'iUSD',
      adaAssetClass,
    );

    const frozenCdps = await findFrozenCDPs(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await runAndAwaitTx(
      context.lucid,
      mergeCdps(
        frozenCdps.map((cdp) => cdp.utxo),
        sysParams,
        context.lucid,
      ),
    );

    ///////////////////////////
    // Liquidation
    ///////////////////////////

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);

    const spDepositedAmount = 20_000_000n;

    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
        context.lucid,
        orefs.collateralAsset,
      );

      await runAndAwaitTx(
        context.lucid,
        openCdp(
          150_000_000n,
          2n * spDepositedAmount,
          sysParams,
          orefs.cdpCreatorUtxo,
          orefs.iasset.utxo,
          orefs.collateralAsset.utxo,
          priceOracleUtxo,
          orefs.interestOracleUtxo,
          orefs.treasuryUtxo,
          context.lucid,
          context.emulator.slot,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          spDepositedAmount,
          sysParams,
          context.lucid,
        ),
      );
    }

    // Process the create account request
    await runAndAwaitTx(
      context.lucid,
      runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user.address).hash,
      ),
    );

    const frozenCdp = matchSingle(
      await findFrozenCDPs(
        context.lucid,
        sysParams.validatorHashes.cdpHash,
        fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      ),
      (_) => new Error('Expected only single frozen CDP'),
    );

    expect(
      frozenCdp.datum.mintedAmt === 18_000_000n &&
        frozenCdp.datum.cdpOwner == null,
      'Expected frozen certain frozen CDP',
    ).toBeTruthy();

    const orefs = await findAllNecessaryOrefs(
      context.lucid,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

    await benchmarkAndAwaitTx(
      'CDP - Liquidate merged CDP',
      await liquidateCdp(
        frozenCdp.utxo,
        orefs.stabilityPoolUtxo,
        orefs.interestCollectorUtxo,
        orefs.treasuryUtxo,
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );

    const cdpOwedInterest = await calculateCdpInterest(
      frozenCdp.datum,
      context,
      sysParams,
    );
    assert(cdpOwedInterest > 0, 'There is some owed interest');

    {
      const orefs = await findAllNecessaryOrefs(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      // Validate that stability pool iassets amount is decreased by
      // the burned iAssets and the iAssets used to pay interest.
      expect(
        assetClassValueOf(orefs.stabilityPoolUtxo.assets, {
          currencySymbol: fromHex(
            sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
          ),
          tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
        }) ===
          spDepositedAmount - 18_000_000n - cdpOwedInterest,
        'Expected different stability pool iassets amount',
      ).toBeTruthy();
    }
  });
});
