import { assert, beforeEach, expect, test } from 'vitest';
import {
  LucidContext,
  repeat,
  runAndAwaitTx,
  runAndAwaitTxBuilder,
} from './test-helpers';
import {
  addrDetails,
  annulRequest,
  createProposal,
  requestSpAccountCreation,
  fromSystemParamsAsset,
  MAX_E2S2S_ENTRIES_COUNT,
} from '../src';
import {
  findStabilityPoolAccount,
  findStabilityPool,
} from './queries/stability-pool-queries';
import {
  readonlyArray as RA,
  function as F,
  option as O,
  array as A,
} from 'fp-ts';
import { findCollateralAsset, findIAsset } from './queries/iasset-queries';
import {
  benchmarkAndAwaitTx,
  benchmarkAndAwaitTxWithTxFee,
} from './utils/benchmark-utils';
import {
  executeLiquidation,
  runFreezeCdp,
  runLiquidateCdp,
  runOpenCdp,
} from './cdp/actions';
import {
  addAssets,
  Emulator,
  fromText,
  generateEmulatorAccount,
  Lucid,
  EmulatorAccount,
  fromHex,
  paymentCredentialOf,
  UTxO,
  Assets,
} from '@lucid-evolution/lucid';
import {
  adaAssetClass,
  AssetClass,
  getInlineDatumOrThrow,
  isSameAssetClass,
  lovelacesAmt,
  matchSingle,
  mkAssetsOf,
  mkLovelacesOf,
  negateAssets,
} from '@3rd-eye-labs/cardano-offchain-common';
import { describe } from 'vitest';
import {
  iusdInitialAssetCfg,
  mkBaseCollateralAsset,
  mkCollateralAssetsChain,
} from './mock/assets-mock';
import {
  runCreateAdjustRequest,
  runCreateCloseRequest,
  runCreateE2s2sSnapshots,
  runOpenCdpAndCreateSPAccount,
  runProcessSpRequest,
} from './stability-pool/actions';
import { getValueChangeAtAddressAfterAction, sendValueTo } from './utils';
import { calculateFeeFromRatio } from '../src/utils/indigo-helpers';
import {
  refreshPriceOracle,
  runFeedPriceToOracle,
} from './price-oracle/actions';
import { expectScriptFailure, expectValue } from './utils/asserts';
import { findFrozenCDPs, findPrice } from './cdp/cdp-queries';
import {
  processSuccessfulProposal,
  whitelistCollateralAsset,
} from './gov/actions';
import { findPriceOracle } from './price-oracle/price-oracle-queries';
import { match, P } from 'ts-pattern';
import { parsePriceOracleDatum } from '../src/contracts/price-oracle/types-new';
import { findGov } from './gov/governance-queries';
import { createMultipleUtxosAtTreasury } from './endpoints/treasury';
import { MAINNET_PROTOCOL_PARAMETERS } from './indigo-test-helpers';
import { rationalFromInt, rationalMul } from '../src/types/rational';
import { init } from './endpoints/initialize';

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

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

const collateralAssetC: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dac',
  ),
  tokenName: fromHex(fromText('C')),
};

const collateralAssetD: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dad',
  ),
  tokenName: fromHex(fromText('D')),
};

const collateralAssetE: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dae',
  ),
  tokenName: fromHex(fromText('E')),
};

const collateralAssetF: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8daf',
  ),
  tokenName: fromHex(fromText('F')),
};

const collateralAssetG: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dba',
  ),
  tokenName: fromHex(fromText('G')),
};

const collateralAssetH: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbb',
  ),
  tokenName: fromHex(fromText('H')),
};

const collateralAssetI: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbc',
  ),
  tokenName: fromHex(fromText('I')),
};

const collateralAssetJ: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbd',
  ),
  tokenName: fromHex(fromText('J')),
};

const collateralAssetK: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbe',
  ),
  tokenName: fromHex(fromText('K')),
};

const collateralAssetL: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dbf',
  ),
  tokenName: fromHex(fromText('L')),
};

const collateralAssetM: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dca',
  ),
  tokenName: fromHex(fromText('M')),
};

const collateralAssetN: AssetClass = {
  currencySymbol: fromHex(
    // random generated
    'aa072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dcb',
  ),
  tokenName: fromHex(fromText('N')),
};

describe('Stability pool', () => {
  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(100_000_000_000_000n),
          mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
        ),
      ),
      user2: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(100_000_000_000_000n),
          mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
        ),
      ),
      user3: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(100_000_000_000_000n),
          mkAssetsOf(collateralAssetA, 10_000_000_000_000n),
          mkAssetsOf(collateralAssetB, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetC, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetD, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetE, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetF, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetG, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetH, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetI, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetJ, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetK, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetL, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetM, 1_000_000_000_000n),
          mkAssetsOf(collateralAssetN, 1_000_000_000_000n),
        ),
      ),
      user4: generateEmulatorAccount(
        addAssets(mkLovelacesOf(100_000_000_000_000n)),
      ),
    };

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

  test<MyContext>('Create Account', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
    const [sysParams, [iusdAssetInfo]] = await init(
      context.lucid,
      [iusdInitialAssetCfg()],
      context.emulator.slot,
    );

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

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

    await benchmarkAndAwaitTx(
      'Stability Pool - Create Account - Request',
      await requestSpAccountCreation(
        iusdAssetInfo.iassetTokenNameAscii,
        10n,
        sysParams,
        context.lucid,
      ),
      context.lucid,
      context.emulator,
    );

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

    const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
      addAssets(acc, utxo.assets),
    )(await context.lucid.utxosAt(context.users.admin.address));

    await benchmarkAndAwaitTx(
      'Stability Pool - Create Account - Process',
      await runProcessSpRequest(
        context,
        sysParams,
        'iUSD',
        paymentCredentialOf(context.users.user1.address).hash,
      ),
      context.lucid,
      context.emulator,
    );

    const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
      addAssets(acc, utxo.assets),
    )(await context.lucid.utxosAt(context.users.admin.address));

    const adminValueDiff = addAssets(
      finalAdminValue,
      negateAssets(initialAdminValue),
    );

    // TODO: Investigate why the admin loses a small amount of lovelaces.
    assert(lovelacesAmt(adminValueDiff) > -7_000n);
  });

  describe('Annul', () => {
    test<MyContext>('Stability Pool - Create Account - Annul', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          1_000_000_000n,
          20n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
          sysParams,
          context.lucid,
        ),
      );

      const accountUtxo = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        pkh.hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Stability Pool - Create Account - Annul',
        await annulRequest(accountUtxo.utxo, sysParams, context.lucid),
        context.lucid,
        context.emulator,
      );
    });

    test<MyContext>('Stability Pool - Adjust Account - Annul', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

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

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

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

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
          sysParams,
          context.lucid,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          'iUSD',
          paymentCredentialOf(context.users.user1.address).hash,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
        ),
      );

      const accountUtxo = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        pkh.hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Stability Pool - Adjust Account - Annul',
        await annulRequest(accountUtxo.utxo, sysParams, context.lucid),
        context.lucid,
        context.emulator,
      );
    });

    test<MyContext>('Stability Pool - Close Account - Annul', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

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

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

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

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
          sysParams,
          context.lucid,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          'iUSD',
          paymentCredentialOf(context.users.user1.address).hash,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
      );

      const accountUtxo = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        pkh.hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Stability Pool - Close Account - Annul',
        await annulRequest(accountUtxo.utxo, sysParams, context.lucid),
        context.lucid,
        context.emulator,
      );
    });
  });

  describe('Adjust', () => {
    test<MyContext>('Deposit Account no liquidation reward', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          20_000_000n,
          5_000_000n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          1_000n,
          sysParams,
          context.lucid,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
      );

      await benchmarkAndAwaitTx(
        'Stability Pool - Adjust Account - Request',
        await runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
        ),
        context.lucid,
        context.emulator,
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          await benchmarkAndAwaitTx(
            'Stability Pool - Deposit Account no liquidation reward - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      // Receives whole create fee for himself
      const expectedRewardFromFees = BigInt(
        sysParams.stabilityPoolParams.accountCreateFeeLovelaces,
      );

      expectValue(
        fundsReceived,
        'Expected to receive only the reward after processing my deposit request',
      ).toEqual(mkLovelacesOf(expectedRewardFromFees));

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a significant amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -700_000n);
    });

    test<MyContext>('Deposit Account ADA liquidation reward', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

      const ACCOUNT_DEPOSIT = 20_000_000n;
      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

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

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

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        adaAssetClass,
        iusdAssetInfo,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          1_000n,
        ),
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTx(
            'Stability Pool - Deposit Account ADA liquidation reward - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedRewardFromLiquidations =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;
      // Receives whole create fee for himself since first account and then half for the other user (split between 2).
      const expectedRewardFromFees =
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) / 2n;

      expectValue(
        fundsReceived,
        'Expected to receive only the reward after processing my deposit request',
      ).toEqual(
        mkLovelacesOf(expectedRewardFromLiquidations + expectedRewardFromFees),
      );

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });

    test<MyContext>('Deposit Account non ADA liquidation reward', 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 ACCOUNT_DEPOSIT = 20_000_000n;
      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

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

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

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

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

      // Send funds to user4 so liquidation is performed from a clean address.
      await sendValueTo(
        context.users.user4.address,
        // The extra collateral asset is to be sent to the treasury.
        mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL + 1_000_000n),
        context.lucid,
      );

      context.emulator.awaitSlot(1000);

      await refreshPriceOracle(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
      );

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        collateralAssetA,
        iusdAssetInfo,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          1_000n,
        ),
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTx(
            'Stability Pool - Deposit Account non ADA liquidation reward - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedReward =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;
      // Receives whole create fee for himself since first account and then half for the other user (split between 2).
      const expectedRewardFromFees =
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) / 2n;

      expectValue(
        fundsReceived,
        'Expected to receive correct non ADA reward after processing deposit request',
      ).toEqual(
        addAssets(
          mkAssetsOf(collateralAssetA, expectedReward),
          mkLovelacesOf(expectedRewardFromFees),
        ),
      );

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });

    test<MyContext>('Withdraw Account no liquidation reward', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          20_000_000n,
          5_000_000n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          10_000n,
          sysParams,
          context.lucid,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          -1_000n,
        ),
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTx(
            'Stability Pool - Withdraw Account no liquidation reward - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

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

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      // Receives whole create fee for himself.
      const expectedRewardFromFees = BigInt(
        sysParams.stabilityPoolParams.accountCreateFeeLovelaces,
      );

      expectValue(
        fundsReceived,
        'Expected only the withdrawn amount in the output not taking into account ADA',
      ).toEqual(
        addAssets(
          mkAssetsOf(
            iassetAc,
            1_000n -
              calculateFeeFromRatio(
                iasset.datum.stabilityPoolWithdrawalFeeRatio,
                1_000n,
              ),
          ),
          mkLovelacesOf(expectedRewardFromFees),
        ),
      );

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a significant amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -700_000n);
    });

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

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

      const ACCOUNT_DEPOSIT = 20_000_000n;

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdpAndCreateSPAccount(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            ACCOUNT_DEPOSIT,
          ),
        );

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        adaAssetClass,
        iusdAssetInfo,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          -1_000n,
        ),
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTx(
            'Stability Pool - Withdraw Account ADA liquidation reward - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

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

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedReward =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;

      // Receives whole create fee for himself since first account and then half for the other user (split between 2).
      const expectedRewardFromFees =
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) / 2n;

      const expectedValReceived = addAssets(
        mkLovelacesOf(expectedReward),
        mkLovelacesOf(expectedRewardFromFees),
        mkAssetsOf(
          iassetAc,
          1_000n -
            calculateFeeFromRatio(
              iasset.datum.stabilityPoolWithdrawalFeeRatio,
              1_000n,
            ),
        ),
      );

      expectValue(
        fundsReceived,
        'Expected the withdrawn amount and the reward in the output',
      ).toEqual(expectedValReceived);

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });

    test<MyContext>('Withdraw Account non ADA liquidation reward', 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,
      );

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

      const ACCOUNT_DEPOSIT = 20_000_000n;

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdpAndCreateSPAccount(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            ACCOUNT_DEPOSIT,
          ),
        );

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

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

      // Send funds to user4 so liquidation is performed from a clean address.
      await sendValueTo(
        context.users.user4.address,
        // The extra collateral asset is to be sent to the treasury.
        mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL + 1_000_000n),
        context.lucid,
      );

      context.emulator.awaitSlot(1000);

      await refreshPriceOracle(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
      );

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        collateralAssetA,
        iusdAssetInfo,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          -1_000n,
        ),
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTx(
            'Stability Pool - Withdraw Account non ADA liquidation reward - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

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

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedReward =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;

      const expectedValReceived = addAssets(
        mkAssetsOf(collateralAssetA, expectedReward),
        mkAssetsOf(
          iassetAc,
          1_000n -
            calculateFeeFromRatio(
              iasset.datum.stabilityPoolWithdrawalFeeRatio,
              1_000n,
            ),
        ),
      );

      // Receives whole create fee for himself since first account and then half for the other user (split between 2).
      const expectedRewardFromFees =
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) / 2n;

      expectValue(
        fundsReceived,
        'Expected the withdrawn amount and the reward in the output not taking into account ADA',
      ).toEqual(
        addAssets(expectedValReceived, mkLovelacesOf(expectedRewardFromFees)),
      );

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });
  });

  describe('Close', () => {
    test<MyContext>('Stability Pool - Close Account with expired oracle', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );
      context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

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

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          1_000_000_000n,
          20n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
          sysParams,
          context.lucid,
        ),
      );

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

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          pkh.hash,
        ),
      );

      const collateralAsset = await findCollateralAsset(
        context.lucid,
        sysParams,
        adaAssetClass,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const priceOracleUtxo = await findPriceOracle(
        context.lucid,
        match(collateralAsset.datum.priceInfo)
          .with({ OracleNft: P.select() }, (oracleNft) => oracleNft)
          .otherwise(() => {
            throw new Error('Expected active oracle');
          }),
      );

      const priceOracleDatum = parsePriceOracleDatum(
        getInlineDatumOrThrow(priceOracleUtxo),
      );

      //Await until oracle is expired
      context.emulator.awaitSlot(Number(priceOracleDatum.expirationTime));

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

      await runAndAwaitTx(
        context.lucid,
        runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
      );

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

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          pkh.hash,
        ),
      );
    });

    test<MyContext>('Stability Pool - Close Account with delisted collateral asset', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

      await runAndAwaitTx(
        context.lucid,
        runOpenCdp(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          1_000_000_000n,
          20n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        requestSpAccountCreation(
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
          sysParams,
          context.lucid,
        ),
      );

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

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          pkh.hash,
        ),
      );

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

      const collateralAsset = await findCollateralAsset(
        context.lucid,
        sysParams,
        adaAssetClass,
        iusdAssetInfo.iassetTokenNameAscii,
        adaAssetClass,
      );

      const [tx, pollId] = await createProposal(
        {
          UpdateCollateralAsset: {
            correspondingIAsset: fromHex(
              fromText(iusdAssetInfo.iassetTokenNameAscii),
            ),
            collateralAsset: adaAssetClass,
            newAssetExtraDecimals: 0n,
            newAssetPriceInfo: {
              Delisted: { price: rationalFromInt(1n) },
            },
            newInterestOracleNft: collateralAsset.datum.interestOracleNft,
            newLiquidationRatio: { numerator: 120n, denominator: 100n },
            newMaintenanceRatio: { numerator: 150n, denominator: 100n },
            newRedemptionRatio: { numerator: 150n, denominator: 100n },
            newMinCollateralAmt: 10_000_000n,
          },
        },
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        [],
      );

      await runAndAwaitTxBuilder(context.lucid, tx);

      await processSuccessfulProposal(
        pollId,
        null,
        null,
        null,
        null,
        (
          await findCollateralAsset(
            context.lucid,
            sysParams,
            adaAssetClass,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
          )
        ).utxo,
        null,
        sysParams,
        context,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
      );

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

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          pkh.hash,
        ),
      );
    });

    test<MyContext>('Close Account no rewards (last SP account)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runOpenCdpAndCreateSPAccount(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          adaAssetClass,
          10_000n,
        ),
      );

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
      );

      await benchmarkAndAwaitTx(
        'Stability Pool - Close Account - Request',
        await runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        context.lucid,
        context.emulator,
      );

      await benchmarkAndAwaitTx(
        'Stability Pool - Close Account no rewards - Process (last SP account)',
        await runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
        context.lucid,
        context.emulator,
      );
    });

    test<MyContext>('Close Account no rewards (more SP accounts)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg()],
        context.emulator.slot,
      );

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdpAndCreateSPAccount(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            1000n,
          ),
        );

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

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

      await runAndAwaitTx(
        context.lucid,
        runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      await benchmarkAndAwaitTx(
        'Stability Pool - Close Account no liquidation rewards - Process (more SP accounts)',
        await runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
        context.lucid,
        context.emulator,
      );

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });

    test<MyContext>('Close Account with ADA liquidation rewards (more SP accounts)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [iusdInitialAssetCfg(0n)],
        context.emulator.slot,
      );

      const ACCOUNT_DEPOSIT = 20_000_000n;

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdpAndCreateSPAccount(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            ACCOUNT_DEPOSIT,
          ),
        );

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        adaAssetClass,
        iusdAssetInfo,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
      );

      const accountUtxo = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        paymentCredentialOf(context.users.user1.address).hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const stabilityPoolBefore = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [txFee, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTxWithTxFee(
            'Stability Pool - Close Account ADA liquidation rewards - Process (more SP accounts)',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      const stabilityPoolAfter = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedReward =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;

      // Receives whole create fee for himself since first account and then half for the other user (split between 2).
      const expectedRewardFromFees =
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) / 2n;

      const remainingDeposit = ACCOUNT_DEPOSIT - LIQUIDATED_DEBT / 2n;
      const withdrawalFee = calculateFeeFromRatio(
        iasset.datum.stabilityPoolWithdrawalFeeRatio,
        ACCOUNT_DEPOSIT - LIQUIDATED_DEBT / 2n,
      );

      const iassetAc: AssetClass = {
        currencySymbol: fromHex(
          sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
        ),
        tokenName: iasset.datum.assetName,
      };

      const expectedVal = addAssets(
        mkLovelacesOf(lovelacesAmt(accountUtxo.utxo.assets)),
        mkLovelacesOf(expectedReward),
        mkLovelacesOf(expectedRewardFromFees),
        negateAssets(mkLovelacesOf(txFee)),
        //TODO: fee evaluation is slightly off
        mkLovelacesOf(6776n),
        mkAssetsOf(iassetAc, remainingDeposit - withdrawalFee),
      );

      const actualSpDiff = addAssets(
        negateAssets(stabilityPoolBefore.utxo.assets),
        stabilityPoolAfter.utxo.assets,
      );

      const expectedSpDiff = addAssets(
        mkLovelacesOf(-expectedReward),
        mkLovelacesOf(-expectedRewardFromFees),
        mkAssetsOf(iassetAc, -remainingDeposit + withdrawalFee),
      );

      expectValue(
        actualSpDiff,
        'Stability pool expected different diff after account closure with rewards',
      ).toEqual(expectedSpDiff);

      expectValue(
        fundsReceived,
        'account expected to get 50% of the liquidated collateral excluding the liquidation fee and withdrawal fee',
      ).toEqual(expectedVal);

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });

    test<MyContext>('Close Account with non ADA liquidation rewards (more SP accounts)', 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 ACCOUNT_DEPOSIT = 20_000_000n;
      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdpAndCreateSPAccount(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            ACCOUNT_DEPOSIT,
          ),
        );

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

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

      // Send funds to user4 so liquidation is performed from a clean address.
      await sendValueTo(
        context.users.user4.address,
        // The extra collateral asset is to be sent to the treasury.
        mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL + 1_000_000n),
        context.lucid,
      );

      context.emulator.awaitSlot(1000);

      await refreshPriceOracle(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
      );

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        collateralAssetA,
        iusdAssetInfo,
      );

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

      await runAndAwaitTx(
        context.lucid,
        runCreateCloseRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
      );

      const accountUtxo = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        paymentCredentialOf(context.users.user1.address).hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const stabilityPoolBefore = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

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

      const initialAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const [txFee, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTxWithTxFee(
            'Stability Pool - Close Account non ADA liquidation rewards - Process (more SP accounts)',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      const stabilityPoolAfter = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedReward =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;

      // Receives whole create fee for himself since first account and then half for the other user (split between 2).
      const expectedRewardFromFees =
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
        BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) / 2n;

      const remainingDeposit = ACCOUNT_DEPOSIT - LIQUIDATED_DEBT / 2n;
      const withdrawalFee = calculateFeeFromRatio(
        iasset.datum.stabilityPoolWithdrawalFeeRatio,
        ACCOUNT_DEPOSIT - LIQUIDATED_DEBT / 2n,
      );

      const iassetAc: AssetClass = {
        currencySymbol: fromHex(
          sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
        ),
        tokenName: iasset.datum.assetName,
      };

      const expectedVal = addAssets(
        mkLovelacesOf(lovelacesAmt(accountUtxo.utxo.assets)),
        mkLovelacesOf(expectedRewardFromFees),
        negateAssets(mkLovelacesOf(txFee)),
        //TODO: fee evaluation is slightly off
        mkLovelacesOf(6776n),
        mkAssetsOf(collateralAssetA, expectedReward),
        mkAssetsOf(iassetAc, remainingDeposit - withdrawalFee),
      );

      const actualSpDiff = addAssets(
        negateAssets(stabilityPoolBefore.utxo.assets),
        stabilityPoolAfter.utxo.assets,
      );

      const expectedSpDiff = addAssets(
        mkLovelacesOf(-expectedRewardFromFees),
        mkAssetsOf(collateralAssetA, -expectedReward),
        mkAssetsOf(iassetAc, -remainingDeposit + withdrawalFee),
      );

      expectValue(
        actualSpDiff,
        'Stability pool expected different diff after account closure with rewards',
      ).toEqual(expectedSpDiff);

      expectValue(
        fundsReceived,
        'account expected to get 50% of the liquidated collateral excluding the liquidation fee and withdrawal fee',
      ).toEqual(expectedVal);

      const finalAdminValue = A.reduce<UTxO, Assets>({}, (acc, utxo) =>
        addAssets(acc, utxo.assets),
      )(await context.lucid.utxosAt(context.users.admin.address));

      const adminValueDiff = addAssets(
        finalAdminValue,
        negateAssets(initialAdminValue),
      );

      // TODO: Investigate why the admin loses a small amount of lovelaces.
      assert(lovelacesAmt(adminValueDiff) > -7_000n);
    });
  });

  // These are the most significant tests for benchmarking the maximum number of collateral assets supported.
  describe('Liquidate', () => {
    test<MyContext>('Liquidate with 10 reward assets in the pool - collecting with 2 assets in treasury', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
        collateralAssetI,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(
        10,
      );

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        16n,
        sysParams,
        context,
      );

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            BigInt(initialAssets.length + 2) * LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Add each collateral asset as reward to stability pool at epoch 0 and scale 0
       ***********/
      for (let idx = 0; idx < initialAssets.length; idx++) {
        const collateral = initialAssets[idx];

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          // The extra collateral asset is to be sent to the treasury.
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL + 1_000_000n),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
        );
      }

      /**********
       * Execute last liquidation
       ***********/

      {
        {
          const sp = await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          );

          expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
          expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
          expect(
            sp.datum.assetStates.length,
            'Unexpected asset states',
          ).toEqual(initialAssets.length);
        }

        const collateral = initialAssets[initialAssets.length - 1];

        // Make sure that the liquidation will be performed using a treasury UTxO with 2 assets.
        assert(collateral !== adaAssetClass);

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          // The extra collateral asset is to be sent to the treasury.
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL + 1_000_000n),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
          (tx) =>
            benchmarkAndAwaitTx(
              'Stability Pool - Liquidate with 10 reward assets in the pool - collecting with 2 assets in treasury',
              tx,
              context.lucid,
              context.emulator,
            ),
        );
      }

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
        expect(sp.datum.assetStates.length, 'Unexpected asset states').toEqual(
          initialAssets.length,
        );
      }
    });

    test<MyContext>('Liquidate with 10 reward assets in the pool - direct treasury payment', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
        collateralAssetI,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(
        10,
      );

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        16n,
        sysParams,
        context,
      );

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            BigInt(initialAssets.length + 2) * LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Add each collateral asset as reward to stability pool at epoch 0 and scale 0
       ***********/
      for (let idx = 0; idx < initialAssets.length; idx++) {
        const collateral = initialAssets[idx];

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
          undefined,
          true,
        );
      }

      /**********
       * Execute last liquidation
       ***********/

      {
        {
          const sp = await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          );

          expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
          expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
          expect(
            sp.datum.assetStates.length,
            'Unexpected asset states',
          ).toEqual(initialAssets.length);
        }

        const collateral = initialAssets[initialAssets.length - 1];

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
          (tx) =>
            benchmarkAndAwaitTx(
              'Stability Pool - Liquidate with 10 reward assets in the pool - direct treasury payment',
              tx,
              context.lucid,
              context.emulator,
            ),
          true,
        );
      }

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
        expect(sp.datum.assetStates.length, 'Unexpected asset states').toEqual(
          initialAssets.length,
        );
      }
    });

    test<MyContext>('Liquidate with 10 reward assets in the pool and increasing epoch', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
        collateralAssetI,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(
        10,
      );

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        27n,
        sysParams,
        context,
      );

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            BigInt(initialAssets.length + 1) * LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Add each collateral asset as reward to stability pool at epoch 0 and scale 0
       ***********/
      for (let idx = 0; idx < initialAssets.length; idx++) {
        const collateral = initialAssets[idx];

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          // The extra collateral asset is to be sent to the treasury.
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL + 1_000_000n),
          context.lucid,
        );

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
        );
      }

      /**********
       * Execute last liquidation
       ***********/

      {
        {
          const sp = await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          );

          expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
          expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
          expect(
            sp.datum.assetStates.length,
            'Unexpected asset states',
          ).toEqual(initialAssets.length);
        }

        const collateral = initialAssets[initialAssets.length - 1];

        // Make sure that the liquidation will be performed using a treasury UTxO with 2 assets.
        assert(collateral !== adaAssetClass);

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          // The extra collateral asset is to be sent to the treasury.
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL + 1_000_000n),
          context.lucid,
        );

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
          async (tx) =>
            benchmarkAndAwaitTx(
              'Stability Pool - Liquidate with 10 reward assets in the pool and increasing epoch',
              tx,
              context.lucid,
              context.emulator,
            ),
        );
      }

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );
        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(1n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
        expect(sp.datum.assetStates.length, 'Unexpected asset states').toEqual(
          initialAssets.length,
        );
      }
    });

    // TODO: This test shows abnormally low memory and CPU use compared with the other similar tests
    // (e.g. the test above increasing epoch) or this very test performed with one less asset.
    test.todo<MyContext>(
      'Liquidate with 10 reward assets in the pool and increasing scale',
      async (context: MyContext) => {
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

        const initialAssets = [
          adaAssetClass,
          collateralAssetA,
          collateralAssetB,
          collateralAssetC,
          collateralAssetD,
          collateralAssetE,
          collateralAssetF,
          collateralAssetG,
          collateralAssetH,
          collateralAssetI,
        ];

        expect(initialAssets.length, 'Unexpected reward assets count').toEqual(
          10,
        );

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

        await createMultipleUtxosAtTreasury(
          mkLovelacesOf(2n),
          18n,
          sysParams,
          context,
        );

        // After price doubles the collateral ratio will be 110%
        const LIQUIDATED_DEBT = 5_000_000n;
        const LIQUIDATED_COLLATERAL = 11_000_000n;

        /**********
         * Create account for user2
         ***********/
        {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateralAssetA,
          );

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

          await runAndAwaitTx(
            context.lucid,
            requestSpAccountCreation(
              iusdAssetInfo.iassetTokenNameAscii,
              BigInt(initialAssets.length) * LIQUIDATED_DEBT +
                LIQUIDATED_DEBT +
                10n,
              sysParams,
              context.lucid,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }

        /**********
         * Add each collateral asset as reward to stability pool at epoch 0 and scale 0
         ***********/
        for (let idx = 0; idx < initialAssets.length; idx++) {
          const collateral = initialAssets[idx];

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

          // Send funds to user4 so liquidation is performed from a clean address.
          await sendValueTo(
            context.users.user4.address,
            // The extra collateral asset is to be sent to the treasury.
            mkAssetsOf(collateral, LIQUIDATED_COLLATERAL + 1_000_000n),
            context.lucid,
          );

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

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateral,
          );

          await executeLiquidation(
            context,
            sysParams,
            LIQUIDATED_DEBT,
            LIQUIDATED_COLLATERAL,
            collateral,
            iusdAssetInfo,
          );
        }

        /**********
         * Make some liquidations so next liquidation increases scale
         ***********/

        await repeat(1, async () => {
          context.lucid.selectWallet.fromSeed(context.users.user4.seedPhrase);

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            adaAssetClass,
          );

          await executeLiquidation(
            context,
            sysParams,
            LIQUIDATED_DEBT,
            LIQUIDATED_COLLATERAL,
            adaAssetClass,
            iusdAssetInfo,
          );

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

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        });

        /**********
         * Execute last liquidation
         ***********/

        {
          {
            const sp = await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            );

            expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
            expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
            expect(
              sp.datum.assetStates.length,
              'Unexpected asset states',
            ).toEqual(initialAssets.length);
          }

          const collateral = initialAssets[initialAssets.length - 1];

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

          // Send funds to user4 so liquidation is performed from a clean address.
          await sendValueTo(
            context.users.user4.address,
            // The extra collateral asset is to be sent to the treasury.
            mkAssetsOf(collateral, LIQUIDATED_COLLATERAL + 1_000_000n),
            context.lucid,
          );

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

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateral,
          );

          await executeLiquidation(
            context,
            sysParams,
            LIQUIDATED_DEBT,
            LIQUIDATED_COLLATERAL,
            collateral,
            iusdAssetInfo,
            (tx) =>
              benchmarkAndAwaitTx(
                'Stability Pool - Liquidate with 10 reward assets in the pool and increasing scale',
                tx,
                context.lucid,
                context.emulator,
              ),
          );
        }

        {
          const sp = await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          );

          expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
          expect(sp.datum.state.scale, 'Unexpected scale').toEqual(1n);
          expect(
            sp.datum.assetStates.length,
            'Unexpected asset states',
          ).toEqual(initialAssets.length);
        }
      },
    );
  });

  // We're skipping these tests, because they take really looong to execute... It must be some Lucid evolution issue.
  describe.todo('Snapshot e2s2s', () => {
    test<MyContext>('Record snapshots for 15 assets at once', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
        collateralAssetI,
        collateralAssetJ,
        collateralAssetK,
        collateralAssetL,
        collateralAssetM,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(
        14,
      );

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        34n,
        sysParams,
        context,
      );

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            BigInt(initialAssets.length) * LIQUIDATED_DEBT + LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Add each collateral asset as reward to stability pool at epoch 0 and scale 0
       ***********/
      for (const collateral of initialAssets) {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

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

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);
        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
        );
      }

      /**********
       * Make multiple liquidations
       ***********/

      await repeat(4, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

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

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(4n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(0n);
        expect(sp.datum.assetStates.length, 'Unexpected asset states').toEqual(
          initialAssets.length,
        );

        expect(
          sp.datum.assetStates.every(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          ),
          'Expected every collateral assets e2s2s to be of the maximum size to be snapshoted',
        ).toBeTruthy();
      }

      await benchmarkAndAwaitTx(
        'Stability Pool - Record snapshots for 15 assets at once',
        await runCreateE2s2sSnapshots(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        ),
        context.lucid,
        context.emulator,
      );

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(
          sp.datum.assetStates.every(
            ([_, assetState]) => assetState.epoch2scale2sum.length === 1,
          ),
          'Expected every e2s2s was snapshotted',
        ).toBeTruthy();
      }
    });
  });

  // TODO: test request annulment

  // We're skipping these tests, because they take really looong to execute... It must be some Lucid evolution issue.
  describe.todo('Complex cases', () => {
    test<MyContext>('Deposit Account multiple rewards (9 assets)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(9);

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        14n,
        sysParams,
        context,
      );

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;
      const ACCOUNT_DEPOSIT = LIQUIDATED_DEBT * BigInt(initialAssets.length);

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            ACCOUNT_DEPOSIT * 3n,
            ACCOUNT_DEPOSIT + LIQUIDATED_DEBT,
          ),
        );

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

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      for (let idx = 0; idx < initialAssets.length; idx++) {
        const collateral = initialAssets[idx];

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
        );
      }

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          1_000n,
        ),
      );

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

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          benchmarkAndAwaitTx(
            'Stability Pool - Deposit Account multiple rewards (9 assets) - Process',
            await runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedRewardPerAsset =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
          2n -
        1n; // The -1n is to compensate for rounding errors.

      const expectedReceived = addAssets(
        mkAssetsOf(collateralAssetA, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetB, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetC, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetD, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetE, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetF, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetG, expectedRewardPerAsset),
        mkAssetsOf(collateralAssetH, expectedRewardPerAsset),
        mkAssetsOf(
          adaAssetClass,
          expectedRewardPerAsset +
            // whole for him, half for the other user.
            BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
            BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) /
              2n,
        ),
      );

      expectValue(fundsReceived, 'Expected to receive correct reward').toEqual(
        expectedReceived,
      );
    });

    // TODO: also test when multiple epochs
    test<MyContext>('Deposit Account from scale 0, when now theres scale 4; collecting rewards only from scale 0 and 1', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [
          {
            ...iusdInitialAssetCfg(),
            collateralAssets: mkCollateralAssetsChain([
              mkBaseCollateralAsset(collateralAssetA, 0n),
              mkBaseCollateralAsset(collateralAssetB, 0n),
              mkBaseCollateralAsset(collateralAssetC, 0n),
              mkBaseCollateralAsset(adaAssetClass, 0n),
            ]),
          },
        ],
        context.emulator.slot,
      );

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        12n,
        sysParams,
        context,
      );

      const SINGLE_ACCOUNT_DEPOSIT = 2_500_005n;
      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

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

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

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      await repeat(7, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);
        if (isLast) {
          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateralAssetB,
          );

          // Send funds to user4 so liquidation is performed from a clean address.
          await sendValueTo(
            context.users.user4.address,
            mkAssetsOf(collateralAssetB, LIQUIDATED_COLLATERAL),
            context.lucid,
          );

          context.emulator.awaitSlot(1000);

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateralAssetB,
          );

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

          // Make an additional liquidation with collateral Asset B
          await executeLiquidation(
            context,
            sysParams,
            LIQUIDATED_DEBT,
            LIQUIDATED_COLLATERAL,
            collateralAssetB,
            iusdAssetInfo,
          );
        } else {
          // Send funds to user4 so liquidation is performed from a clean address.
          await sendValueTo(
            context.users.user4.address,
            mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
            context.lucid,
          );

          context.emulator.awaitSlot(1000);

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateralAssetA,
          );

          context.lucid.selectWallet.fromSeed(context.users.user4.seedPhrase);
          await executeLiquidation(
            context,
            sysParams,
            LIQUIDATED_DEBT,
            LIQUIDATED_COLLATERAL,
            collateralAssetA,
            iusdAssetInfo,
          );
        }

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

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            5_000_000n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      });

      expect(
        (
          await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.state.scale,
        'Unexpected scale',
      ).toEqual(4n);

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          5_000_000n,
        ),
      );

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

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
          ),
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      // We're multiplying base deposit by 2 because the price increases by 2 and by 10% because the CR was 110% when CDP got liquidated.
      // Then we got ~ this number.
      const expectedRewardExclFee = 5_500_010n;

      const expectedReward =
        expectedRewardExclFee -
        calculateFeeFromRatio(
          iasset.datum.liquidationProcessingFeeRatio,
          expectedRewardExclFee,
        );

      expectValue(fundsReceived, 'Wrong reward amount').toEqual(
        addAssets(
          // We only get the collateralAssetA as a reward because collateralAssetB was liquidated in different scale
          // than 0 or 1. Also, we're not getting back any iAsset since all of our deposit got drained.
          mkAssetsOf(collateralAssetA, expectedReward),
          mkLovelacesOf(
            BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
              BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) /
                2n -
              // This is just some inaccuracy.
              1n,
          ),
        ),
      );
    });

    test<MyContext>('Cross snapshot deposit referencing 1 e2s2s snapshots; deposit account from scale 3 (4th entry), where the pools scale is 8, i.e. 0-3 scale are in a snapshot, 4-8 scale are in the pool datum', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [
          {
            ...iusdInitialAssetCfg(),
            collateralAssets: mkCollateralAssetsChain([
              mkBaseCollateralAsset(collateralAssetA, 0n),
            ]),
          },
        ],
        context.emulator.slot,
      );

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        30n,
        sysParams,
        context,
      );

      /**********
       * add iAssets to user1 wallet
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

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

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            5_000_010n,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Make multiple liquidations to reach scale 4
       ***********/

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      await repeat(6, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT + 10n,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      expect(
        (
          await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.state.scale,
        'Unexpected scale',
      ).toEqual(3n);

      /**********
       * Create user1 account at scale 3
       ***********/

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user1.address).hash,
          ),
        );
      }

      /**********
       * Make more liquidations to reach the limit of epoch2scale2sum
       ***********/

      await repeat(2, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      });

      /**********
       * Attempt to do liquidation, but expect it to fail since epoch2scale2sum needs to be snapshotted.
       ***********/

      // TODO: make this assertion in a separate test

      {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);
        const price = await findPrice(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          collateralAssetA,
        );

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

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            collateralAssetA,
            LIQUIDATED_COLLATERAL,
            LIQUIDATED_DEBT,
          ),
        );

        await runFeedPriceToOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
          rationalMul(price, rationalFromInt(2n)),
        );

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

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

        // Set the price back.
        await runFeedPriceToOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
          price,
        );

        await expectScriptFailure(
          // E2S2S large, make e2s2s snap
          'W',
          runLiquidateCdp(context, sysParams, frozenCdp.utxo),
        );
      }

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/
      // TODO: no need to do liquidation as part of this
      {
        context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          ),
          'Expected every collateral assets e2s2s to be of the maximum size to be snapshoted',
        ).toBeTruthy();

        await runAndAwaitTx(
          context.lucid,
          runCreateE2s2sSnapshots(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          ),
        );

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) => assetState.epoch2scale2sum.length === 1,
          ),
          'Expected every e2s2s was snapshotted',
        ).toBeTruthy();

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

        await runAndAwaitTx(
          context.lucid,
          runLiquidateCdp(context, sysParams, frozenCdp.utxo),
        );
      }

      /**********
       * Make liquidations to create scale 9
       ***********/

      await repeat(7, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      const sp = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
      expect(sp.datum.state.scale, 'Unexpected scale').toEqual(8n);
      expect(
        sp.datum.assetStates.every(([_, assetState]) =>
          F.pipe(
            assetState.epoch2scale2sum,
            RA.findFirst(([key, _]) => key.epoch === 0n && key.scale === 3n),
            O.match(
              // Expect it not to be there.
              () => true,
              (_) => false,
            ),
          ),
        ),
        'The accounts expected epoch and scale cannot be in any of the stability pools e2s2s',
      ).toBeTruthy();

      const account = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        paymentCredentialOf(context.users.user1.address).hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      expect(
        account.datum.state.epoch,
        'Expected account to be at epoch 0',
      ).toEqual(0n);
      expect(
        account.datum.state.scale,
        'Expected account to be at scale 4',
      ).toEqual(3n);

      /**********
       * Make deposit and the rewards get calculated cross scale.
       ***********/

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          100n,
        ),
      );

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

      // TODO: finish this, it's failing so far because the e2s2s snapshots are not properly referenced yet.
      // TODO: nope this is not the worst case, worst case should be when 1st entry in is one snapshot UTXO, 2nd in another snapshot UTXO
      await benchmarkAndAwaitTx(
        "Stability Pool - Deposit Account, referencing 1 e2s2s snapshot and pool's snapshot (1 reward asset) - Process",
        await runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
        context.lucid,
        context.emulator,
      );

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        const spCollateralAssets = sp.datum.assetStates.map(([key, _]) => key);

        const expectedSpCollateralAssets = [
          // Ada because of account creation fee
          adaAssetClass,
          collateralAssetA,
        ];

        expect(
          sp.datum.assetStates.length === expectedSpCollateralAssets.length &&
            A.zip(spCollateralAssets)(expectedSpCollateralAssets).every(
              ([actual, expected]) => isSameAssetClass(actual, expected),
            ),
          'Expected single reward asset in the stability pool',
        ).toBeTruthy();
      }
    });

    test<MyContext>('Worst case cross snapshot account deposit referencing 2 e2s2s snapshots; 1 reward asset; deposit account from scale 3 (4th entry), where the pools scale is 12, i.e. 0-3 scale are in a 1st snapshot, 4-7 in 2nd snapshot, 8-12 scale are in the pool datum', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [
          {
            ...iusdInitialAssetCfg(),
            collateralAssets: mkCollateralAssetsChain([
              mkBaseCollateralAsset(collateralAssetA, 0n),
            ]),
          },
        ],
        context.emulator.slot,
      );

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        50n,
        sysParams,
        context,
      );

      /**********
       * add iAssets to user1 wallet
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

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

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            5_000_010n,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Make multiple liquidations to reach scale 3
       ***********/

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      await repeat(6, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT + 10n,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      expect(
        (
          await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.state.scale,
        'Unexpected scale',
      ).toEqual(3n);

      /**********
       * Create user1 account at scale 3
       ***********/

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user1.address).hash,
          ),
        );
      }

      /**********
       * Make more liquidations to reach the limit of epoch2scale2sum
       ***********/

      await repeat(2, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/

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

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          ),
          'Expected every collateral assets e2s2s to be of the maximum size to be snapshoted',
        ).toBeTruthy();

        await runAndAwaitTx(
          context.lucid,
          runCreateE2s2sSnapshots(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          ),
        );

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) => assetState.epoch2scale2sum.length === 1,
          ),
          'Expected every e2s2s was snapshotted',
        ).toBeTruthy();
      }

      /**********
       * Make liquidations to reach scale 8
       ***********/

      await repeat(8, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(8n);
      }

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/

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

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          ),
          'Expected every collateral assets e2s2s to be of the maximum size to be snapshoted',
        ).toBeTruthy();

        await runAndAwaitTx(
          context.lucid,
          runCreateE2s2sSnapshots(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          ),
        );

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) => assetState.epoch2scale2sum.length === 1,
          ),
          'Expected every e2s2s was snapshotted',
        ).toBeTruthy();
      }

      /**********
       * Make liquidations to reach scale 12
       ***********/

      await repeat(8, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(12n);

        expect(
          sp.datum.assetStates.every(([_, assetState]) =>
            F.pipe(
              assetState.epoch2scale2sum,
              RA.findFirst(([key, _]) => key.epoch === 0n && key.scale === 3n),
              O.match(
                // Expect it not to be there.
                () => true,
                (_) => false,
              ),
            ),
          ),
          'The accounts expected epoch and scale cannot be in any of the stability pools e2s2s',
        ).toBeTruthy();
      }

      {
        const account = await findStabilityPoolAccount(
          context.lucid,
          sysParams,
          paymentCredentialOf(context.users.user1.address).hash,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(
          account.datum.state.epoch,
          'Expected account to be at epoch 0',
        ).toEqual(0n);
        expect(
          account.datum.state.scale,
          'Expected account to be at scale 4',
        ).toEqual(3n);
      }

      /**********
       * Make deposit and the rewards get calculated cross scale.
       ***********/

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          100n,
        ),
      );

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

      await benchmarkAndAwaitTx(
        'Stability Pool - Deposit Account, referencing 2 e2s2s snapshot (1 reward asset) (worst case) - Process',
        await runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
        context.lucid,
        context.emulator,
      );

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        const spCollateralAssets = sp.datum.assetStates.map(([key, _]) => key);

        const expectedSpCollateralAssets = [
          // Ada from account creation fee
          adaAssetClass,
          collateralAssetA,
        ];

        expect(
          sp.datum.assetStates.length === expectedSpCollateralAssets.length &&
            A.zip(spCollateralAssets)(expectedSpCollateralAssets).every(
              ([actual, expected]) => isSameAssetClass(actual, expected),
            ),
          'Expected single reward asset in the stability pool',
        ).toBeTruthy();
      }
    });

    test<MyContext>('Worst case cross snapshot account deposit referencing 2 e2s2s snapshots; 9 reward assets; all assets are known to account before adjust', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(9);

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        80n,
        sysParams,
        context,
      );

      /**********
       * add iAssets to user1 wallet
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

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

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            BigInt(initialAssets.length) * LIQUIDATED_DEBT +
              LIQUIDATED_DEBT +
              10n,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Add each collateral asset as reward to stability pool at epoch 0 and scale 0
       ***********/
      for (const collateral of initialAssets) {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateral, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(context, sysParams, iusdAssetInfo, collateral);

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateral,
          iusdAssetInfo,
        );
      }

      /**********
       * Make multiple liquidations to reach scale 3
       ***********/

      await repeat(6, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT + 10n,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      expect(
        (
          await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.state.scale,
        'Unexpected scale',
      ).toEqual(3n);

      /**********
       * Create user1 account at scale 3
       ***********/

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user1.address).hash,
          ),
        );
      }

      /**********
       * Make more liquidations to reach the limit of epoch2scale2sum
       ***********/

      await repeat(2, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/

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

        {
          const sp = await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          );

          expect(
            sp.datum.assetStates.length,
            'Expected all reward assets in SP',
          ).toEqual(initialAssets.length);

          expect(
            sp.datum.assetStates.every(
              ([_, assetState]) =>
                assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
            ),
            'Expected every collateral assets e2s2s to be of the maximum size to be snapshoted',
          ).toBeTruthy();
        }

        await runAndAwaitTx(
          context.lucid,
          runCreateE2s2sSnapshots(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          ),
        );

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) => assetState.epoch2scale2sum.length === 1,
          ),
          'Expected every e2s2s was snapshotted',
        ).toBeTruthy();
      }

      /**********
       * Make liquidations to reach scale 8
       ***********/

      await repeat(8, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(8n);
      }

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/

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

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          ),
          'Expected every collateral assets e2s2s to be of the maximum size to be snapshoted',
        ).toBeTruthy();

        await runAndAwaitTx(
          context.lucid,
          runCreateE2s2sSnapshots(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          ),
        );

        expect(
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.every(
            ([_, assetState]) => assetState.epoch2scale2sum.length === 1,
          ),
          'Expected every e2s2s was snapshotted',
        ).toBeTruthy();
      }

      /**********
       * Make liquidations to reach scale 12
       ***********/

      await repeat(8, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(12n);

        expect(
          sp.datum.assetStates.every(([_, assetState]) =>
            F.pipe(
              assetState.epoch2scale2sum,
              RA.findFirst(([key, _]) => key.epoch === 0n && key.scale === 3n),
              O.match(
                // Expect it not to be there.
                () => true,
                (_) => false,
              ),
            ),
          ),
          'The accounts expected epoch and scale cannot be in any of the stability pools e2s2s',
        ).toBeTruthy();
      }

      {
        const account = await findStabilityPoolAccount(
          context.lucid,
          sysParams,
          paymentCredentialOf(context.users.user1.address).hash,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(
          account.datum.state.epoch,
          'Expected account to be at epoch 0',
        ).toEqual(0n);
        expect(
          account.datum.state.scale,
          'Expected account to be at scale 4',
        ).toEqual(3n);

        expect(
          account.datum.assetSums.length,
          'Expected all assets to be known by account',
        ).toEqual(initialAssets.length);
      }

      /**********
       * Make deposit and the rewards get calculated cross scale.
       ***********/

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          100n,
        ),
      );

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

      await benchmarkAndAwaitTx(
        'Stability Pool - Deposit Account, referencing 2 e2s2s snapshot (9 reward asset known by account before) (worst case) - Process',
        await runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
        context.lucid,
        context.emulator,
      );

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        const spCollateralAssets = sp.datum.assetStates.map(([key, _]) => key);

        const expectedSpCollateralAssets = [...initialAssets];

        expect(
          sp.datum.assetStates.length === expectedSpCollateralAssets.length &&
            A.zip(spCollateralAssets)(expectedSpCollateralAssets).every(
              ([actual, expected]) => isSameAssetClass(actual, expected),
            ),
          'Expected all initial assets to be reward assets in the stability pool',
        ).toBeTruthy();
      }
    });

    test<MyContext>('Worst case cross snapshot account deposit referencing 2 e2s2s snapshots; 9 reward assets; most assets are unknown to account', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
        collateralAssetC,
        collateralAssetD,
        collateralAssetE,
        collateralAssetF,
        collateralAssetG,
        collateralAssetH,
      ];

      expect(initialAssets.length, 'Unexpected reward assets count').toEqual(9);

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        80n,
        sysParams,
        context,
      );

      /**********
       * add iAssets to user1 wallet
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user1.seedPhrase);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

      /**********
       * Create account for user2
       ***********/
      {
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            5_000_010n,
            sysParams,
            context.lucid,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      }

      /**********
       * Make multiple liquidations to reach scale 3
       ***********/

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      await repeat(6, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT + 10n,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      expect(
        (
          await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.state.scale,
        'Unexpected scale',
      ).toEqual(3n);

      /**********
       * Create user1 account at scale 3
       ***********/

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

        await runAndAwaitTx(
          context.lucid,
          requestSpAccountCreation(
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
            sysParams,
            context.lucid,
          ),
        );
        context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user1.address).hash,
          ),
        );
      }

      /**********
       * Add each collateral asset as reward to stability pool
       ***********/

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

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            BigInt(initialAssets.length) * LIQUIDATED_DEBT,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

        for (const collateral of initialAssets) {
          context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

          // Send funds to user4 so liquidation is performed from a clean address.
          await sendValueTo(
            context.users.user4.address,
            mkAssetsOf(collateral, LIQUIDATED_COLLATERAL),
            context.lucid,
          );

          context.emulator.awaitSlot(1000);

          await refreshPriceOracle(
            context,
            sysParams,
            iusdAssetInfo,
            collateral,
          );

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

          await executeLiquidation(
            context,
            sysParams,
            LIQUIDATED_DEBT,
            LIQUIDATED_COLLATERAL,
            collateral,
            iusdAssetInfo,
          );
        }
      }

      /**********
       * Make more liquidations to reach the limit of epoch2scale2sum
       ***********/

      await repeat(2, async (isLast) => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );

        if (!isLast) {
          context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

          await runAndAwaitTx(
            context.lucid,
            runCreateAdjustRequest(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              LIQUIDATED_DEBT,
            ),
          );

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

          await runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user2.address).hash,
            ),
          );
        }
      });

      expect(
        (
          await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          )
        ).datum.state.scale,
        'Unexpected scale',
      ).toEqual(BigInt(MAX_E2S2S_ENTRIES_COUNT - 1));

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/

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

        {
          const sp = await findStabilityPool(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
          );

          expect(
            sp.datum.assetStates.length,
            'Expected all reward assets in SP',
          ).toEqual(initialAssets.length);
        }

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

      /**********
       * Make liquidations to reach scale 8
       ***********/

      await repeat(7, async () => {
        // When any needs snapshotting
        if (
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.some(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          )
        ) {
          await runAndAwaitTx(
            context.lucid,
            runCreateE2s2sSnapshots(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            ),
          );
        }

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

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(8n);
      }

      /**********
       * Snapshot the epoch2scale2sum.
       ***********/

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

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

      /**********
       * Make liquidations to reach scale 12
       ***********/

      await repeat(8, async () => {
        // When any needs snapshotting
        if (
          (
            await findStabilityPool(
              context.lucid,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            )
          ).datum.assetStates.some(
            ([_, assetState]) =>
              assetState.epoch2scale2sum.length === MAX_E2S2S_ENTRIES_COUNT,
          )
        ) {
          await runAndAwaitTx(
            context.lucid,
            runCreateE2s2sSnapshots(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
            ),
          );
        }

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

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT + 10n,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
      });

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected scale').toEqual(12n);

        expect(
          sp.datum.assetStates.every(([_, assetState]) =>
            F.pipe(
              assetState.epoch2scale2sum,
              RA.findFirst(([key, _]) => key.epoch === 0n && key.scale === 3n),
              O.match(
                // Expect it not to be there.
                () => true,
                (_) => false,
              ),
            ),
          ),
          'The accounts expected epoch and scale cannot be in any of the stability pools e2s2s',
        ).toBeTruthy();
      }

      {
        const account = await findStabilityPoolAccount(
          context.lucid,
          sysParams,
          paymentCredentialOf(context.users.user1.address).hash,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(
          account.datum.state.epoch,
          'Expected account to be at epoch 0',
        ).toEqual(0n);
        expect(
          account.datum.state.scale,
          'Expected account to be at scale 4',
        ).toEqual(3n);

        expect(
          account.datum.assetSums.length,
          'Expected only single asset to be known by account (and ADA from account creation fees)',
        ).toEqual(2);
      }

      /**********
       * Make deposit and the rewards get calculated cross scale.
       ***********/

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          100n,
        ),
      );

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

      await benchmarkAndAwaitTx(
        'Stability Pool - Deposit Account, referencing 2 e2s2s snapshot 1x and 1 e2s2s 9x (9 reward asset most of them UNKNOWN by the account) (worst case) - Process',
        await runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
        context.lucid,
        context.emulator,
      );

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        const spCollateralAssets = sp.datum.assetStates.map(([key, _]) => key);

        const expectedSpCollateralAssets = [...initialAssets];

        expect(
          sp.datum.assetStates.length === expectedSpCollateralAssets.length &&
            A.zip(spCollateralAssets)(expectedSpCollateralAssets).every(
              ([actual, expected]) => isSameAssetClass(actual, expected),
            ),
          'Expected all initial assets to be reward assets in the stability pool',
        ).toBeTruthy();
      }
    });

    test<MyContext>('Create account, afterwards 2 new collateral assets are whitelisted, and a liquidation with them happens in reverse as theyve been whitelisted, user should withdraw rewards from new assets as well', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialCollateralAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
      ];

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        10n,
        sysParams,
        context,
      );

      /**********
       * Create account for user1 and user2
       ***********/

      const SINGLE_ACCOUNT_DEPOSIT = 10_000_000n;

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            10_000_000_000n,
            3_000_000_000n,
          ),
        );

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

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      /**********
       * Liquidate CDP with collateral asset A
       ***********/

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

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      // Send funds to user4 so liquidation is performed from a clean address.
      await sendValueTo(
        context.users.user4.address,
        mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
        context.lucid,
      );

      context.emulator.awaitSlot(1000);

      await refreshPriceOracle(
        context,
        sysParams,
        iusdAssetInfo,
        collateralAssetA,
      );

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        collateralAssetA,
        iusdAssetInfo,
      );

      /**********
       * Whitelist the collateralAssetC
       ***********/

      const newCollateralAssetCInfo = await whitelistCollateralAsset(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetC,
        rationalFromInt(1n),
        0n,
        true,
      );

      /**********
       * Whitelist the collateralAssetD
       ***********/

      const newCollateralAssetDInfo = await whitelistCollateralAsset(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetD,
        rationalFromInt(1n),
        0n,
        false,
      );

      /**********
       * Liquidate CDP with collateral asset D (the new whitelisted one)
       ***********/

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetD, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          {
            iassetTokenNameAscii: iusdAssetInfo.iassetTokenNameAscii,
            collateralAssets: [newCollateralAssetDInfo],
          },
          collateralAssetD,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetD,
          {
            iassetTokenNameAscii: iusdAssetInfo.iassetTokenNameAscii,
            collateralAssets: [newCollateralAssetDInfo],
          },
        );
      }

      {
        /**********
         * Liquidate CDP with collateral asset C (the new whitelisted one)
         ***********/

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

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetC, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          {
            iassetTokenNameAscii: iusdAssetInfo.iassetTokenNameAscii,
            collateralAssets: [newCollateralAssetCInfo],
          },
          collateralAssetC,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetC,
          {
            iassetTokenNameAscii: iusdAssetInfo.iassetTokenNameAscii,
            collateralAssets: [newCollateralAssetCInfo],
          },
        );
      }

      /**********
       * Deposit to user1 account and withdraw rewards
       ***********/

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
        ),
      );

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

      const [_, fundsReceived] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.user1.address,
        async () =>
          runAndAwaitTx(
            context.lucid,
            runProcessSpRequest(
              context,
              sysParams,
              iusdAssetInfo.iassetTokenNameAscii,
              paymentCredentialOf(context.users.user1.address).hash,
            ),
          ),
      );

      const iasset = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const expectedRewardAmt =
        (LIQUIDATED_COLLATERAL -
          calculateFeeFromRatio(
            iasset.datum.liquidationProcessingFeeRatio,
            LIQUIDATED_COLLATERAL,
          )) /
        2n;

      const expectedValReceived = addAssets(
        mkAssetsOf(collateralAssetA, expectedRewardAmt),
        mkAssetsOf(collateralAssetC, expectedRewardAmt),
        mkAssetsOf(collateralAssetD, expectedRewardAmt),
        // For account creation fees
        mkLovelacesOf(
          BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) +
            BigInt(sysParams.stabilityPoolParams.accountCreateFeeLovelaces) /
              2n,
        ),
      );

      expectValue(
        fundsReceived,
        'Expected the withdrawn amount and the reward in the output not taking into account ADA',
      ).toEqual(expectedValReceived);

      const sp = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const spCollateralAssets = sp.datum.assetStates.map(([key, _]) => key);

      // In the order how the liquidations happened.
      const expectedSpCollateralAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetD,
        collateralAssetC,
      ];

      // NOTE: the newly whitelisted collateral assets order is based on the order of liquidations instead of
      // on the order of collateral assets chain.
      expect(
        sp.datum.assetStates.length === expectedSpCollateralAssets.length &&
          A.zip(spCollateralAssets)(expectedSpCollateralAssets).every(
            ([actual, expected]) => isSameAssetClass(actual, expected),
          ),
        'Expected the specific order of collateral assets in the stability pool based on liquidations',
      ).toBeTruthy();

      const account = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        paymentCredentialOf(context.users.user1.address).hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      expect(
        account.datum.assetSums.length === expectedSpCollateralAssets.length &&
          A.zip(account.datum.assetSums.map(([key, _]) => key))(
            expectedSpCollateralAssets,
          ).every(([actual, expected]) => isSameAssetClass(actual, expected)),
        'Expected accounts collateral assets to be the same and in the same order after an update as stability pool ones',
      ).toBeTruthy();
    });

    test<MyContext>("Create account at scale 0, afterwards a new collateral asset is whitelisted, it's used in liquidation with scale + 1 from account, user should withdraw rewards", async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const initialCollateralAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetB,
      ];

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

      await createMultipleUtxosAtTreasury(
        mkLovelacesOf(2n),
        10n,
        sysParams,
        context,
      );

      /**********
       * Create account for user1 and user2
       ***********/

      const SINGLE_ACCOUNT_DEPOSIT = 2_500_005n;

      for (const u of [context.users.user1, context.users.user2]) {
        context.lucid.selectWallet.fromSeed(u.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runOpenCdp(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            adaAssetClass,
            10_000_000_000n,
            3_000_000_000n,
          ),
        );

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

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(u.address).hash,
          ),
        );
      }

      /**********
       * Make liquidations to create scale 1
       ***********/

      // After price doubles the collateral ratio will be 110%
      const LIQUIDATED_DEBT = 5_000_000n;
      const LIQUIDATED_COLLATERAL = 11_000_000n;

      await repeat(2, async () => {
        context.lucid.selectWallet.fromSeed(context.users.user3.seedPhrase);

        // Send funds to user4 so liquidation is performed from a clean address.
        await sendValueTo(
          context.users.user4.address,
          mkAssetsOf(collateralAssetA, LIQUIDATED_COLLATERAL),
          context.lucid,
        );

        context.emulator.awaitSlot(1000);

        await refreshPriceOracle(
          context,
          sysParams,
          iusdAssetInfo,
          collateralAssetA,
        );

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

        await executeLiquidation(
          context,
          sysParams,
          LIQUIDATED_DEBT,
          LIQUIDATED_COLLATERAL,
          collateralAssetA,
          iusdAssetInfo,
        );
        context.lucid.selectWallet.fromSeed(context.users.user2.seedPhrase);

        await runAndAwaitTx(
          context.lucid,
          runCreateAdjustRequest(
            context.lucid,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            LIQUIDATED_DEBT,
          ),
        );

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

        await runAndAwaitTx(
          context.lucid,
          runProcessSpRequest(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            paymentCredentialOf(context.users.user2.address).hash,
          ),
        );
      });

      /**********
       * Expect stability pool epoch and scale
       ***********/

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected sp epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected sp scale').toEqual(1n);
      }

      /**********
       * Whitelist the collateralAssetC
       ***********/

      const newCollateralAssetCInfo = await whitelistCollateralAsset(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAssetC,
        rationalFromInt(1n),
        0n,
        true,
      );

      /**********
       * Liquidate CDP with collateral asset C (the new whitelisted one)
       ***********/

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

      // Send funds to user4 so liquidation is performed from a clean address.
      await sendValueTo(
        context.users.user4.address,
        mkAssetsOf(collateralAssetC, LIQUIDATED_COLLATERAL),
        context.lucid,
      );

      context.emulator.awaitSlot(1000);

      await refreshPriceOracle(
        context,
        sysParams,
        {
          iassetTokenNameAscii: iusdAssetInfo.iassetTokenNameAscii,
          collateralAssets: [newCollateralAssetCInfo],
        },
        collateralAssetC,
      );

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

      await executeLiquidation(
        context,
        sysParams,
        LIQUIDATED_DEBT,
        LIQUIDATED_COLLATERAL,
        collateralAssetC,
        {
          iassetTokenNameAscii: iusdAssetInfo.iassetTokenNameAscii,
          collateralAssets: [newCollateralAssetCInfo],
        },
      );

      /**********
       * Expect stability pool epoch and scale
       ***********/

      {
        const sp = await findStabilityPool(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(sp.datum.state.epoch, 'Unexpected sp epoch').toEqual(0n);
        expect(sp.datum.state.scale, 'Unexpected sp scale').toEqual(1n);
      }

      /**********
       * Expect acount epoch and scale
       ***********/

      {
        const account = await findStabilityPoolAccount(
          context.lucid,
          sysParams,
          paymentCredentialOf(context.users.user1.address).hash,
          iusdAssetInfo.iassetTokenNameAscii,
        );

        expect(account.datum.state.epoch, 'Expected account epoch').toEqual(0n);
        expect(account.datum.state.scale, 'Expected account scale').toEqual(0n);
      }

      /**********
       * Deposit to user1 account and withdraw rewards
       ***********/

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

      await runAndAwaitTx(
        context.lucid,
        runCreateAdjustRequest(
          context.lucid,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          10n,
        ),
      );

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

      await runAndAwaitTx(
        context.lucid,
        runProcessSpRequest(
          context,
          sysParams,
          iusdAssetInfo.iassetTokenNameAscii,
          paymentCredentialOf(context.users.user1.address).hash,
        ),
      );

      const sp = await findStabilityPool(
        context.lucid,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const spCollateralAssets = sp.datum.assetStates.map(([key, _]) => key);

      const expectedSpCollateralAssets = [
        adaAssetClass,
        collateralAssetA,
        collateralAssetC,
      ];

      expect(
        sp.datum.assetStates.length === expectedSpCollateralAssets.length &&
          A.zip(spCollateralAssets)(expectedSpCollateralAssets).every(
            ([actual, expected]) => isSameAssetClass(actual, expected),
          ),
        'Expected the specific order of collateral assets in the stability pool',
      ).toBeTruthy();

      const account = await findStabilityPoolAccount(
        context.lucid,
        sysParams,
        paymentCredentialOf(context.users.user1.address).hash,
        iusdAssetInfo.iassetTokenNameAscii,
      );

      expect(account.datum.state.epoch, 'Expected account epoch').toEqual(0n);
      expect(account.datum.state.scale, 'Expected account scale').toEqual(1n);

      expect(
        account.datum.assetSums.length === expectedSpCollateralAssets.length &&
          A.zip(account.datum.assetSums.map(([key, _]) => key))(
            expectedSpCollateralAssets,
          ).every(([actual, expected]) => isSameAssetClass(actual, expected)),
        'Expected accounts collateral assets to be the same and in the same order after an update as stability pool ones',
      ).toBeTruthy();
    });
  });
});
