import { assert, beforeEach, describe, expect, test } from 'vitest';
import {
  Emulator,
  EmulatorAccount,
  fromHex,
  fromText,
  generateEmulatorAccount,
  Lucid,
  paymentCredentialOf,
  addAssets,
} from '@lucid-evolution/lucid';
import {
  adjustRob,
  cancelRob,
  claimRob,
  openRob,
} from '../../src/contracts/rob/transactions';
import { findAllRobs, findSingleRob } from './rob-queries';
import { addrDetails } from '../../src/utils/lucid-utils';
import {
  IndigoTestContext,
  LucidContext,
  repeat,
  runAndAwaitTx,
  runAndAwaitTxBuilder,
} from '../test-helpers';
import {
  createProposal,
  fromSystemParamsAsset,
  MIN_ROB_COLLATERAL_AMT,
  robAmtToSpend,
  robCollateralAmtToSpend,
} from '../../src';
import { strictEqual } from 'assert';
import {
  adaAssetClass,
  AssetClass,
  assetClassValueOf,
  lovelacesAmt,
  mkAssetsOf,
  mkLovelacesOf,
} from '@3rd-eye-labs/cardano-offchain-common';
import {
  iusdInitialAssetCfg,
  mkBaseCollateralAsset,
} from '../mock/assets-mock';
import { init } from '../endpoints/initialize';
import {
  findAllNecessaryOrefs,
  findOwnCdpNew,
  findPriceOracleFromCollateralAsset,
  findRandomCdpCreatorNew,
} from '../cdp/cdp-queries';
import {
  redeemWithCdpAdjust,
  redeemWithCdpClose,
  redeemWithCdpCreate,
  testRedeemRob,
} from './transactions-mutated';
import { expectScriptFailure, expectValue } from '../utils/asserts';
import { findGov } from '../gov/governance-queries';
import {
  findCollateralAsset,
  findCollateralAssetNew,
  findIAssetNew,
} from '../queries/iasset-queries';
import { processSuccessfulProposal } from '../gov/actions';
import { runOpenCdp } from '../cdp/actions';
import {
  runFeedPriceToOracle,
  waitForOracleExpiration,
} from '../price-oracle/actions';
import { runRedeemRob } from './actions';
import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers';
import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
import { sendValueTo, totalValueAtAddress } from '../utils';
import { MAINNET_PROTOCOL_PARAMETERS } from '../indigo-test-helpers';
import {
  Rational,
  rationalDiv,
  rationalFloor,
  rationalFromInt,
  rationalMul,
} from '../../src/types/rational';

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

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

describe('ROB', () => {
  beforeEach<MyContext>(async (context: MyContext) => {
    context.users = {
      admin: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(100_000_000_000_000n),
          mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
        ),
      ),
      user1: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(1_000_000_000_000n),
          mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
        ),
      ),
      user2: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(1_000_000_000_000n),
          mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
        ),
      ),
    };

    context.emulator = new Emulator(
      Object.values(context.users),
      MAINNET_PROTOCOL_PARAMETERS,
    );
    context.lucid = await Lucid(context.emulator, 'Custom');
  });

  describe('Composite Txs', () => {
    describe('redeem with CDP Adjust', () => {
      test<MyContext>('ROB buy orders redeem with CDP deposit and mint (ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 8; // Adjusted for Pyth updates

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              20_000_000n,
              {
                BuyIAssetOrder: {
                  collateralAsset: adaAssetClass,
                  maxPrice: rationalFromInt(1n),
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            100_000_000n,
            500_000n,
          ),
        );

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

        const SINGLE_REDEMPTION_AMT = 1_000_000n;

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} buy orders redeem with CDP deposit and mint`,
          await redeemWithCdpAdjust(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_REDEMPTION_AMT]),
            5n,
            BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
            (await findOwnCdpNew(context.lucid, sysParams)).utxo,
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.treasuryUtxo,
            orefs.interestCollectorUtxo,
            context.emulator.slot,
            context.lucid,
            sysParams,
          ),
          context.lucid,
          context.emulator,
        );
      });

      test<MyContext>('ROB buy orders redeem with CDP deposit and mint (non ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 7; // Adjusted for Pyth updates

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              20_000_000n,
              {
                BuyIAssetOrder: {
                  collateralAsset: collateralAssetA,
                  maxPrice: rationalFromInt(1n),
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

        const allCollateralInWallet = assetClassValueOf(
          await totalValueAtAddress(context.lucid, context.users.user1.address),
          collateralAssetA,
        );

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            allCollateralInWallet,
            500_000n,
          ),
        );

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

        const SINGLE_PAYOUT_IASSET_AMT = 1_000_000n;

        const collateralAdjustment =
          BigInt(robs_count) *
          (SINGLE_PAYOUT_IASSET_AMT -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              SINGLE_PAYOUT_IASSET_AMT,
            ));

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} buy orders redeem with CDP deposit and mint (non ADA case)`,
          await redeemWithCdpAdjust(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_PAYOUT_IASSET_AMT]),
            // Since there's no more collateral in wallet (because all used as collateral before), this must be coming from redemptions.
            collateralAdjustment,
            BigInt(robs_count) * SINGLE_PAYOUT_IASSET_AMT,
            (await findOwnCdpNew(context.lucid, sysParams)).utxo,
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.treasuryUtxo,
            orefs.interestCollectorUtxo,
            context.emulator.slot,
            context.lucid,
            sysParams,
          ),
          context.lucid,
          context.emulator,
        );

        const res = await findOwnCdpNew(context.lucid, sysParams);

        expect(
          assetClassValueOf(res.utxo.assets, collateralAssetA),
          'Expected different CDP collateral',
        ).toEqual(allCollateralInWallet + collateralAdjustment);
      });

      test<MyContext>('ROB sell orders redeem with CDP deposit and burn (ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 8;
        const ROB_DEPOSIT = 20_000_000n;

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            1_000_000_000n,
            ROB_DEPOSIT * BigInt(robs_count) +
              // Should cover the minting fee
              1_000_000n,
          ),
        );

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              ROB_DEPOSIT,
              {
                SellIAssetOrder: {
                  allowedCollateralAssets: [
                    [adaAssetClass, rationalFromInt(1n)],
                  ],
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

        const INITIAL_MINT = 20_000_000n;

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            100_000_000n,
            INITIAL_MINT,
          ),
        );

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

        const SINGLE_REDEMPTION_AMT = 1_000_000n;

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} sell orders redeem with CDP deposit and burn`,
          await redeemWithCdpAdjust(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_REDEMPTION_AMT]),
            5n,
            -BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
            (await findOwnCdpNew(context.lucid, sysParams)).utxo,
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.treasuryUtxo,
            orefs.interestCollectorUtxo,
            context.emulator.slot,
            context.lucid,
            sysParams,
          ),
          context.lucid,
          context.emulator,
        );

        const ownCdp = await findOwnCdpNew(context.lucid, sysParams);

        expect(ownCdp.datum.mintedAmt, 'unexpected minted amt').toEqual(
          INITIAL_MINT - BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
        );
      });

      test<MyContext>('ROB sell orders redeem with CDP withdraw and burn (non ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 7; // TODO: Verify against Pyth feeds
        const ROB_DEPOSIT = 20_000_000n;

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            1_000_000_000n,
            ROB_DEPOSIT * BigInt(robs_count) +
              // Should cover the minting fee
              1_000_000n,
          ),
        );

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              ROB_DEPOSIT,
              {
                SellIAssetOrder: {
                  allowedCollateralAssets: [
                    [collateralAssetA, rationalFromInt(1n)],
                  ],
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

        const allCollateralInWallet = assetClassValueOf(
          await totalValueAtAddress(context.lucid, context.users.user1.address),
          collateralAssetA,
        );

        const INITIAL_MINT = 20_000_000n;

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            allCollateralInWallet,
            INITIAL_MINT,
          ),
        );

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

        const SINGLE_REDEMPTION_AMT = 1_000_000n;
        const reimbursementFee = calculateFeeFromRatio(
          orefs.iasset.datum.redemptionReimbursementRatio,
          SINGLE_REDEMPTION_AMT,
        );

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} sell orders redeem with CDP withdraw and burn (non ADA case)`,
          await redeemWithCdpAdjust(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_REDEMPTION_AMT]),
            // Withdrawing the amount to pay for redemptions
            -BigInt(robs_count) * (SINGLE_REDEMPTION_AMT + reimbursementFee),
            // redeeming iassets, and using them to burn
            -BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
            (await findOwnCdpNew(context.lucid, sysParams)).utxo,
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.treasuryUtxo,
            orefs.interestCollectorUtxo,
            context.emulator.slot,
            context.lucid,
            sysParams,
          ),
          context.lucid,
          context.emulator,
        );

        const ownCdp = await findOwnCdpNew(context.lucid, sysParams);

        expect(ownCdp.datum.mintedAmt, 'unexpected minted amt').toEqual(
          INITIAL_MINT - BigInt(robs_count) * SINGLE_REDEMPTION_AMT,
        );
        expect(
          assetClassValueOf(ownCdp.utxo.assets, collateralAssetA),
          'unexpected collateral amt',
        ).toEqual(
          allCollateralInWallet -
            BigInt(robs_count) * (SINGLE_REDEMPTION_AMT + reimbursementFee),
        );
      });
    });

    describe('redeem with CDP close', () => {
      test<MyContext>('ROB sell orders redeem with CDP close (ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 5;
        const ROB_DEPOSIT = 20_000_000n;

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            1_000_000_000n,
            ROB_DEPOSIT * BigInt(robs_count) +
              // Should cover the minting fee
              1_000_000n,
          ),
        );

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              ROB_DEPOSIT,
              {
                SellIAssetOrder: {
                  allowedCollateralAssets: [
                    [adaAssetClass, rationalFromInt(1n)],
                  ],
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

        const SINGLE_PAYOUT_COLLATERAL_AMT = 1_000_000n;

        const mintAmt =
          BigInt(robs_count) *
          (SINGLE_PAYOUT_COLLATERAL_AMT -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              SINGLE_PAYOUT_COLLATERAL_AMT,
            ));

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            100_000_000n,
            mintAmt,
          ),
        );

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

        await sendValueTo(
          context.users.user2.address,
          mkAssetsOf(
            iassetAc,
            assetClassValueOf(
              await totalValueAtAddress(
                context.lucid,
                context.users.user1.address,
              ),
              iassetAc,
            ),
          ),
          context.lucid,
        );

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

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} sell orders redeem with CDP close (ADA case)`,
          await redeemWithCdpClose(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_PAYOUT_COLLATERAL_AMT]),
            (await findOwnCdpNew(context.lucid, sysParams)).utxo,
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.interestCollectorUtxo,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        );
      });

      test<MyContext>('ROB sell orders redeem with CDP close (non ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 4;
        const ROB_DEPOSIT = 20_000_000n;

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            1_000_000_000n,
            ROB_DEPOSIT * BigInt(robs_count) +
              // Should cover the minting fee
              1_000_000n,
          ),
        );

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              ROB_DEPOSIT,
              {
                SellIAssetOrder: {
                  allowedCollateralAssets: [
                    [collateralAssetA, rationalFromInt(1n)],
                  ],
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

        const SINGLE_PAYOUT_COLLATERAL_AMT = 1_000_000n;

        const mintAmt =
          BigInt(robs_count) *
          (SINGLE_PAYOUT_COLLATERAL_AMT -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              SINGLE_PAYOUT_COLLATERAL_AMT,
            ));

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            100_000_000n,
            mintAmt,
          ),
        );

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

        await sendValueTo(
          context.users.user2.address,
          mkAssetsOf(
            iassetAc,
            assetClassValueOf(
              await totalValueAtAddress(
                context.lucid,
                context.users.user1.address,
              ),
              iassetAc,
            ),
          ),
          context.lucid,
        );

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

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} sell orders redeem with CDP close (non ADA case)`,
          await redeemWithCdpClose(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_PAYOUT_COLLATERAL_AMT]),
            (await findOwnCdpNew(context.lucid, sysParams)).utxo,
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.interestCollectorUtxo,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        );
      });
    });

    describe('redeem with CDP create', () => {
      test<MyContext>('ROB buy orders redeem with CDP create (ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 4;

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              20_000_000n,
              {
                BuyIAssetOrder: {
                  collateralAsset: adaAssetClass,
                  maxPrice: rationalFromInt(1n),
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

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

        const SINGLE_PAYOUT_IASSET_AMT = 1_000_000n;

        const mintedAmt =
          BigInt(robs_count) * SINGLE_PAYOUT_IASSET_AMT +
          // This is extra that minting fee is paid from and user keeps what we validate after this Tx.
          SINGLE_PAYOUT_IASSET_AMT;

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

        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} buy orders redeem with CDP create (ADA case)`,
          await redeemWithCdpCreate(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_PAYOUT_IASSET_AMT]),
            100_000_000n,
            // everything that gets minted is sent to ROB
            mintedAmt,
            sysParams,
            await findRandomCdpCreatorNew(context, sysParams),
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.treasuryUtxo,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        );

        expect(
          assetClassValueOf(
            await totalValueAtAddress(
              context.lucid,
              context.users.user1.address,
            ),
            {
              currencySymbol: fromHex(
                sysParams.robParams.iassetPolicyId.unCurrencySymbol,
              ),
              tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
            },
          ),
          'Unexpected iassets owned by user',
        ).toEqual(
          SINGLE_PAYOUT_IASSET_AMT -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.debtMintingFeeRatio,
              mintedAmt,
            ),
        );
      });

      test<MyContext>('ROB buy orders redeem with CDP create (non ADA case)', async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

        const robs_count = 3;

        await repeat(robs_count, async () => {
          await runAndAwaitTx(
            context.lucid,
            openRob(
              iusdAssetInfo.iassetTokenNameAscii,
              20_000_000n,
              {
                BuyIAssetOrder: {
                  collateralAsset: collateralAssetA,
                  maxPrice: rationalFromInt(1n),
                },
              },
              context.lucid,
              sysParams,
            ),
          );
        });

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

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

        const SINGLE_PAYOUT_IASSET_AMT = 1_000_000n;

        const mintedAmt =
          BigInt(robs_count) * SINGLE_PAYOUT_IASSET_AMT +
          // This is extra that minting fee is paid from and user keeps what we validate after this Tx.
          SINGLE_PAYOUT_IASSET_AMT;
        const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
          context.lucid,
          orefs.collateralAsset,
        );
        await benchmarkAndAwaitTx(
          `ROB composite - ${robs_count} buy orders redeem with CDP create (non ADA case)`,
          await redeemWithCdpCreate(
            (
              await findAllRobs(
                context.lucid,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
              )
            ).map((rob) => [rob.utxo, SINGLE_PAYOUT_IASSET_AMT]),
            // use all as collateral
            assetClassValueOf(
              await totalValueAtAddress(
                context.lucid,
                context.users.user1.address,
              ),
              collateralAssetA,
            ),
            // everything that gets minted is sent to ROB
            mintedAmt,
            sysParams,
            await findRandomCdpCreatorNew(context, sysParams),
            orefs.iasset.utxo,
            orefs.collateralAsset.utxo,
            priceOracleUtxo!,
            orefs.interestOracleUtxo,
            orefs.treasuryUtxo,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        );

        const userVal = await totalValueAtAddress(
          context.lucid,
          context.users.user1.address,
        );
        expect(
          assetClassValueOf(userVal, {
            currencySymbol: fromHex(
              sysParams.robParams.iassetPolicyId.unCurrencySymbol,
            ),
            tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
          }),
          'Unexpected iassets owned by user',
        ).toEqual(
          SINGLE_PAYOUT_IASSET_AMT -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.debtMintingFeeRatio,
              mintedAmt,
            ),
        );

        // Since everything was used as collateral, user owns only the amount after redemption.
        expect(
          assetClassValueOf(userVal, collateralAssetA),
          'Unexpected collateral owned by user',
        ).toEqual(
          BigInt(robs_count) *
            (SINGLE_PAYOUT_IASSET_AMT -
              calculateFeeFromRatio(
                (
                  await findIAssetNew(
                    context,
                    sysParams,
                    iusdAssetInfo.iassetTokenNameAscii,
                  )
                ).datum.redemptionReimbursementRatio,
                SINGLE_PAYOUT_IASSET_AMT,
              )),
        );
      });
    });
  });

  test<MyContext>('adjust order type BUY positive and negative', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    {
      const robBefore = await findSingleRob(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        ownPkh,
      );

      const amtToSpendBefore = robCollateralAmtToSpend(
        robBefore.utxo.assets,
        robBefore.datum.orderType,
      );

      await runAndAwaitTx(
        context.lucid,
        adjustRob(
          context.lucid,
          robBefore.utxo,
          -1_000_000n,
          undefined,
          sysParams,
        ),
      );

      const adjustedUtxo1 = await findSingleRob(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        ownPkh,
      );

      const amtToSpendAfter = robCollateralAmtToSpend(
        adjustedUtxo1.utxo.assets,
        adjustedUtxo1.datum.orderType,
      );

      assert(amtToSpendBefore - amtToSpendAfter === 1_000_000n);

      expect(
        lovelacesAmt(adjustedUtxo1.utxo.assets) >= amtToSpendAfter,
        'Lovelaces to spend has to be smaller than actual lovelaces in UTXO',
      ).toBeTruthy();
    }

    {
      const robBefore = await findSingleRob(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        ownPkh,
      );

      const amtToSpendBefore = robCollateralAmtToSpend(
        robBefore.utxo.assets,
        robBefore.datum.orderType,
      );

      await runAndAwaitTx(
        context.lucid,
        adjustRob(
          context.lucid,
          robBefore.utxo,
          5_000_000n,
          undefined,
          sysParams,
        ),
      );

      const adjustedUtxo2 = await findSingleRob(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        ownPkh,
      );

      const amtToSpendAfter = robCollateralAmtToSpend(
        adjustedUtxo2.utxo.assets,
        adjustedUtxo2.datum.orderType,
      );

      strictEqual(amtToSpendAfter - amtToSpendBefore, 5_000_000n);

      expect(
        lovelacesAmt(adjustedUtxo2.utxo.assets) >= amtToSpendAfter,
        'Lovelaces to spend has to be smaller than actual lovelaces in UTXO',
      ).toBeTruthy();
    }
  });

  test<MyContext>('adjust order type SELL positive and negative', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      findSingleRob(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        ownPkh,
      ).then((rob) =>
        adjustRob(context.lucid, rob.utxo, -1_000_000n, undefined, sysParams),
      ),
    );

    const adjustedUtxo1 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    assert(
      robAmtToSpend(adjustedUtxo1.utxo.assets, adjustedUtxo1.datum.orderType, {
        currencySymbol: fromHex(
          sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
        ),
        tokenName: adjustedUtxo1.datum.iasset,
      }) ===
        20_000_000n - 1_000_000n,
    );

    expect(
      assetClassValueOf(adjustedUtxo1.utxo.assets, iassetAc) ===
        robAmtToSpend(
          adjustedUtxo1.utxo.assets,
          adjustedUtxo1.datum.orderType,
          {
            currencySymbol: fromHex(
              sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
            ),
            tokenName: adjustedUtxo1.datum.iasset,
          },
        ),
      'IAssets to spend has to equal iassets in UTXO',
    ).toBeTruthy();

    await runAndAwaitTx(
      context.lucid,
      adjustRob(
        context.lucid,
        adjustedUtxo1.utxo,
        5_000_000n,
        undefined,
        sysParams,
      ),
    );

    const adjustedUtxo2 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const expectedResultAdaAmt = 20_000_000n - 1_000_000n + 5_000_000n;

    strictEqual(
      robAmtToSpend(adjustedUtxo2.utxo.assets, adjustedUtxo2.datum.orderType, {
        currencySymbol: fromHex(
          sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
        ),
        tokenName: adjustedUtxo2.datum.iasset,
      }),
      expectedResultAdaAmt,
    );

    expect(
      assetClassValueOf(adjustedUtxo2.utxo.assets, iassetAc) >=
        robAmtToSpend(
          adjustedUtxo2.utxo.assets,
          adjustedUtxo2.datum.orderType,
          {
            currencySymbol: fromHex(
              sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
            ),
            tokenName: adjustedUtxo2.datum.iasset,
          },
        ),
      'IAssets to spend has to equal iassets in UTXO',
    ).toBeTruthy();
  });

  test<MyContext>('claim from BUY order type after a redemption', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const redemptionIAssetAmt = 11_000_000n;

    const amtToSpendBefore = robCollateralAmtToSpend(
      robUtxo.utxo.assets,
      robUtxo.datum.orderType,
    );

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, redemptionIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toEqual(redemptionIAssetAmt);

    expect(
      amtToSpendBefore -
        robCollateralAmtToSpend(
          redeemedRob.utxo.assets,
          redeemedRob.datum.orderType,
        ),
      'ROB has wrong number redeemed',
    ).toEqual(
      // Since price is 1
      redemptionIAssetAmt -
        calculateFeeFromRatio(
          (
            await findIAssetNew(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.redemptionReimbursementRatio,
          redemptionIAssetAmt,
        ),
    );

    await runAndAwaitTx(
      context.lucid,
      claimRob(context.lucid, redeemedRob.utxo, sysParams),
    );

    const claimedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(claimedRob.utxo.assets, iassetAc),
      'ROB has to have 0 redemption assets after claim',
    ).toBe(0n);
  });

  test<MyContext>('claim from BUY order type after a redemption when price is NOT 1:1', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    const initialDeposit = 20_000_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        initialDeposit,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const newPrice: Rational = { numerator: 75n, denominator: 100n };
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      newPrice,
    );

    const redemptionIAssetAmt = 7_500_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, redemptionIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const expectedRedeemedAmt = rationalFloor(
      rationalMul(
        rationalFromInt(
          redemptionIAssetAmt -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              redemptionIAssetAmt,
            ),
        ),
        newPrice,
      ),
    );

    expect(
      robCollateralAmtToSpend(
        redeemedRob.utxo.assets,
        redeemedRob.datum.orderType,
      ),
      'Wrong amt to spend in datum',
    ).toEqual(initialDeposit - expectedRedeemedAmt);

    expectValue(
      redeemedRob.utxo.assets,
      'Wrong value after redemption',
    ).toEqual(
      addAssets(
        mkAssetsOf(iassetAc, redemptionIAssetAmt),
        mkLovelacesOf(
          MIN_ROB_COLLATERAL_AMT + initialDeposit - expectedRedeemedAmt,
        ),
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      claimRob(context.lucid, redeemedRob.utxo, sysParams),
    );

    const claimedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(claimedRob.utxo.assets, iassetAc),
      'ROB has to have 0 redemption assets after claim',
    ).toBe(0n);
  });

  test<MyContext>('claim from SELL order type after a redemption when price is NOT 1:1', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    const initialDeposit = 20_000_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        initialDeposit,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expectValue(
      robUtxo.utxo.assets,
      'Wrong ROB value before redemption',
    ).toEqual(
      addAssets(
        mkLovelacesOf(MIN_ROB_COLLATERAL_AMT),
        mkAssetsOf(iassetAc, initialDeposit),
      ),
    );

    const newPrice: Rational = { numerator: 125n, denominator: 100n };
    await runFeedPriceToOracle(
      context,
      sysParams,
      iusdAssetInfo,
      adaAssetClass,
      newPrice,
    );

    const payoutCollateralAmt = 7_500_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutCollateralAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const expectedRedemptionAmt = rationalFloor(
      rationalDiv(
        rationalFromInt(
          payoutCollateralAmt -
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              payoutCollateralAmt,
            ),
        ),
        newPrice,
      ),
    );

    expect(
      robAmtToSpend(redeemedRob.utxo.assets, redeemedRob.datum.orderType, {
        currencySymbol: fromHex(
          sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
        ),
        tokenName: redeemedRob.datum.iasset,
      }),
      'Wrong amt to spend in datum',
    ).toEqual(initialDeposit - expectedRedemptionAmt);

    expectValue(
      redeemedRob.utxo.assets,
      'Wrong value after redemption',
    ).toEqual(
      addAssets(
        mkAssetsOf(adaAssetClass, MIN_ROB_COLLATERAL_AMT + payoutCollateralAmt),
        mkAssetsOf(iassetAc, initialDeposit - expectedRedemptionAmt),
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      claimRob(context.lucid, redeemedRob.utxo, sysParams),
    );
  });

  test<MyContext>('redemption of BUY order type when limit price not met fails', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: { numerator: 9n, denominator: 10n },
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    await expectScriptFailure(
      'Max price exceeded',
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, 11_000_000n]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );
  });

  test<MyContext>('claim ADA collateral from SELL order type after a redemption', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    const payoutCollateralAmt = 11_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutCollateralAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    // Price is 1
    const expectedRedemptionAmt =
      payoutCollateralAmt -
      calculateFeeFromRatio(
        (
          await findIAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.redemptionReimbursementRatio,
        payoutCollateralAmt,
      );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(20_000_000n - expectedRedemptionAmt);

    await runAndAwaitTx(
      context.lucid,
      claimRob(context.lucid, redeemedRob.utxo, sysParams),
    );

    const claimedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(claimedRob.utxo.assets, adaAssetClass),
      'ROB has to have 0 redemption assets after claim',
    ).toBe(MIN_ROB_COLLATERAL_AMT);
  });

  test<MyContext>('redemption of SELL order type when limit price not met fails', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [
              [adaAssetClass, { numerator: 11n, denominator: 10n }],
            ],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    await expectScriptFailure(
      'Min price not satisfied',
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, 11_000_000n]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );
  });

  test<MyContext>('claim 2 collateral assets from SELL order type after 2 redemptions', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    const iassetAC: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [
              [adaAssetClass, rationalFromInt(1n)],
              [collateralAssetA, rationalFromInt(1n)],
            ],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const payoutCollateralAmt = 2_000_000n;

    const expectedRedemptionAmt =
      payoutCollateralAmt -
      calculateFeeFromRatio(
        (
          await findIAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.redemptionReimbursementRatio,
        payoutCollateralAmt,
      );

    /**********
     * First redemption of ROB using ADA as payout
     ***********/

    {
      await runAndAwaitTx(
        context.lucid,
        runRedeemRob(
          context,
          sysParams,
          [
            [
              await findSingleRob(
                context,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
                ownPkh,
              ),
              payoutCollateralAmt,
            ],
          ],
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          context.emulator.slot,
        ),
      );

      expect(
        assetClassValueOf(
          (
            await findSingleRob(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              ownPkh,
            )
          ).utxo.assets,
          iassetAC,
        ),
        'ROB has wrong number of iassets after redemption',
      ).toBe(20_000_000n - expectedRedemptionAmt);
    }

    /**********
     * Second redemption of ROB using Collateral A as payout
     ***********/

    {
      await runAndAwaitTx(
        context.lucid,
        runRedeemRob(
          context,
          sysParams,
          [
            [
              await findSingleRob(
                context,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
                ownPkh,
              ),
              payoutCollateralAmt,
            ],
          ],
          iusdAssetInfo.iassetTokenNameAscii,
          collateralAssetA,
          context.emulator.slot,
        ),
      );

      expectValue(
        (
          await findSingleRob(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            ownPkh,
          )
        ).utxo.assets,
        'Wrong ROB value after 2 redemptions',
      ).toEqual(
        addAssets(
          mkLovelacesOf(MIN_ROB_COLLATERAL_AMT + payoutCollateralAmt),
          mkAssetsOf(collateralAssetA, payoutCollateralAmt),
          mkAssetsOf(iassetAC, 20_000_000n - 2n * expectedRedemptionAmt),
        ),
      );
    }

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    await runAndAwaitTx(
      context.lucid,
      claimRob(context.lucid, redeemedRob.utxo, sysParams),
    );

    const claimedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(claimedRob.utxo.assets, adaAssetClass),
      'ROB has to have 0 redemption assets after claim',
    ).toBe(MIN_ROB_COLLATERAL_AMT);
  });

  test<MyContext>('claim using adjust after a redemption', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const payoutIAssetAmt = 11_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(payoutIAssetAmt);

    await runAndAwaitTx(
      context.lucid,
      adjustRob(
        context.lucid,
        redeemedRob.utxo,
        -1_000_000n,
        undefined,
        sysParams,
      ),
    );

    const adjustedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(adjustedRob.utxo.assets, iassetAc),
      'ROB has to have 0 redemption assets after adjust',
    ).toBe(0n);

    // Since price is 1
    expect(
      robCollateralAmtToSpend(
        adjustedRob.utxo.assets,
        adjustedRob.datum.orderType,
      ),
    ).toEqual(
      // 20mil start, -1mil adjusted, rest redeemed
      20_000_000n -
        1_000_000n -
        payoutIAssetAmt +
        calculateFeeFromRatio(
          (
            await findIAssetNew(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.redemptionReimbursementRatio,
          payoutIAssetAmt,
        ),
    );
  });

  test<MyContext>('single redemption and cancel', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const payoutIAssetAmt = 11_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(payoutIAssetAmt);

    await runAndAwaitTx(
      context.lucid,
      cancelRob(redeemedRob.utxo, sysParams, context.lucid),
    );
  });

  test<MyContext>('redeem, redeem again and cancel', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        40_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const payoutIAssetAmt = 11_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(payoutIAssetAmt);

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[redeemedRob, payoutIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const closableRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    strictEqual(
      assetClassValueOf(closableRob.utxo.assets, iassetAc),
      payoutIAssetAmt * 2n,
      'ROB has wrong number of iassets after 2 redemptions',
    );

    await runAndAwaitTx(
      context.lucid,
      cancelRob(closableRob.utxo, sysParams, context.lucid),
    );
  });

  test<MyContext>('multi redemption buy ROBs case (2 ROBs)', async (context: MyContext) => {
    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,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    const initialDeposit = 20_000_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        initialDeposit,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

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

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        initialDeposit,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo1 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.admin.address),
    );
    const robUtxo2 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.user1.address),
    );

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

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [
          [robUtxo1, 10_000_000n],
          [robUtxo2, 11_000_000n],
        ],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const resultRobUtxo1 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.admin.address),
    );
    const resultRobUtxo2 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.user1.address),
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expectValue(resultRobUtxo1.utxo.assets, 'Wrong ROB1 value').toEqual(
      addAssets(
        mkLovelacesOf(
          MIN_ROB_COLLATERAL_AMT +
            initialDeposit -
            10_000_000n +
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              10_000_000n,
            ),
        ),
        mkAssetsOf(iassetAc, 10_000_000n),
      ),
    );
    expectValue(resultRobUtxo2.utxo.assets, 'Wrong ROB2 value').toEqual(
      addAssets(
        mkLovelacesOf(
          MIN_ROB_COLLATERAL_AMT +
            initialDeposit -
            11_000_000n +
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              11_000_000n,
            ),
        ),
        mkAssetsOf(iassetAc, 11_000_000n),
      ),
    );
  });

  test<MyContext>('multi redemption sell ROBs case (2 ROBs)', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const initialDeposit = 20_000_000n;

    /**********
     * user 1 opens ROB
     ***********/
    {
      context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          100_000_000n,
          30_000_000n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        openRob(
          iusdAssetInfo.iassetTokenNameAscii,
          initialDeposit,
          {
            SellIAssetOrder: {
              allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
            },
          },
          context.lucid,
          sysParams,
        ),
      );
    }

    /**********
     * user 2 opens ROB
     ***********/
    {
      context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          100_000_000n,
          30_000_000n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        openRob(
          iusdAssetInfo.iassetTokenNameAscii,
          initialDeposit,
          {
            SellIAssetOrder: {
              allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
            },
          },
          context.lucid,
          sysParams,
        ),
      );
    }

    const robUtxo1 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.user1.address),
    );
    const robUtxo2 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.user2.address),
    );

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

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [
          [robUtxo1, 10_000_000n],
          [robUtxo2, 11_000_000n],
        ],
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
    );

    const resultRobUtxo1 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.user1.address),
    );
    const resultRobUtxo2 = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      paymentCredentialOf(context.users.user2.address),
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expectValue(resultRobUtxo1.utxo.assets, 'Wrong ROB1 value').toEqual(
      addAssets(
        mkLovelacesOf(MIN_ROB_COLLATERAL_AMT + 10_000_000n),
        mkAssetsOf(
          iassetAc,
          initialDeposit -
            10_000_000n +
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              10_000_000n,
            ),
        ),
      ),
    );

    expectValue(resultRobUtxo2.utxo.assets, 'Wrong ROB2 value').toEqual(
      addAssets(
        mkLovelacesOf(MIN_ROB_COLLATERAL_AMT + 11_000_000n),
        mkAssetsOf(
          iassetAc,
          initialDeposit -
            11_000_000n +
            calculateFeeFromRatio(
              (
                await findIAssetNew(
                  context,
                  sysParams,
                  iusdAssetInfo.iassetTokenNameAscii,
                )
              ).datum.redemptionReimbursementRatio,
              11_000_000n,
            ),
        ),
      ),
    );
  });

  test<MyContext>('multi redemption BUY ROBs case (20 ROBs)', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const initialDeposit = 20_000_000n;

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

    await repeat(19, async () => {
      // Adjsuted for Pyth updates
      await runAndAwaitTx(
        context.lucid,
        openRob(
          iusdAssetInfo.iassetTokenNameAscii,
          initialDeposit,
          {
            BuyIAssetOrder: {
              collateralAsset: adaAssetClass,
              maxPrice: rationalFromInt(1n),
            },
          },
          context.lucid,
          sysParams,
        ),
      );
    });

    const allRobs = await findAllRobs(
      context.lucid,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
    );

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

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await benchmarkAndAwaitTx(
      'ROB - redeem 20 buy orders',
      await runRedeemRob(
        context,
        sysParams,
        allRobs.map((rob) => [rob, 1_000_000n]),
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('multi redemption SELL ROBs case single allowed collateral (18 ROBs)', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const initialDeposit = 5_000_000n;

    context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);
    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        1_000_000_000n,
        initialDeposit * 18n +
          // Should cover the minting fee
          1_000_000n,
      ),
    );

    await repeat(18, async () => {
      await runAndAwaitTx(
        context.lucid,
        openRob(
          iusdAssetInfo.iassetTokenNameAscii,
          initialDeposit,
          {
            SellIAssetOrder: {
              allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
            },
          },
          context.lucid,
          sysParams,
        ),
      );
    });

    const allRobs = await findAllRobs(
      context.lucid,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
    );

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

    await benchmarkAndAwaitTx(
      'ROB - redeem 18 SELL orders, single allowed collateral',
      await runRedeemRob(
        context,
        sysParams,
        allRobs.map((rob) => [rob, 1_000_000n]),
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

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

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const redemptionIAssetAmt = 11_000_000n;

    //Await until oracle is expired
    await waitForOracleExpiration(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

    await expectScriptFailure(
      'X',
      testRedeemRob(
        [[robUtxo.utxo, redemptionIAssetAmt]],
        await findPriceOracleFromCollateralAsset(
          context.lucid,
          await findCollateralAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
          ),
        ),
        (
          await findIAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).utxo,
        (
          await findCollateralAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
          )
        ).utxo,
        context.lucid,
        context.emulator.slot,
        sysParams,
      ),
    );
  });

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

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(20_000_000n);

    const redemptionIAssetAmt = 11_000_000n;

    //Await until oracle is expired
    await waitForOracleExpiration(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

    await expectScriptFailure(
      'X',
      testRedeemRob(
        [[robUtxo.utxo, redemptionIAssetAmt]],
        await findPriceOracleFromCollateralAsset(
          context.lucid,
          await findCollateralAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
          ),
        ),
        (
          await findIAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).utxo,
        (
          await findCollateralAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
          )
        ).utxo,
        context.lucid,
        context.emulator.slot,
        sysParams,
      ),
    );
  });

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

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
        100_000_000n,
        30_000_000n,
      ),
    );

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        20_000_000n,
        {
          BuyIAssetOrder: {
            collateralAsset: adaAssetClass,
            maxPrice: rationalFromInt(1n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const redemptionIAssetAmt = 11_000_000n;

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

    const collateralAssetToModify = await findCollateralAsset(
      context.lucid,
      sysParams,
      fromSystemParamsAsset(sysParams.cdpCreatorParams.collateralAssetAuthTk),
      iusdAssetInfo.iassetTokenNameAscii,
      adaAssetClass,
    );

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

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

    await runAndAwaitTxBuilder(context.lucid, tx);

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

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

      await expectScriptFailure(
        'W',
        testRedeemRob(
          [[robUtxo.utxo, redemptionIAssetAmt]],
          undefined,
          orefs.iasset.utxo,
          orefs.collateralAsset.utxo,
          context.lucid,
          context.emulator.slot,
          sysParams,
        ),
      );
    }
  });

  test<MyContext>('BUY order redemption, collateral with more decimal digits', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const extraDecimals = 2n;

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        10_000_000_000n,
        30_000_000n,
      ),
    );

    const robOrderAmount = 2_000_000_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        robOrderAmount,
        {
          BuyIAssetOrder: {
            collateralAsset: collateralAssetA,
            maxPrice: rationalFromInt(100n),
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const payoutIAssetAmt = 11_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(payoutIAssetAmt);

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
      'ROB has wrong number of collateral asset after redemption',
    ).toBe(
      robOrderAmount -
        (payoutIAssetAmt -
          rationalFloor(
            rationalMul(
              rationalFromInt(payoutIAssetAmt),
              iusdInitialAssetCfg().redemptionReimbursementRatio,
            ),
          )) *
          10n ** extraDecimals,
    );
  });

  test<MyContext>('BUY order redemption, collateral with less decimal digits', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const extraDecimals = -2n;

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        10_000_000n,
        300_000_000n,
      ),
    );

    const robOrderAmount = 200_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        robOrderAmount,
        {
          BuyIAssetOrder: {
            collateralAsset: collateralAssetA,
            maxPrice: { numerator: 1n, denominator: 100n },
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, iassetAc),
      'ROB should have no iassets before redemption',
    ).toBe(0n);

    const payoutIAssetAmt = 11_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutIAssetAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(payoutIAssetAmt);

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
      'ROB has wrong number of collateral asset after redemption',
    ).toBe(
      robOrderAmount -
        (payoutIAssetAmt -
          rationalFloor(
            rationalMul(
              rationalFromInt(payoutIAssetAmt),
              iusdInitialAssetCfg().redemptionReimbursementRatio,
            ),
          )) /
          10n ** -extraDecimals,
    );
  });

  test<MyContext>('SELL order redemption, collateral with more decimal digits', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const extraDecimals = 2n;

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        10_000_000_000n,
        30_000_000n,
      ),
    );

    const robOrderAmount = 20_000_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        robOrderAmount,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [
              [collateralAssetA, rationalFromInt(100n)],
            ],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, collateralAssetA),
      'ROB should have no collateral assets before redemption',
    ).toBe(0n);

    const payoutCollateralAmt = 1_100_000_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutCollateralAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(
      robOrderAmount -
        (payoutCollateralAmt -
          rationalFloor(
            rationalMul(
              rationalFromInt(payoutCollateralAmt),
              iusdInitialAssetCfg().redemptionReimbursementRatio,
            ),
          )) /
          10n ** extraDecimals,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
      'ROB has wrong number of collateral asset after redemption',
    ).toBe(payoutCollateralAmt);
  });

  test<MyContext>('SELL order redemption, collateral with less decimal digits', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const extraDecimals = -2n;

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

    const [ownPkh, _] = await addrDetails(context.lucid);

    await runAndAwaitTx(
      context.lucid,
      runOpenCdp(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        10_000_000n,
        300_000_000n,
      ),
    );

    const robOrderAmount = 20_000_000n;

    await runAndAwaitTx(
      context.lucid,
      openRob(
        iusdAssetInfo.iassetTokenNameAscii,
        robOrderAmount,
        {
          SellIAssetOrder: {
            allowedCollateralAssets: [
              [collateralAssetA, { numerator: 1n, denominator: 100n }],
            ],
          },
        },
        context.lucid,
        sysParams,
      ),
    );

    const robUtxo = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    const iassetAc: AssetClass = {
      currencySymbol: fromHex(
        sysParams.robParams.iassetPolicyId.unCurrencySymbol,
      ),
      tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    };

    expect(
      assetClassValueOf(robUtxo.utxo.assets, collateralAssetA),
      'ROB should have no collateral assets before redemption',
    ).toBe(0n);

    const payoutCollateralAmt = 110_000n;

    await runAndAwaitTx(
      context.lucid,
      runRedeemRob(
        context,
        sysParams,
        [[robUtxo, payoutCollateralAmt]],
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetA,
        context.emulator.slot,
      ),
    );

    const redeemedRob = await findSingleRob(
      context,
      sysParams,
      iusdAssetInfo.iassetTokenNameAscii,
      ownPkh,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, iassetAc),
      'ROB has wrong number of iassets after redemption',
    ).toBe(
      robOrderAmount -
        (payoutCollateralAmt -
          rationalFloor(
            rationalMul(
              rationalFromInt(payoutCollateralAmt),
              iusdInitialAssetCfg().redemptionReimbursementRatio,
            ),
          )) *
          10n ** -extraDecimals,
    );

    expect(
      assetClassValueOf(redeemedRob.utxo.assets, collateralAssetA),
      'ROB has wrong number of collateral asset after redemption',
    ).toBe(payoutCollateralAmt);
  });
});
