import { assert, beforeEach, test } from 'vitest';
import {
  IndigoTestContext,
  repeat,
  runAndAwaitTx,
  runAndAwaitTxBuilder,
} from '../test-helpers';
import {
  createIndigoTestContext,
  EXAMPLE_TOKEN_1,
} from '../indigo-test-helpers';
import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
import {
  batchProcessStableswapOrders,
  cancelStableswapOrder,
  createStableswapOrder,
  updateStableswapPoolFees,
} from '../../src/contracts/stableswap/transactions';
import {
  createScriptAddress,
  fromSystemParamsAsset,
  mkTreasuryAddr,
} from '../../src';
import {
  addAssets,
  Assets,
  credentialToAddress,
  fromHex,
  fromText,
  paymentCredentialOf,
  UTxO,
} from '@lucid-evolution/lucid';
import {
  findAllNecessaryOrefs,
  findPriceOracleFromCollateralAsset,
  findStableswapPool,
} from '../cdp/cdp-queries';
import {
  findSingleStableswapOrder,
  findStableswapOrders,
} from './stableswap-queries';
import { feedPriceOracleTx } from '../../src/contracts/price-oracle/transactions';
import {
  addressFromBech32,
  assetClassValueOf,
  lovelacesAmt,
  mkLovelacesOf,
  negateAssets,
} from '@3rd-eye-labs/cardano-offchain-common';
import { runCreateStableswapPool } from './stableswap-actions';
import { getValueChangeAtAddressAfterAction } from '../utils';
import { array as A } from 'fp-ts';
import { runOpenCdp } from '../cdp/actions';
import { Data } from '@evolution-sdk/evolution';
import { serialiseStableswapOrderDatum } from '../../src/contracts/stableswap/types-new';
import { BASE_MAX_EXECUTION_FEE } from '../../src/contracts/stableswap/helpers';
import { StableswapPoolContent } from '../../src/contracts/cdp/types-new';
import { mutatedBatchProcessStableswapOrders } from './transactions-mutated';
import { expectScriptFailure } from '../utils/asserts';
import {
  findRandomTreasuryUtxoWithOnlyAda,
  findRandomTreasuryUtxoWithAsset,
} from '../treasury/treasury-queries';
import { createUtxoAtTreasury } from '../endpoints/treasury';
import { rationalFromInt, rationalZero } from '../../src/types/rational';

beforeEach<IndigoTestContext>(async (context: IndigoTestContext) => {
  await createIndigoTestContext(context);
});

test<IndigoTestContext>('Stableswap - Create Order', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  const stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  await benchmarkAndAwaitTx(
    'Stableswap - Create Order',
    await createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
    context.lucid,
    context.emulator,
  );
});

test<IndigoTestContext>('Stableswap - Cancel Order', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  const stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await benchmarkAndAwaitTx(
    'Stableswap - Cancel Order',
    await cancelStableswapOrder(
      stableswapOrder.utxo,
      context.systemParams,
      context.lucid,
    ),
    context.lucid,
    context.emulator,
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to mint', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

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

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      benchmarkAndAwaitTx(
        'Stableswap - Batch a single order to mint',
        await batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000n - 50_000n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to mint with no minting fee', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    rationalFromInt(1n),
    // 0% minting fee.
    rationalZero,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      await runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 0n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to mint with collateral asset in the pool', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

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

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  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));

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      benchmarkAndAwaitTx(
        'Stableswap - Batch a single order to mint with collateral in the pool',
        await batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  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),
  );

  assert(
    lovelacesAmt(adminValueDiff) >= 0n &&
      lovelacesAmt(adminValueDiff) < 100_000n,
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      20_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000n - 50_000n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to redeem', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      5_000_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  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 [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      benchmarkAndAwaitTx(
        'Stableswap - Batch a single order to redeem',
        await batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  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),
  );

  assert(
    lovelacesAmt(adminValueDiff) >= 0n &&
      lovelacesAmt(adminValueDiff) < 100_000n,
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 25_000n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n - (5_000_000n - 25_000n),
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      10_000_000n - 50_000n - 5_000_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + (5_000_000n - 25_000n),
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to redeem with no redemption fee', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    rationalFromInt(1n),
    // 0% minting fee.
    rationalZero,
    // 0% redemption fee.
    rationalZero,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  // Mint iAsset to supply the pool with collateral asset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      5_000_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  // This will not be used in the transaction, but is passed only as a placeholder.
  treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  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 [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  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),
  );

  // This one will result in a bigger gain for the admin as the tx fee is lower
  assert(lovelacesAmt(adminValueDiff) >= 0n);

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 0n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n - 5_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000n - 5_000_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + 5_000_000n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to redeem emptying the pool', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const orefs = await findAllNecessaryOrefs(
    context.lucid,
    context.systemParams,
    context.assetConfigs[0].iassetTokenNameAscii,
    context.assetConfigs[0].collateralAssets[0].collateralAsset,
  );
  const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
    context.lucid,
    orefs.collateralAsset,
  );

  await runAndAwaitTx(
    context.lucid,
    feedPriceOracleTx(
      context.lucid,
      priceOracleUtxo!,
      rationalFromInt(1n),
      context.assetConfigs[0].collateralAssets[0].oracleParams!,
      context.emulator.slot,
    ),
  );

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

  // Mint extra iAsset to have enough to empty the pool.
  await runAndAwaitTx(
    context.lucid,
    runOpenCdp(
      context,
      context.systemParams,
      context.assetConfigs[0].iassetTokenNameAscii,
      context.assetConfigs[0].collateralAssets[0].collateralAsset,
      10_000_000n,
      5_000_000n,
    ),
  );

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

  // Mint iAsset to supply the pool with collateral asset
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem all the assets left in the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      // After taking a 0.5% fee, the effective amount is 10_000_000.
      10_050_251n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  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 [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  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),
  );

  assert(
    lovelacesAmt(adminValueDiff) >= 0n &&
      lovelacesAmt(adminValueDiff) < 100_000n,
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_251n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) == 0n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      10_000_000n - 50_000n - 10_050_251n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + 10_000_000n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to mint assets with smaller scale', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    rationalFromInt(1_000n),
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000n - 50n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to mint assets with bigger scale', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    { numerator: 1n, denominator: 1_000n },
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000_000n - 50_000_000n,
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to redeem assets with smaller scale', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    rationalFromInt(1_000n),
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      5_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 25n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n - (5_000_000n - 25_000n),
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000n - 50n - 5_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + (5_000_000n - 25_000n),
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch a single order to redeem assets with bigger scale', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    { numerator: 1n, denominator: 1_000n },
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      5_000_000_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrder.datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          [stableswapOrder.utxo],
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 25_000_000n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n - (5_000_000n - 25_000n),
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      10_000_000_000n - 50_000_000n - 5_000_000_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + (5_000_000n - 25_000n),
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch multiple orders to mint from same owner, 11', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  const numberOfOrders = 11;

  await repeat(numberOfOrders, async () => {
    await runAndAwaitTx(
      context.lucid,
      createStableswapOrder(
        context.assetConfigs[0].iassetTokenNameAscii,
        EXAMPLE_TOKEN_1,
        10_000_000n,
        true,
        stableswapPool.datum,
        context.systemParams,
        context.lucid,
      ),
    );
  });

  const stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      benchmarkAndAwaitTx(
        'Stableswap - Batch multiple orders to mint from same owner, 11',
        await batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) ==
      BigInt(numberOfOrders) * 50_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      BigInt(numberOfOrders) * 10_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      BigInt(numberOfOrders) * (10_000_000n - 50_000n),
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch multiple orders to redeem from same owner, 9', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  const numberOfOrders = 9;

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      BigInt(numberOfOrders) * 10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      stableswapOrders.map((order) => order.utxo),
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem part of the collateral asset previously supplied to the pool.
  await repeat(numberOfOrders, async () => {
    await runAndAwaitTx(
      context.lucid,
      createStableswapOrder(
        context.assetConfigs[0].iassetTokenNameAscii,
        EXAMPLE_TOKEN_1,
        5_000_000n,
        false,
        stableswapPool.datum,
        context.systemParams,
        context.lucid,
      ),
    );
  });

  stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      benchmarkAndAwaitTx(
        'Stableswap - Batch multiple orders to redeem from same owner, 9',
        await batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) ==
      BigInt(numberOfOrders) * 25_000n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      BigInt(numberOfOrders) * (10_000_000n - (5_000_000n - 25_000n)),
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      BigInt(numberOfOrders) * (10_000_000n - 50_000n - 5_000_000n) &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        BigInt(numberOfOrders) * (-10_000_000n + (5_000_000n - 25_000n)),
    'Unexpected value received by order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch multiple orders to mint from different owners', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      5_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n + 25_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n + 5_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == 10_000_000n - 50_000n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) == -10_000_000n,
    'Unexpected value received by order #1 owner',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 5_000_000n - 25_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) == -5_000_000n,
    'Unexpected value received by order #2 owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch multiple orders to mint with rounded amounts', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    // 0.999991 relative price
    { numerator: 1_000_000n, denominator: 1_000_009n },
    //15% fee
    { numerator: 15n, denominator: 100n },
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  const numberOfOrders = 10;

  await repeat(numberOfOrders, async () => {
    await runAndAwaitTx(
      context.lucid,
      createStableswapOrder(
        context.assetConfigs[0].iassetTokenNameAscii,
        EXAMPLE_TOKEN_1,
        // The eq. iAsset amount is 100,000.9, rounded to 100,000.
        100_000n,
        true,
        stableswapPool.datum,
        context.systemParams,
        context.lucid,
      ),
    );
  });

  const stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  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 [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) ==
      BigInt(numberOfOrders) * 15_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      BigInt(numberOfOrders) * 100_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      BigInt(numberOfOrders) * (100_000n - 15_000n) &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        BigInt(numberOfOrders) * -100_000n,
    'Unexpected value received by orders owner',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == 0n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) == 0n,
    'Unexpected value received by the admin',
  );
});

test<IndigoTestContext>('Stableswap - Batch orders to mint with insignificant value', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    // The collateral asset has one more decimal place than the iAsset.
    rationalFromInt(10n),
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      // 1.0000001 of collateral asset, equivalent to 1 iAsset.
      10_000_001n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

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

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 5_000n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      10_000_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 1_000_000n - 5_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) == -10_000_001n,
    'Unexpected value received by orders owner',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == 0n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) == 1n,
    'Unexpected value received by the admin',
  );
});

test<IndigoTestContext>('Stableswap - Batch multiple orders to redeem from different owners', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

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

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

  // Mint iAsset to supply the pool with collateral asset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      stableswapOrders.map((order) => order.utxo),
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

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

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      5_000_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

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

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      2_500_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const numberOfOrders = 2;

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 25_000n + 12_500n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      BigInt(numberOfOrders) * 10_000_000n -
        (5_000_000n - 25_000n) -
        (2_500_000n - 12_500n),
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) ==
      10_000_000n - 50_000n - 5_000_000n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + (5_000_000n - 25_000n),
    'Unexpected value received by order #1 owner',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      10_000_000n - 50_000n - 2_500_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        -10_000_000n + (2_500_000n - 12_500n),
    'Unexpected value received by order #2 owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch multiple orders to redeem with rounded amounts', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    // 1.000019 relative price
    { numerator: 1_000_000n, denominator: 999_981n },
    // 1% fee
    { numerator: 1n, denominator: 100n },
    // 0.0015% fee
    { numerator: 15n, denominator: 1_000_000n },
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  const numberOfOrders = 3;

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      BigInt(numberOfOrders) * 200_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

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

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      stableswapOrders.map((order) => order.utxo),
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

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

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

  // Redeem part of the collateral asset previously supplied to the pool.
  await repeat(numberOfOrders, async () => {
    await runAndAwaitTx(
      context.lucid,
      createStableswapOrder(
        context.assetConfigs[0].iassetTokenNameAscii,
        EXAMPLE_TOKEN_1,
        // The fee will be 1.5 iAssets, rounded to 1.
        // The equivalent collateral amt after fees is 100000.9000171, rounded to 100,000.
        100_000n,
        false,
        stableswapPool.datum,
        context.systemParams,
        context.lucid,
      ),
    );
  });

  stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  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 [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      await runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

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

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) ==
      BigInt(numberOfOrders) * 1n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      BigInt(numberOfOrders) * (200_000n - 100_000n),
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) ==
      BigInt(numberOfOrders) * -100_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) ==
        BigInt(numberOfOrders) * 100_000n,
    'Unexpected value received by order owner',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == 0n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) == 0n,
    'Unexpected value received by the admin',
  );
});

test<IndigoTestContext>('Stableswap - Batch an order to redeem with insignificant value', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
    // The iAsset has one more decimal place than the collateral asset.
    { numerator: 1n, denominator: 10n },
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      2_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      stableswapOrders.map((order) => order.utxo),
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

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

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

  // Redeem part of the collateral asset previously supplied to the pool.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      // 1.000001 of iAsset, equivalent to 1 collateral asset.
      10_000_001n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  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 [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      await runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

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

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      2_000_000n - 1_000_000n + 5_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == -10_000_001n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) == 1_000_000n - 5_000n,
    'Unexpected value received by order owner',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == 1n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) == 0n,
    'Unexpected value received by the admin',
  );
});

test<IndigoTestContext>('Stableswap - Batch an order to mint with an order to redeem', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      20_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  let stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      stableswapOrders.map((order) => order.utxo),
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

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

  // Order to redeem.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

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

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

  // Order to mint.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n + 50_000n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  // The first 20_000_000 come from the minting order processed before the batch.
  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      20_000_000n + 50_000n,
    'Unexpected collateral asset value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000n - 50_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) == -10_000_000n,
    'Unexpected value received by minting order owner',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == -10_000_000n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) ==
        10_000_000n - 50_000n,
    'Unexpected value received by redeeming order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch an order to mint with an order to redeem (with destination datum)', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  // Mint iAsset to supply the pool with collateral asset.
  // It also provides the treasury with a UTxO with iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      20_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
      undefined,
      Data.fromCBORHex('d87980'),
    ),
  );

  let stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      stableswapOrders.map((order) => order.utxo),
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

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

  // Order to redeem.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
      undefined,
      Data.fromCBORHex('d87980'),
    ),
  );

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

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

  // Order to mint.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
      undefined,
      Data.fromCBORHex('d87a80'),
    ),
  );

  stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  treasuryUtxo = await findRandomTreasuryUtxoWithAsset(
    context.lucid,
    context.systemParams,
    iassetAc,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n + 50_000n &&
      0n <= lovelacesAmt(treasuryValChange) &&
      lovelacesAmt(treasuryValChange) <= 9_000n,
    'Unexpected value received by the treasury',
  );

  // This comes from the minting order processed before the batch.
  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) ==
      20_000_000n + 50_000n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == 10_000_000n - 50_000n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) == -10_000_000n,
    'Unexpected value received by minting order owner',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == -10_000_000n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) ==
        10_000_000n - 50_000n,
    'Unexpected value received by redeeming order owner',
  );
});

test<IndigoTestContext>('Stableswap - Batch an order to mint with an order to redeem with an empty pool', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  let orefs = await findAllNecessaryOrefs(
    context.lucid,
    context.systemParams,
    context.assetConfigs[0].iassetTokenNameAscii,
    context.assetConfigs[0].collateralAssets[0].collateralAsset,
  );
  const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
    context.lucid,
    orefs.collateralAsset,
  );

  await runAndAwaitTx(
    context.lucid,
    feedPriceOracleTx(
      context.lucid,
      priceOracleUtxo!,
      rationalFromInt(1n),
      context.assetConfigs[0].collateralAssets[0].oracleParams!,
      context.emulator.slot,
    ),
  );

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

  // Order to mint.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

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

  orefs = await findAllNecessaryOrefs(
    context.lucid,
    context.systemParams,
    context.assetConfigs[0].iassetTokenNameAscii,
    context.assetConfigs[0].collateralAssets[0].collateralAsset,
  );

  // Get some iAsset to be able to redeem from stableswap pool.
  await runAndAwaitTx(
    context.lucid,
    runOpenCdp(
      context,
      context.systemParams,
      context.assetConfigs[0].iassetTokenNameAscii,
      context.assetConfigs[0].collateralAssets[0].collateralAsset,
      50_000_000n,
      20_000_000n,
    ),
  );

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

  // Order to redeem.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_050_251n,
      false,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
    ),
  );

  const stableswapOrders = await findStableswapOrders(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  const [__, treasuryValChange] = await getValueChangeAtAddressAfterAction(
    context.lucid,
    mkTreasuryAddr(context.lucid, context.systemParams),
    async () =>
      runAndAwaitTx(
        context.lucid,
        batchProcessStableswapOrders(
          stableswapOrders.map((order) => order.utxo),
          stableswapPool.utxo,
          treasuryUtxo,
          context.systemParams,
          context.lucid,
        ),
      ),
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

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

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

  const userValueDiff = addAssets(
    finalUserValue,
    negateAssets(initialUserValue),
  );

  const iassetAc = {
    currencySymbol: fromHex(
      context.systemParams.stableswapParams.iassetSymbol.unCurrencySymbol,
    ),
    tokenName: stableswapOrders[0].datum.iasset,
  };

  assert(
    assetClassValueOf(treasuryValChange, iassetAc) == 50_000n + 50_251n,
    'Unexpected iAsset value received by the treasury',
  );

  assert(
    assetClassValueOf(stableswapPool.utxo.assets, EXAMPLE_TOKEN_1) == 0n,
    'Unexpected value held by stableswap pool',
  );

  assert(
    assetClassValueOf(adminValueDiff, iassetAc) == 10_000_000n - 50_000n &&
      assetClassValueOf(adminValueDiff, EXAMPLE_TOKEN_1) == -10_000_000n,
    'Unexpected value received by minting order owner',
  );

  assert(
    assetClassValueOf(userValueDiff, iassetAc) == -10_050_251n &&
      assetClassValueOf(userValueDiff, EXAMPLE_TOKEN_1) == 10_000_000n,
    'Unexpected value received by redeeming order owner',
  );
});

test<IndigoTestContext>('Stableswap - Update fees from fee manager', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool: { utxo: UTxO; datum: StableswapPoolContent } =
    await findStableswapPool(
      context.lucid,
      context.systemParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
      fromText(context.assetConfigs[0].iassetTokenNameAscii),
      EXAMPLE_TOKEN_1,
    );

  const feeTxHash = await runAndAwaitTxBuilder(
    context.lucid,
    context.lucid.newTx().pay.ToAddress(
      credentialToAddress(context.lucid.config().network!, {
        hash: context.systemParams.validatorHashes.stableswapHash,
        type: 'Script',
      }),
      mkLovelacesOf(2_000_000n),
    ),
  );

  assert(
    stableswapPool.datum.mintingFeeRatio.numerator === 5n &&
      stableswapPool.datum.mintingFeeRatio.denominator === 1_000n,
    'Unexpected minting fee percentage prior to update',
  );

  assert(
    stableswapPool.datum.redemptionFeeRatio.numerator === 5n &&
      stableswapPool.datum.redemptionFeeRatio.denominator === 1_000n,
    'Unexpected redemption fee percentage prior to update',
  );

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

  await benchmarkAndAwaitTx(
    'Stableswap - Update fees',
    await updateStableswapPoolFees(
      stableswapPool.utxo,
      { txHash: feeTxHash, outputIndex: 0 },
      { numerator: 1n, denominator: 100n },
      { numerator: 1n, denominator: 100n },
      context.systemParams,
      context.lucid,
    ),
    context.lucid,
    context.emulator,
  );

  ///////////////////////////////////
  // Checks after last transaction //
  ///////////////////////////////////

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  assert(
    stableswapPool.datum.mintingFeeRatio.numerator === 1n &&
      stableswapPool.datum.mintingFeeRatio.denominator === 100n,
    'Unexpected minting fee percentage',
  );

  assert(
    stableswapPool.datum.mintingFeeRatio.numerator === 1n &&
      stableswapPool.datum.mintingFeeRatio.denominator === 100n,
    'Unexpected redemption fee percentage',
  );
});

test<IndigoTestContext>('Stableswap - Update fees from fee manager - fails with large minting fee', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  const stableswapPool: { utxo: UTxO; datum: StableswapPoolContent } =
    await findStableswapPool(
      context.lucid,
      context.systemParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
      fromText(context.assetConfigs[0].iassetTokenNameAscii),
      EXAMPLE_TOKEN_1,
    );

  const feeTxHash = await runAndAwaitTxBuilder(
    context.lucid,
    context.lucid.newTx().pay.ToAddress(
      credentialToAddress(context.lucid.config().network!, {
        hash: context.systemParams.validatorHashes.stableswapHash,
        type: 'Script',
      }),
      mkLovelacesOf(2_000_000n),
    ),
  );

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

  await expectScriptFailure(
    'Minting fee ratio is between [0%, 100%]',
    updateStableswapPoolFees(
      stableswapPool.utxo,
      { txHash: feeTxHash, outputIndex: 0 },
      { numerator: 1_005n, denominator: 10n },
      { numerator: 1n, denominator: 100n },
      context.systemParams,
      context.lucid,
    ),
  );
});

test<IndigoTestContext>('Stableswap - Update fees from fee manager - fails with large redemption fee', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  const stableswapPool: { utxo: UTxO; datum: StableswapPoolContent } =
    await findStableswapPool(
      context.lucid,
      context.systemParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
      fromText(context.assetConfigs[0].iassetTokenNameAscii),
      EXAMPLE_TOKEN_1,
    );

  const feeTxHash = await runAndAwaitTxBuilder(
    context.lucid,
    context.lucid.newTx().pay.ToAddress(
      credentialToAddress(context.lucid.config().network!, {
        hash: context.systemParams.validatorHashes.stableswapHash,
        type: 'Script',
      }),
      mkLovelacesOf(2_000_000n),
    ),
  );

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

  await expectScriptFailure(
    'Redemption fee ratio is between [0%, 100%]',
    updateStableswapPoolFees(
      stableswapPool.utxo,
      { txHash: feeTxHash, outputIndex: 0 },
      { numerator: 1n, denominator: 100n },
      { numerator: 1_005n, denominator: 10n },
      context.systemParams,
      context.lucid,
    ),
  );
});

test<IndigoTestContext>('Stableswap - Process a two-step order, ex. USDC -> iUSD -> USDC', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  let stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
  const userAddress = await context.lucid.wallet().address();
  const pkh = paymentCredentialOf(userAddress);

  // Create order to mint iAsset, forwarding to a stableswap order to redeem the iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
      createScriptAddress(
        context.lucid.config().network!,
        context.systemParams.validatorHashes.stableswapHash,
      ),
      Data.fromCBORHex(
        serialiseStableswapOrderDatum({
          iasset: fromHex(
            fromText(context.assetConfigs[0].iassetTokenNameAscii),
          ),
          collateralAsset: EXAMPLE_TOKEN_1,
          owner: fromHex(pkh.hash),
          destination: addressFromBech32(userAddress),
          destinationInlineDatum: null,
          maxExecutionFee: BASE_MAX_EXECUTION_FEE,
          maxFeeRatio: stableswapPool.datum.mintingFeeRatio,
        }),
      ),
      BASE_MAX_EXECUTION_FEE,
      1_000_000n,
    ),
  );

  let stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  let treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );

  // Redeem part of the collateral asset previously supplied to the pool.
  stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await runAndAwaitTx(
    context.lucid,
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );
});

test<IndigoTestContext>('Stableswap - Batcher tries to steal more than the max execution fee', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  const stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  // Create order to mint iAsset.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
      undefined,
      undefined,
      BASE_MAX_EXECUTION_FEE,
      10_000_000n,
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await expectScriptFailure(
    'Minted amount wrongly sent to order destination',
    mutatedBatchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
      {
        type: 'exceed-max-execution-fee',
        maxExecutionFee: BASE_MAX_EXECUTION_FEE + 1_000_000n,
      },
    ),
  );
});

test<IndigoTestContext>('Stableswap - Pool exceeds max fee percentage', async (context: IndigoTestContext) => {
  context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

  await runCreateStableswapPool(
    context.assetConfigs[0].iassetTokenNameAscii,
    EXAMPLE_TOKEN_1,
    context,
  );

  const stableswapPool = await findStableswapPool(
    context.lucid,
    context.systemParams.validatorHashes.cdpHash,
    fromSystemParamsAsset(context.systemParams.stableswapParams.cdpToken),
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

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

  // Create order to mint iAsset, though with a 0% max fee percentage.
  await runAndAwaitTx(
    context.lucid,
    createStableswapOrder(
      context.assetConfigs[0].iassetTokenNameAscii,
      EXAMPLE_TOKEN_1,
      10_000_000n,
      true,
      stableswapPool.datum,
      context.systemParams,
      context.lucid,
      undefined,
      undefined,
      BASE_MAX_EXECUTION_FEE,
      0n,
      rationalFromInt(0n),
    ),
  );

  const stableswapOrder = await findSingleStableswapOrder(
    context.lucid,
    context.systemParams.validatorHashes.stableswapHash,
    fromText(context.assetConfigs[0].iassetTokenNameAscii),
    EXAMPLE_TOKEN_1,
  );

  await createUtxoAtTreasury(
    mkLovelacesOf(2_000_000n),
    context.systemParams,
    context,
  );

  const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
    context.lucid,
    context.systemParams,
  );

  await expectScriptFailure(
    'Pool minting fee exceeds order max fee ratio',
    batchProcessStableswapOrders(
      [stableswapOrder.utxo],
      stableswapPool.utxo,
      treasuryUtxo,
      context.systemParams,
      context.lucid,
    ),
  );
});
