import {
  addAssets,
  Emulator,
  EmulatorAccount,
  generateEmulatorAccount,
  Lucid,
  toText,
  fromHex,
  fromText,
  paymentCredentialOf,
  toHex,
  unixTimeToSlot,
} from '@lucid-evolution/lucid';
import { describe, beforeEach, test, expect, assert } from 'vitest';
import {
  adaAssetClass,
  AssetClass,
  assetClassToUnit,
  assetClassValueOf,
  isSameAssetClass,
  mkAssetsOf,
  mkLovelacesOf,
} from '@3rd-eye-labs/cardano-offchain-common';
import { init } from '../endpoints/initialize';
import { findGov } from '../gov/governance-queries';
import {
  addrDetails,
  adjustStakingPosition,
  createProposal,
  createScriptAddress,
  createShardsChunks,
  endProposal,
  executeProposal,
  fromSystemParamsAsset,
  matchSingle,
  mergeShards,
  ONE_DAY,
  openStakingPosition,
  startInterestOracle,
  treasuryPrepareWithdrawal,
  vote,
} from '../../src';

import {
  findAllPollShards,
  findPollManager,
  findRandomPollShard,
} from '../queries/poll-queries';
import { findStakingPosition } from '../queries/staking-queries';
import {
  option as O,
  readonlyArray as RA,
  task as T,
  array as A,
  function as F,
  number as N,
} from 'fp-ts';
import { findExecute } from '../queries/execute-queries';
import {
  getNewUtxosAtAddressAfterAction,
  getValueChangeAtAddressAfterAction,
} from '../utils';
import {
  iusdInitialAssetCfg,
  mkBaseCollateralAsset,
  mkCollateralAssetsChain,
} from '../mock/assets-mock';
import { addressFromBech32 } from '@3rd-eye-labs/cardano-offchain-common';
import {
  EXAMPLE_TOKEN_1,
  EXAMPLE_TOKEN_2,
  EXAMPLE_TOKEN_3,
  MAINNET_PROTOCOL_PARAMETERS,
} from '../indigo-test-helpers';
import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
import {
  findAllTreasuryUtxos,
  findAllTreasuryUtxosWithNonAdaAsset,
} from '../treasury/treasury-queries';
import {
  createIndyUtxoAtTreasury,
  createUtxoAtTreasury,
} from '../endpoints/treasury';
import { findRandomCdpCreator, findStableswapPool } from '../cdp/cdp-queries';
import {
  runCreateAllShards,
  runEndProposal,
  runMergeAllShards,
  runVote,
  waitForVotingEnd,
} from './actions';
import {
  LucidContext,
  runAndAwaitTx,
  runAndAwaitTxBuilder,
} from '../test-helpers';
import {
  findAllCollateralAssetsOfIAsset,
  findAllIAssets,
  findCollateralAssetNew,
  findIAsset,
} from '../queries/iasset-queries';
import { startPriceOracleTx } from '../../src/contracts/price-oracle/transactions';
import { rationalFromInt, rationalZero } from '../../src/types/rational';
import { expectScriptFailure } from '../utils/asserts';
import { MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET } from '../../src/contracts/iasset/helpers';

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

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

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

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

describe('Gov', () => {
  beforeEach<MyContext>(async (context: MyContext) => {
    context.users = {
      admin: generateEmulatorAccount({
        lovelace: BigInt(100_000_000_000_000),
      }),
      user: generateEmulatorAccount(
        addAssets(
          mkLovelacesOf(150_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_2, 1_000_000_000_000_000n),
          mkAssetsOf(EXAMPLE_TOKEN_3, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetA, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetB, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetC, 1_000_000_000_000_000n),
          mkAssetsOf(testAssetD, 1_000_000_000_000_000n),
        ),
      ),
      withdrawalAccount: generateEmulatorAccount({}),
    };

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

  test<MyContext>('Create text proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, _] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await benchmarkAndAwaitTx(
      'Gov - Create text proposal',
      tx,
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Create text proposal with shards', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    // Create all shards in one transaction.
    {
      const govUtxo = await findGov(
        context.lucid,
        sysParams.validatorHashes.govHash,
        fromSystemParamsAsset(sysParams.govParams.govNFT),
      );

      const pollUtxo = await findPollManager(
        context.lucid,
        sysParams.validatorHashes.pollManagerHash,
        fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
        pollId,
      );

      await benchmarkAndAwaitTx(
        `Gov - Create ${govUtxo.datum.protocolParams.totalShards} shards`,
        await createShardsChunks(
          govUtxo.datum.protocolParams.totalShards,
          pollUtxo.utxo,
          sysParams,
          context.emulator.slot,
          context.lucid,
        ),
        context.lucid,
        context.emulator,
      );
    }

    const pollUtxo = await findPollManager(
      context.lucid,
      sysParams.validatorHashes.pollManagerHash,
      fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
      pollId,
    );

    expect(
      pollUtxo.datum.createdShardsCount === pollUtxo.datum.totalShardsCount,
      'Expected total shards count being created',
    ).toBeTruthy();
  });

  test<MyContext>('Merge proposal shards', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    {
      const pollUtxo = await findPollManager(
        context.lucid,
        sysParams.validatorHashes.pollManagerHash,
        fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
        pollId,
      );

      await runAndAwaitTx(
        context.lucid,
        createShardsChunks(
          2n,
          pollUtxo.utxo,
          sysParams,
          context.emulator.slot,
          context.lucid,
        ),
      );

      const targetSlot = unixTimeToSlot(
        context.lucid.config().network!,
        Number(pollUtxo.datum.votingEndTime),
      );
      expect(targetSlot).toBeGreaterThan(context.emulator.slot);

      context.emulator.awaitSlot(targetSlot - context.emulator.slot + 1);
    }

    {
      const pollUtxo = await findPollManager(
        context.lucid,
        sysParams.validatorHashes.pollManagerHash,
        fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
        pollId,
      );

      const allPollShards = await findAllPollShards(
        context.lucid,
        sysParams.validatorHashes.pollShardHash,
        fromSystemParamsAsset(sysParams.pollShardParams.pollToken),
        pollUtxo.datum.pollId,
      );

      assert(allPollShards.length === 2);

      await benchmarkAndAwaitTx(
        `Gov - Merge ${allPollShards.length} shards`,
        await mergeShards(
          pollUtxo.utxo,
          allPollShards.map((u) => u.utxo),
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
        context.lucid,
        context.emulator,
      );
    }

    const pollUtxo = await findPollManager(
      context.lucid,
      sysParams.validatorHashes.pollManagerHash,
      fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
      pollId,
    );

    assert(pollUtxo.datum.talliedShardsCount === 2n);
  });

  test<MyContext>('Create propose iasset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    const [tx, __] = await createProposal(
      {
        ProposeIAsset: {
          asset: fromHex(fromText('iBTC')),
          debtMintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          liquidationProcessingFeeRatio: { numerator: 2n, denominator: 100n },
          stabilityPoolWithdrawalFeeRatio: {
            numerator: 5n,
            denominator: 1_000n,
          },
          redemptionReimbursementRatio: { numerator: 1n, denominator: 100n },
          redemptionProcessingFeeRatio: { numerator: 1n, denominator: 100n },
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await benchmarkAndAwaitTx(
      'Gov - Create iasset proposal',
      tx,
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Create modify iasset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    const [tx, __] = await createProposal(
      {
        ModifyIAsset: {
          asset: fromHex(fromText('iBTC')),
          newDebtMintingFeeRatio: rationalZero,
          newLiquidationProcessingFeeRatio: {
            numerator: 2n,
            denominator: 100n,
          },
          newStabilityPoolWithdrawalFeeRatio: {
            numerator: 5n,
            denominator: 1_000n,
          },
          newRedemptionReimbursementRatio: { numerator: 1n, denominator: 100n },
          newRedemptionProcessingFeeRatio: { numerator: 1n, denominator: 100n },
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await benchmarkAndAwaitTx(
      'Gov - Create modify iasset proposal',
      tx,
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Create add collateral asset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'IUSD_INDY_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    const [tx, __] = await createProposal(
      {
        AddCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: fromSystemParamsAsset(sysParams.govParams.indyAsset),
          assetExtraDecimals: 0n,
          assetPriceInfo: { OracleNft: priceOranceNft },
          interestOracleNft: interestOracleNft,
          redemptionRatio: rationalFromInt(2n),
          maintenanceRatio: { numerator: 150n, denominator: 100n },
          liquidationRatio: { numerator: 120n, denominator: 100n },
          minCollateralAmt: 1_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await benchmarkAndAwaitTx(
      'Gov - Create collateral asset proposal',
      tx,
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Create update collateral asset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'NEW_IUSD_ADA_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    const [tx, __] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: iusdAssetInfo.collateralAssets[0].collateralAsset,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: { OracleNft: priceOranceNft },
          newInterestOracleNft: interestOracleNft,
          newLiquidationRatio: { numerator: 120n, denominator: 100n },
          newMaintenanceRatio: { numerator: 150n, denominator: 100n },
          newRedemptionRatio: { numerator: 150n, denominator: 100n },
          newMinCollateralAmt: 10_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await benchmarkAndAwaitTx(
      'Gov - Create update collateral asset proposal',
      tx,
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Vote on proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(1_000_000n, sysParams, context.lucid),
    );

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

    const stakingPosOref = await findStakingPosition(
      context.lucid,
      sysParams.validatorHashes.stakingHash,
      fromSystemParamsAsset(sysParams.stakingParams.stakingToken),
      pkh.hash,
    );

    const pollShard = await findRandomPollShard(
      context.lucid,
      sysParams.validatorHashes.pollShardHash,
      fromSystemParamsAsset(sysParams.pollShardParams.pollToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Gov - Vote on proposal',
      await vote(
        'Yes',
        pollShard.utxo,
        stakingPosOref.utxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Vote on proposal, then deposit more INDY', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(1_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    const [pkh, _skh] = await addrDetails(context.lucid);
    const stakingPosUtxo = await findStakingPosition(
      context.lucid,
      sysParams.validatorHashes.stakingHash,
      fromSystemParamsAsset(sysParams.stakingParams.stakingToken),
      pkh.hash,
    );

    context.emulator.awaitSlot(60);

    await runAndAwaitTx(
      context.lucid,
      adjustStakingPosition(
        stakingPosUtxo.utxo,
        500_000_000n,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );
  });

  test<MyContext>('Vote on 2 proposals sequentially (lower pollID first)', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(1_000_000n, sysParams, context.lucid),
    );
    const [pkh, _] = await addrDetails(context.lucid);

    // Create proposals
    const createProposalsTask = F.pipe(
      [fromHex(fromText('proposal 1')), fromHex(fromText('proposal 2'))].map(
        (txtContent): T.Task<bigint> => {
          return async () => {
            const govUtxo = await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            );

            const [tx, pollId] = await createProposal(
              { TextProposal: txtContent },
              null,
              sysParams,
              context.lucid,
              context.emulator.slot,
              govUtxo.utxo,
              [],
            );

            await runAndAwaitTxBuilder(context.lucid, tx);

            await runCreateAllShards(pollId, sysParams, context);

            return pollId;
          };
        },
      ),
      T.sequenceSeqArray,
    );

    const pollIds = await createProposalsTask();

    // vote on each proposal
    const voteEachProposalTask = F.pipe(
      pollIds.map(
        (pollId): T.Task<void> =>
          async () => {
            await runVote(
              pollId,
              Number(pollId) % 2 == 0 ? 'Yes' : 'No',
              sysParams,
              context,
            );
          },
      ),
      T.sequenceSeqArray,
    );

    await voteEachProposalTask();

    const stakingPosUtxo = await findStakingPosition(
      context.lucid,
      sysParams.validatorHashes.stakingHash,
      fromSystemParamsAsset(sysParams.stakingParams.stakingToken),
      pkh.hash,
    );

    expect([...stakingPosUtxo.datum.lockedAmount.map((x) => x[0])]).toEqual([
      1n,
      2n,
    ]);
  });

  test<MyContext>('Vote on 2 proposals in reverse (higher pollID first), both yes and no votes', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(1_000_000n, sysParams, context.lucid),
    );
    const [pkh, _] = await addrDetails(context.lucid);

    // Create proposals
    const createProposalsTask = F.pipe(
      [fromHex(fromText('proposal 1')), fromHex(fromText('proposal 2'))].map(
        (txtContent): T.Task<bigint> => {
          return async () => {
            const govUtxo = await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            );

            const [tx, pollId] = await createProposal(
              { TextProposal: txtContent },
              null,
              sysParams,
              context.lucid,
              context.emulator.slot,
              govUtxo.utxo,
              [],
            );

            await runAndAwaitTxBuilder(context.lucid, tx);

            await runCreateAllShards(pollId, sysParams, context);

            return pollId;
          };
        },
      ),
      T.sequenceSeqArray,
    );

    const pollIdsDescending = F.pipe(
      await createProposalsTask(),
      RA.toArray, // Sort it from high to low
      A.map(Number),
      A.sort(N.Ord),
      A.map(BigInt),
      A.reverse,
    );

    // vote on each proposal
    const voteEachProposalTask = F.pipe(
      pollIdsDescending.map(
        (pollId): T.Task<void> =>
          async () => {
            await runVote(
              pollId,
              Number(pollId) % 2 == 0 ? 'Yes' : 'No',
              sysParams,
              context,
            );
          },
      ),
      T.sequenceSeqArray,
    );

    await voteEachProposalTask();

    const stakingPosUtxo = await findStakingPosition(
      context.lucid,
      sysParams.validatorHashes.stakingHash,
      fromSystemParamsAsset(sysParams.stakingParams.stakingToken),
      pkh.hash,
    );

    expect([...stakingPosUtxo.datum.lockedAmount.map((x) => x[0])]).toEqual([
      2n,
      1n,
    ]);
  });

  test<MyContext>('End passed proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    {
      const pollUtxo = await findPollManager(
        context.lucid,
        sysParams.validatorHashes.pollManagerHash,
        fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
        pollId,
      );

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

      await benchmarkAndAwaitTx(
        'Gov - End proposal',
        await endProposal(
          pollUtxo.utxo,
          govUtxo.utxo,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
        context.lucid,
        context.emulator,
      );
    }

    await expect(
      findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      ),
    ).resolves.toBeDefined();
  });

  test<MyContext>('End failed proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'No', sysParams, context);

    {
      const pollUtxo = await findPollManager(
        context.lucid,
        sysParams.validatorHashes.pollManagerHash,
        fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
        pollId,
      );

      const targetSlot = unixTimeToSlot(
        context.lucid.config().network!,
        Number(pollUtxo.datum.votingEndTime),
      );
      expect(targetSlot).toBeGreaterThan(context.emulator.slot);

      context.emulator.awaitSlot(targetSlot - context.emulator.slot + 1);
    }

    await runMergeAllShards(pollId, sysParams, context);

    const pollUtxo = await findPollManager(
      context.lucid,
      sysParams.validatorHashes.pollManagerHash,
      fromSystemParamsAsset(sysParams.pollManagerParams.pollToken),
      pollId,
    );

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

    const [_, newUtxos] = await getNewUtxosAtAddressAfterAction(
      context.lucid,
      createScriptAddress(
        context.lucid.config().network!,
        sysParams.validatorHashes.treasuryHash,
      ),
      async () =>
        benchmarkAndAwaitTx(
          'Gov - End failed proposal',
          await endProposal(
            pollUtxo.utxo,
            govUtxo.utxo,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    const treasuryOutput = matchSingle(
      newUtxos,
      () => new Error('Expected single treasury output'),
    );

    assert(
      assetClassValueOf(
        treasuryOutput.assets,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === govUtxo.datum.protocolParams.proposalDeposit,
      'Treasury should get proposal deposit back on failed proposal end',
    );

    await expect(
      findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      ),
    ).rejects.toThrow();
  });

  test<MyContext>('Execute text proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );
    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Execute text proposal',
      await executeProposal(
        executeUtxo.utxo,
        govUtxo.utxo,
        null,
        null,
        null,
        null,
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute text proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );
    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const [_, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        await benchmarkAndAwaitTx(
          'Execute text proposal with treasury withdrawal',
          await executeProposal(
            executeUtxo.utxo,
            govUtxo.utxo,
            treasuryWithdrawalUtxo,
            null,
            null,
            null,
            null,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    expect(
      assetClassValueOf(
        newVal,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === withdrawalIndyAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });

  test<MyContext>('Execute text proposal with treasury withdrawal with Treasury PrepareWithdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase);
    const withdrawalAmt = 2n;

    await createUtxoAtTreasury(
      mkAssetsOf(EXAMPLE_TOKEN_1, 5n),
      sysParams,
      context,
    );
    await createUtxoAtTreasury(
      mkAssetsOf(EXAMPLE_TOKEN_2, 5n),
      sysParams,
      context,
    );
    await createUtxoAtTreasury(
      mkAssetsOf(EXAMPLE_TOKEN_3, 5n),
      sysParams,
      context,
    );
    await createUtxoAtTreasury(mkAssetsOf(testAssetA, 5n), sysParams, context);
    await createUtxoAtTreasury(mkAssetsOf(testAssetB, 5n), sysParams, context);
    await createUtxoAtTreasury(mkAssetsOf(testAssetC, 5n), sysParams, context);
    await createUtxoAtTreasury(mkAssetsOf(testAssetD, 5n), sysParams, context);

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

    const [tx, pollId] = await createProposal(
      { TextProposal: fromHex(fromText('smth')) },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            ...EXAMPLE_TOKEN_1,
            amount: withdrawalAmt,
          },
          { ...EXAMPLE_TOKEN_2, amount: withdrawalAmt },
          { ...EXAMPLE_TOKEN_3, amount: withdrawalAmt },
          { ...testAssetA, amount: withdrawalAmt },
          { ...testAssetB, amount: withdrawalAmt },
          { ...testAssetC, amount: withdrawalAmt },
          { ...testAssetD, amount: withdrawalAmt },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const govUtxo = await findGov(
      context.lucid,
      sysParams.validatorHashes.govHash,
      fromSystemParamsAsset(sysParams.govParams.govNFT),
    );
    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    // One UTxO with the DAO token, five ADA only UTxOs and
    // one per each withdrawn asset.
    assert(
      (await findAllTreasuryUtxos(context.lucid, sysParams)).length === 13,
      'Expected 13 treasury withdrawal UTXOs prior to prepare withdrawal',
    );

    assert(
      (
        await findAllTreasuryUtxosWithNonAdaAsset(
          context.lucid,
          sysParams,
          false,
        )
      ).length === 7,
      'Expected 7 UTXOs to be used by the prepare withdrawal transaction',
    );

    await benchmarkAndAwaitTx(
      'Prepare withdrawal with 7 inputs / assets',
      await treasuryPrepareWithdrawal(
        await findAllTreasuryUtxosWithNonAdaAsset(
          context.lucid,
          sysParams,
          false,
        ),
        executeUtxo.utxo,
        context.lucid,
        sysParams,
      ),
      context.lucid,
      context.emulator,
    );

    const treasuryWithdrawalUtxos = await findAllTreasuryUtxos(
      context.lucid,
      sysParams,
    );

    // One with the value to withdraw and one UTxO with the change,
    // plus five ADA only UTxOs and one UTxO with the DAO token.
    assert(
      treasuryWithdrawalUtxos.length === 8,
      'Expected 8 treasury withdrawal UTXOs',
    );

    const treasuryWithdrawalUtxo = matchSingle(
      treasuryWithdrawalUtxos.filter(
        (utxo) =>
          utxo.assets[assetClassToUnit(EXAMPLE_TOKEN_1)] === withdrawalAmt,
      ),
      () => new Error('Expected a single treasury withdrawal UTXO'),
    );

    const [_, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        await runAndAwaitTx(
          context.lucid,
          executeProposal(
            executeUtxo.utxo,
            govUtxo.utxo,
            treasuryWithdrawalUtxo,
            null,
            null,
            null,
            null,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
        ),
    );

    expect(
      assetClassValueOf(newVal, EXAMPLE_TOKEN_1) === withdrawalAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });

  test<MyContext>('Execute create IAsset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      {
        ProposeIAsset: {
          asset: fromHex(fromText('iBTC')),
          debtMintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          liquidationProcessingFeeRatio: { numerator: 2n, denominator: 100n },
          stabilityPoolWithdrawalFeeRatio: {
            numerator: 5n,
            denominator: 1_000n,
          },
          redemptionReimbursementRatio: { numerator: 1n, denominator: 100n },
          redemptionProcessingFeeRatio: { numerator: 1n, denominator: 100n },
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Execute create IAsset proposal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        (
          await findAllIAssets(
            context.lucid,
            sysParams.validatorHashes.iassetHash,
            fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
          )
        ).map((iasset) => iasset.utxo),
        null,
        null,
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute create asset proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

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

    const [tx, pollId] = await createProposal(
      {
        ProposeIAsset: {
          asset: fromHex(fromText('iBTC')),
          debtMintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          liquidationProcessingFeeRatio: { numerator: 2n, denominator: 100n },
          stabilityPoolWithdrawalFeeRatio: {
            numerator: 5n,
            denominator: 1_000n,
          },
          redemptionReimbursementRatio: { numerator: 1n, denominator: 100n },
          redemptionProcessingFeeRatio: { numerator: 1n, denominator: 100n },
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const [__, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        benchmarkAndAwaitTx(
          'Execute create asset proposal with treasury withdrawal',
          await executeProposal(
            executeUtxo.utxo,
            (
              await findGov(
                context.lucid,
                sysParams.validatorHashes.govHash,
                fromSystemParamsAsset(sysParams.govParams.govNFT),
              )
            ).utxo,
            treasuryWithdrawalUtxo,
            (
              await findAllIAssets(
                context.lucid,
                sysParams.validatorHashes.iassetHash,
                fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
              )
            ).map((iasset) => iasset.utxo),
            null,
            null,
            null,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    expect(
      assetClassValueOf(
        newVal,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === withdrawalIndyAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });

  test<MyContext>('Execute modify asset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const iassetToModify = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      'iUSD',
    );

    const [tx, pollId] = await createProposal(
      {
        ModifyIAsset: {
          asset: fromHex(fromText('iUSD')),
          newDebtMintingFeeRatio: iassetToModify.datum.debtMintingFeeRatio,
          newLiquidationProcessingFeeRatio:
            iassetToModify.datum.liquidationProcessingFeeRatio,
          newStabilityPoolWithdrawalFeeRatio:
            iassetToModify.datum.stabilityPoolWithdrawalFeeRatio,
          newRedemptionReimbursementRatio:
            iassetToModify.datum.redemptionReimbursementRatio,
          newRedemptionProcessingFeeRatio:
            iassetToModify.datum.redemptionProcessingFeeRatio,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Gov - Execute modify asset proposal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetToModify.utxo,
        null,
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute modify asset proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

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

    const iassetToModify = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      'iUSD',
    );

    const [tx, pollId] = await createProposal(
      {
        ModifyIAsset: {
          asset: fromHex(fromText('iUSD')),
          newDebtMintingFeeRatio: iassetToModify.datum.debtMintingFeeRatio,
          newLiquidationProcessingFeeRatio:
            iassetToModify.datum.liquidationProcessingFeeRatio,
          newStabilityPoolWithdrawalFeeRatio:
            iassetToModify.datum.stabilityPoolWithdrawalFeeRatio,
          newRedemptionReimbursementRatio:
            iassetToModify.datum.redemptionReimbursementRatio,
          newRedemptionProcessingFeeRatio:
            iassetToModify.datum.redemptionProcessingFeeRatio,
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const [__, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        benchmarkAndAwaitTx(
          'Gov - Execute modify asset proposal with treasury withdrawal',
          await executeProposal(
            executeUtxo.utxo,
            (
              await findGov(
                context.lucid,
                sysParams.validatorHashes.govHash,
                fromSystemParamsAsset(sysParams.govParams.govNFT),
              )
            ).utxo,
            treasuryWithdrawalUtxo,
            null,
            iassetToModify.utxo,
            null,
            null,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    expect(
      assetClassValueOf(
        newVal,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === withdrawalIndyAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });

  describe('Add collateral asset proposal', () => {
    test<MyContext>('Execute add collateral asset proposal (no collateral assets on the iassets)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const collateralAsset1: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('ABC')),
      };

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

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

      const [startInterestTx, interestOracleNft] = await startInterestOracle(
        0n,
        0n,
        0n,
        {
          biasTime: 120_000n,
          owner: pkh.hash,
        },
        context.lucid,
      );
      await runAndAwaitTxBuilder(context.lucid, startInterestTx);

      const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
        context.lucid,
        'IUSD_INDY_ORACLE',
        rationalFromInt(1n),
        { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
        context.emulator.slot,
      );
      await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

      const allIassetOrefs = (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo);

      const [tx, pollId] = await createProposal(
        {
          AddCollateralAsset: {
            correspondingIAsset: fromHex(
              fromText(iusdAssetInfo.iassetTokenNameAscii),
            ),
            collateralAsset: collateralAsset1,
            assetExtraDecimals: 0n,
            assetPriceInfo: { OracleNft: priceOranceNft },
            interestOracleNft: interestOracleNft,
            redemptionRatio: rationalFromInt(2n),
            maintenanceRatio: { numerator: 150n, denominator: 100n },
            liquidationRatio: { numerator: 120n, denominator: 100n },
            minCollateralAmt: 1_000_000n,
          },
        },
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
        govUtxo.utxo,
        allIassetOrefs,
      );

      await runAndAwaitTxBuilder(context.lucid, tx);

      await runCreateAllShards(pollId, sysParams, context);

      await runAndAwaitTx(
        context.lucid,
        openStakingPosition(100_000_000_000n, sysParams, context.lucid),
      );

      await runVote(pollId, 'Yes', sysParams, context);

      await waitForVotingEnd(pollId, sysParams, context);

      await runMergeAllShards(pollId, sysParams, context);

      await runEndProposal(pollId, sysParams, context);

      const executeUtxo = await findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      );

      const iassetOutput = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Gov - Execute add collateral asset proposal (no collateral assets on the iasset)',
        await executeProposal(
          executeUtxo.utxo,
          (
            await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            )
          ).utxo,
          null,
          null,
          iassetOutput.utxo,
          (
            await findAllCollateralAssetsOfIAsset(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(
                sysParams.executeParams.collateralAssetToken,
              ),
              fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
            )
          ).map((o) => o.utxo),
          null,
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
        context.lucid,
        context.emulator,
      );

      const res = await findCollateralAssetNew(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAsset1,
      );

      expect(
        res.datum.firstCollateralAsset,
        'Expected this collateral asset to become first',
      ).toBeTruthy();

      expect(
        F.pipe(
          res.datum.nextCollateralAsset,
          O.fromNullable,
          O.match(
            () => true,
            // There shouldn't be any next collateral asset.
            (_) => false,
          ),
        ),
        'Expected no next collateral asset',
      ).toBeTruthy();
    });

    test<MyContext>('Execute add collateral asset proposal (start of the collateral asset chain)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const collateralAsset1: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('B')),
      };
      const collateralAsset2: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('A')),
      };

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

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

      const [startInterestTx, interestOracleNft] = await startInterestOracle(
        0n,
        0n,
        0n,
        {
          biasTime: 120_000n,
          owner: pkh.hash,
        },
        context.lucid,
      );
      await runAndAwaitTxBuilder(context.lucid, startInterestTx);

      const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
        context.lucid,
        'IUSD_INDY_ORACLE',
        rationalFromInt(1n),
        { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
        context.emulator.slot,
      );
      await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

      const allIassetOrefs = (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo);

      const [tx, pollId] = await createProposal(
        {
          AddCollateralAsset: {
            correspondingIAsset: fromHex(
              fromText(iusdAssetInfo.iassetTokenNameAscii),
            ),
            collateralAsset: collateralAsset2,
            assetExtraDecimals: 0n,
            assetPriceInfo: { OracleNft: priceOranceNft },
            interestOracleNft: interestOracleNft,
            redemptionRatio: rationalFromInt(2n),
            maintenanceRatio: { numerator: 150n, denominator: 100n },
            liquidationRatio: { numerator: 120n, denominator: 100n },
            minCollateralAmt: 1_000_000n,
          },
        },
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
        govUtxo.utxo,
        allIassetOrefs,
      );

      await runAndAwaitTxBuilder(context.lucid, tx);

      await runCreateAllShards(pollId, sysParams, context);

      await runAndAwaitTx(
        context.lucid,
        openStakingPosition(100_000_000_000n, sysParams, context.lucid),
      );

      await runVote(pollId, 'Yes', sysParams, context);

      await waitForVotingEnd(pollId, sysParams, context);

      await runMergeAllShards(pollId, sysParams, context);

      await runEndProposal(pollId, sysParams, context);

      const executeUtxo = await findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      );

      const iassetOutput = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Gov - Execute add collateral asset proposal (start of the collateral asset chain)',
        await executeProposal(
          executeUtxo.utxo,
          (
            await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            )
          ).utxo,
          null,
          null,
          iassetOutput.utxo,
          (
            await findAllCollateralAssetsOfIAsset(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(
                sysParams.executeParams.collateralAssetToken,
              ),
              fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
            )
          ).map((o) => o.utxo),
          null,
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
        context.lucid,
        context.emulator,
      );

      const res = await findCollateralAssetNew(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAsset2,
      );

      expect(
        res.datum.firstCollateralAsset,
        'Expected this collateral asset to become first',
      ).toBeTruthy();

      expect(
        F.pipe(
          res.datum.nextCollateralAsset,
          O.fromNullable,
          O.match(
            // There should be some next collateral asset
            () => false,
            (next) => isSameAssetClass(next, collateralAsset1),
          ),
        ),
        'Expected correct next collateral asset',
      ).toBeTruthy();
    });

    test<MyContext>('Execute add collateral asset proposal (middle of the collateral asset chain)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const collateralAsset1: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('A')),
      };
      const collateralAsset2: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('B')),
      };
      const collateralAsset3: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('C')),
      };

      const [sysParams, [iusdAssetInfo]] = await init(
        context.lucid,
        [
          {
            ...iusdInitialAssetCfg(),
            collateralAssets: [
              {
                ...iusdInitialAssetCfg().collateralAssets[0],
                collateralAsset: collateralAsset1,
                nextCollateralAsset: collateralAsset3,
              },
              {
                ...iusdInitialAssetCfg().collateralAssets[0],
                collateralAsset: collateralAsset3,
              },
            ],
          },
        ],
        context.emulator.slot,
      );

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

      const [startInterestTx, interestOracleNft] = await startInterestOracle(
        0n,
        0n,
        0n,
        {
          biasTime: 120_000n,
          owner: pkh.hash,
        },
        context.lucid,
      );
      await runAndAwaitTxBuilder(context.lucid, startInterestTx);

      const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
        context.lucid,
        'IUSD_INDY_ORACLE',
        rationalFromInt(1n),
        { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
        context.emulator.slot,
      );
      await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

      const allIassetOrefs = (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo);

      const [tx, pollId] = await createProposal(
        {
          AddCollateralAsset: {
            correspondingIAsset: fromHex(
              fromText(iusdAssetInfo.iassetTokenNameAscii),
            ),
            collateralAsset: collateralAsset2,
            assetExtraDecimals: 0n,
            assetPriceInfo: { OracleNft: priceOranceNft },
            interestOracleNft: interestOracleNft,
            redemptionRatio: rationalFromInt(2n),
            maintenanceRatio: { numerator: 150n, denominator: 100n },
            liquidationRatio: { numerator: 120n, denominator: 100n },
            minCollateralAmt: 1_000_000n,
          },
        },
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
        govUtxo.utxo,
        allIassetOrefs,
      );

      await runAndAwaitTxBuilder(context.lucid, tx);

      await runCreateAllShards(pollId, sysParams, context);

      await runAndAwaitTx(
        context.lucid,
        openStakingPosition(100_000_000_000n, sysParams, context.lucid),
      );

      await runVote(pollId, 'Yes', sysParams, context);

      await waitForVotingEnd(pollId, sysParams, context);

      await runMergeAllShards(pollId, sysParams, context);

      await runEndProposal(pollId, sysParams, context);

      const executeUtxo = await findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      );

      const iassetOutput = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Gov - Execute add collateral asset proposal (middle of the collateral asset chain)',
        await executeProposal(
          executeUtxo.utxo,
          (
            await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            )
          ).utxo,
          null,
          null,
          iassetOutput.utxo,
          (
            await findAllCollateralAssetsOfIAsset(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(
                sysParams.executeParams.collateralAssetToken,
              ),
              fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
            )
          ).map((o) => o.utxo),
          null,
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
        context.lucid,
        context.emulator,
      );

      const newCollateralAsset = await findCollateralAssetNew(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAsset2,
      );
      const collateralAssetFirst = await findCollateralAssetNew(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAsset1,
      );

      expect(
        collateralAssetFirst.datum.firstCollateralAsset,
        'Expected this collateral asset to BE first',
      ).toBeTruthy();

      expect(
        F.pipe(
          collateralAssetFirst.datum.nextCollateralAsset,
          O.fromNullable,
          O.match(
            // There should be some next collateral asset
            () => false,
            (next) => isSameAssetClass(next, collateralAsset2),
          ),
        ),
        'Expected correct next collateral asset on the first collateral asset',
      ).toBeTruthy();

      expect(
        newCollateralAsset.datum.firstCollateralAsset,
        'Expected this collateral asset to NOT BE first',
      ).toBeFalsy();

      expect(
        F.pipe(
          newCollateralAsset.datum.nextCollateralAsset,
          O.fromNullable,
          O.match(
            // There should be some next collateral asset
            () => false,
            (next) => isSameAssetClass(next, collateralAsset3),
          ),
        ),
        'Expected correct next collateral asset on the new collateral asset',
      ).toBeTruthy();
    });

    test<MyContext>('Execute add collateral asset proposal (end of the collateral asset chain)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

      const collateralAsset1: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('A')),
      };
      const collateralAsset2: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText('B')),
      };

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

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

      const [startInterestTx, interestOracleNft] = await startInterestOracle(
        0n,
        0n,
        0n,
        {
          biasTime: 120_000n,
          owner: pkh.hash,
        },
        context.lucid,
      );
      await runAndAwaitTxBuilder(context.lucid, startInterestTx);

      const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
        context.lucid,
        'IUSD_INDY_ORACLE',
        rationalFromInt(1n),
        { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
        context.emulator.slot,
      );
      await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

      const allIassetOrefs = (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo);

      const [tx, pollId] = await createProposal(
        {
          AddCollateralAsset: {
            correspondingIAsset: fromHex(
              fromText(iusdAssetInfo.iassetTokenNameAscii),
            ),
            collateralAsset: collateralAsset2,
            assetExtraDecimals: 0n,
            assetPriceInfo: { OracleNft: priceOranceNft },
            interestOracleNft: interestOracleNft,
            redemptionRatio: rationalFromInt(2n),
            maintenanceRatio: { numerator: 150n, denominator: 100n },
            liquidationRatio: { numerator: 120n, denominator: 100n },
            minCollateralAmt: 1_000_000n,
          },
        },
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
        govUtxo.utxo,
        allIassetOrefs,
      );

      await runAndAwaitTxBuilder(context.lucid, tx);

      await runCreateAllShards(pollId, sysParams, context);

      await runAndAwaitTx(
        context.lucid,
        openStakingPosition(100_000_000_000n, sysParams, context.lucid),
      );

      await runVote(pollId, 'Yes', sysParams, context);

      await waitForVotingEnd(pollId, sysParams, context);

      await runMergeAllShards(pollId, sysParams, context);

      await runEndProposal(pollId, sysParams, context);

      const executeUtxo = await findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      );

      const iassetOutput = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      await benchmarkAndAwaitTx(
        'Gov - Execute add collateral asset proposal (end of the collateral asset chain)',
        await executeProposal(
          executeUtxo.utxo,
          (
            await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            )
          ).utxo,
          null,
          null,
          iassetOutput.utxo,
          (
            await findAllCollateralAssetsOfIAsset(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(
                sysParams.executeParams.collateralAssetToken,
              ),
              fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
            )
          ).map((o) => o.utxo),
          null,
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
        context.lucid,
        context.emulator,
      );

      const newCollateralAsset = await findCollateralAssetNew(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAsset2,
      );
      const collateralAssetFirst = await findCollateralAssetNew(
        context,
        sysParams,
        iusdAssetInfo.iassetTokenNameAscii,
        collateralAsset1,
      );

      expect(
        collateralAssetFirst.datum.firstCollateralAsset,
        'Expected this collateral asset to BE first',
      ).toBeTruthy();

      expect(
        F.pipe(
          collateralAssetFirst.datum.nextCollateralAsset,
          O.fromNullable,
          O.match(
            // There should be some next collateral asset
            () => false,
            (next) => isSameAssetClass(next, collateralAsset2),
          ),
        ),
        'Expected correct next collateral asset on the first collateral asset',
      ).toBeTruthy();

      expect(
        newCollateralAsset.datum.firstCollateralAsset,
        'Expected this collateral asset to NOT BE first',
      ).toBeFalsy();

      expect(
        F.pipe(
          newCollateralAsset.datum.nextCollateralAsset,
          O.fromNullable,
          O.match(
            () => true,
            // There should be no next collateral asset
            (_) => false,
          ),
        ),
        'Expected no next collateral asset on the new collateral asset',
      ).toBeTruthy();
    });

    test<MyContext>('Execute add iasset and add collateral asset proposals in parallel (execute iasset first, then collateral asset)', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

      const createIAssetProposal = async (
        iassetName: Uint8Array<ArrayBufferLike>,
      ): Promise<bigint> => {
        const govUtxo = await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        );

        const [tx, pollId] = await createProposal(
          {
            ProposeIAsset: {
              asset: iassetName,
              debtMintingFeeRatio: { numerator: 5n, denominator: 1_000n },
              liquidationProcessingFeeRatio: {
                numerator: 2n,
                denominator: 100n,
              },
              stabilityPoolWithdrawalFeeRatio: {
                numerator: 5n,
                denominator: 1_000n,
              },
              redemptionReimbursementRatio: {
                numerator: 1n,
                denominator: 100n,
              },
              redemptionProcessingFeeRatio: {
                numerator: 1n,
                denominator: 100n,
              },
            },
          },
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
          govUtxo.utxo,
          (
            await findAllIAssets(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
            )
          ).map((iasset) => iasset.utxo),
        );

        await runAndAwaitTxBuilder(context.lucid, tx);

        return pollId;
      };

      const createCollateralAssetProposal = async (
        iassetName: Uint8Array<ArrayBufferLike>,
        collateralAset: AssetClass,
      ): Promise<bigint> => {
        const [startInterestTx, interestOracleNft] = await startInterestOracle(
          0n,
          0n,
          0n,
          {
            biasTime: 120_000n,
            owner: pkh.hash,
          },
          context.lucid,
        );
        await runAndAwaitTxBuilder(context.lucid, startInterestTx);

        const [priceOracleTx, priceOracleNft] = await startPriceOracleTx(
          context.lucid,
          'SOME_ORACLE',
          rationalFromInt(1n),
          { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
          context.emulator.slot,
        );
        await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

        const [tx, pollId] = await createProposal(
          {
            AddCollateralAsset: {
              correspondingIAsset: iassetName,
              collateralAsset: collateralAset,
              assetExtraDecimals: 0n,
              assetPriceInfo: { OracleNft: priceOracleNft },
              interestOracleNft: interestOracleNft,
              redemptionRatio: rationalFromInt(2n),
              maintenanceRatio: { numerator: 150n, denominator: 100n },
              liquidationRatio: { numerator: 120n, denominator: 100n },
              minCollateralAmt: 1_000_000n,
            },
          },
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
          govUtxo.utxo,
          [],
        );

        await runAndAwaitTxBuilder(context.lucid, tx);

        return pollId;
      };

      const newIAsset = fromHex(fromText('iBTC'));
      const newCollateralAsset = fromSystemParamsAsset(
        sysParams.govParams.indyAsset,
      );

      const createIAssetPollId = await createIAssetProposal(newIAsset);
      await runCreateAllShards(createIAssetPollId, sysParams, context);

      const addCollateralAssetPollId = await createCollateralAssetProposal(
        newIAsset,
        newCollateralAsset,
      );
      await runCreateAllShards(addCollateralAssetPollId, sysParams, context);

      await runAndAwaitTx(
        context.lucid,
        openStakingPosition(100_000_000_000n, sysParams, context.lucid),
      );

      await runVote(createIAssetPollId, 'Yes', sysParams, context);
      await runVote(addCollateralAssetPollId, 'Yes', sysParams, context);

      await waitForVotingEnd(createIAssetPollId, sysParams, context);
      await waitForVotingEnd(addCollateralAssetPollId, sysParams, context);

      await runMergeAllShards(createIAssetPollId, sysParams, context);
      await runMergeAllShards(addCollateralAssetPollId, sysParams, context);

      await runEndProposal(createIAssetPollId, sysParams, context);
      await runEndProposal(addCollateralAssetPollId, sysParams, context);

      // Execute the iasset proposal
      await runAndAwaitTx(
        context.lucid,
        executeProposal(
          (
            await findExecute(
              context.lucid,
              sysParams.validatorHashes.executeHash,
              fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
              createIAssetPollId,
            )
          ).utxo,
          (
            await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            )
          ).utxo,
          null,
          (
            await findAllIAssets(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
            )
          ).map((iasset) => iasset.utxo),
          null,
          null,
          null,
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
      );

      // Execute the add collateral asset proposal
      await runAndAwaitTx(
        context.lucid,
        executeProposal(
          (
            await findExecute(
              context.lucid,
              sysParams.validatorHashes.executeHash,
              fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
              addCollateralAssetPollId,
            )
          ).utxo,
          (
            await findGov(
              context.lucid,
              sysParams.validatorHashes.govHash,
              fromSystemParamsAsset(sysParams.govParams.govNFT),
            )
          ).utxo,
          null,
          null,
          (
            await findIAsset(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
              toText(toHex(newIAsset)),
            )
          ).utxo,
          (
            await findAllCollateralAssetsOfIAsset(
              context.lucid,
              sysParams.validatorHashes.iassetHash,
              fromSystemParamsAsset(
                sysParams.executeParams.collateralAssetToken,
              ),
              newIAsset,
            )
          ).map((o) => o.utxo),
          null,
          null,
          sysParams,
          context.lucid,
          context.emulator.slot,
        ),
      );
    });

    test<MyContext>('Execute add collateral asset proposal with treasury withdrawal', async (context: MyContext) => {
      context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

      const withdrawalIndyAmt = 1_000n;
      const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
        withdrawalIndyAmt,
        sysParams,
        context,
      );

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

      const [startInterestTx, interestOracleNft] = await startInterestOracle(
        0n,
        0n,
        0n,
        {
          biasTime: 120_000n,
          owner: pkh.hash,
        },
        context.lucid,
      );
      await runAndAwaitTxBuilder(context.lucid, startInterestTx);

      const [priceOracleTx, priceOracleNft] = await startPriceOracleTx(
        context.lucid,
        'IUSD_INDY_ORACLE',
        rationalFromInt(1n),
        { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
        context.emulator.slot,
      );
      await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

      const allIassetOrefs = (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo);

      const [tx, pollId] = await createProposal(
        {
          AddCollateralAsset: {
            correspondingIAsset: fromHex(
              fromText(iusdAssetInfo.iassetTokenNameAscii),
            ),
            collateralAsset: fromSystemParamsAsset(
              sysParams.govParams.indyAsset,
            ),
            assetExtraDecimals: 0n,
            assetPriceInfo: { OracleNft: priceOracleNft },
            interestOracleNft: interestOracleNft,
            redemptionRatio: rationalFromInt(2n),
            maintenanceRatio: { numerator: 150n, denominator: 100n },
            liquidationRatio: { numerator: 120n, denominator: 100n },
            minCollateralAmt: 1_000_000n,
          },
        },
        {
          destination: addressFromBech32(
            context.users.withdrawalAccount.address,
          ),
          value: [
            {
              currencySymbol: fromHex(
                sysParams.govParams.indyAsset[0].unCurrencySymbol,
              ),
              tokenName: fromHex(
                fromText(sysParams.govParams.indyAsset[1].unTokenName),
              ),
              amount: withdrawalIndyAmt,
            },
          ],
        },
        sysParams,
        context.lucid,
        context.emulator.slot,
        govUtxo.utxo,
        allIassetOrefs,
      );

      await runAndAwaitTxBuilder(context.lucid, tx);

      await runCreateAllShards(pollId, sysParams, context);

      await runAndAwaitTx(
        context.lucid,
        openStakingPosition(100_000_000_000n, sysParams, context.lucid),
      );

      await runVote(pollId, 'Yes', sysParams, context);

      await waitForVotingEnd(pollId, sysParams, context);

      await runMergeAllShards(pollId, sysParams, context);

      await runEndProposal(pollId, sysParams, context);

      const executeUtxo = await findExecute(
        context.lucid,
        sysParams.validatorHashes.executeHash,
        fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
        pollId,
      );

      const iassetOutput = await findIAsset(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
        iusdAssetInfo.iassetTokenNameAscii,
      );

      const [__, newVal] = await getValueChangeAtAddressAfterAction(
        context.lucid,
        context.users.withdrawalAccount.address,
        async () =>
          await benchmarkAndAwaitTx(
            'Gov - Execute add collateral asset proposal with treasury withdrawal',
            await executeProposal(
              executeUtxo.utxo,
              (
                await findGov(
                  context.lucid,
                  sysParams.validatorHashes.govHash,
                  fromSystemParamsAsset(sysParams.govParams.govNFT),
                )
              ).utxo,
              treasuryWithdrawalUtxo,
              null,
              iassetOutput.utxo,
              (
                await findAllCollateralAssetsOfIAsset(
                  context.lucid,
                  sysParams.validatorHashes.iassetHash,
                  fromSystemParamsAsset(
                    sysParams.executeParams.collateralAssetToken,
                  ),
                  fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
                )
              ).map((o) => o.utxo),
              null,
              null,
              sysParams,
              context.lucid,
              context.emulator.slot,
            ),
            context.lucid,
            context.emulator,
          ),
      );

      expect(
        assetClassValueOf(
          newVal,
          fromSystemParamsAsset(sysParams.govParams.indyAsset),
        ) === withdrawalIndyAmt,
        'Unexpected withdrawn indy amt',
      ).toBeTruthy();
    });
  });

  test<MyContext>('Execute add (nth - 1) collateral asset proposal (no ADA whitelisted) succeeds', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const initialCollateralAssets = [];

    for (let i = 0; i < MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 2; i++) {
      const collateralAssetI: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText(i.toString())),
      };
      initialCollateralAssets.push(collateralAssetI);
    }

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'IUSD_INDY_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

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

    const collateralAssetN: AssetClass = {
      currencySymbol: fromHex(
        '00000000000000000000000000000000000000000000000000000000',
      ),
      tokenName: fromHex(
        fromText(MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET.toString()),
      ),
    };

    const [tx, pollId] = await createProposal(
      {
        AddCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: collateralAssetN,
          assetExtraDecimals: 0n,
          assetPriceInfo: { OracleNft: priceOranceNft },
          interestOracleNft: interestOracleNft,
          redemptionRatio: rationalFromInt(2n),
          maintenanceRatio: { numerator: 150n, denominator: 100n },
          liquidationRatio: { numerator: 120n, denominator: 100n },
          minCollateralAmt: 1_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetOutput = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await runAndAwaitTx(
      context.lucid,
      executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetOutput.utxo,
        (
          await findAllCollateralAssetsOfIAsset(
            context.lucid,
            sysParams.validatorHashes.iassetHash,
            fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
            fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
          )
        ).map((o) => o.utxo),
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );

    const whitelistedCollateralAssets = await findAllCollateralAssetsOfIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
      fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    );

    expect(
      whitelistedCollateralAssets.length ==
        MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 1,
      'Expected different number of whitelisted collateral assets',
    ).toBeTruthy();
  });

  test<MyContext>('Execute add nth collateral asset proposal (no ADA whitelisted) fails', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const initialCollateralAssets = [];

    for (let i = 0; i < MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 1; i++) {
      const collateralAssetI: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText(i.toString())),
      };
      initialCollateralAssets.push(collateralAssetI);
    }

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'IUSD_INDY_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    const whitelistedCollateralAssets = await findAllCollateralAssetsOfIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
      fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    );

    expect(
      whitelistedCollateralAssets.length ==
        MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 1,
      'Expected different number of whitelisted collateral assets',
    ).toBeTruthy();

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

    const collateralAssetN: AssetClass = {
      currencySymbol: fromHex(
        '00000000000000000000000000000000000000000000000000000000',
      ),
      tokenName: fromHex(
        fromText(MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET.toString()),
      ),
    };

    const [tx, pollId] = await createProposal(
      {
        AddCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: collateralAssetN,
          assetExtraDecimals: 0n,
          assetPriceInfo: { OracleNft: priceOranceNft },
          interestOracleNft: interestOracleNft,
          redemptionRatio: rationalFromInt(2n),
          maintenanceRatio: { numerator: 150n, denominator: 100n },
          liquidationRatio: { numerator: 120n, denominator: 100n },
          minCollateralAmt: 1_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetOutput = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await expectScriptFailure(
      'Max collateral assets reached',
      executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetOutput.utxo,
        (
          await findAllCollateralAssetsOfIAsset(
            context.lucid,
            sysParams.validatorHashes.iassetHash,
            fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
            fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
          )
        ).map((o) => o.utxo),
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );
  });

  test<MyContext>('Execute add nth collateral asset proposal (ADA whitelisted) succeeds', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const initialCollateralAssets = [adaAssetClass];

    for (let i = 0; i < MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 2; i++) {
      const collateralAssetI: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText(i.toString())),
      };
      initialCollateralAssets.push(collateralAssetI);
    }

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'IUSD_INDY_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

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

    const collateralAssetN: AssetClass = {
      currencySymbol: fromHex(
        '00000000000000000000000000000000000000000000000000000000',
      ),
      tokenName: fromHex(
        fromText(MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET.toString()),
      ),
    };

    const [tx, pollId] = await createProposal(
      {
        AddCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: collateralAssetN,
          assetExtraDecimals: 0n,
          assetPriceInfo: { OracleNft: priceOranceNft },
          interestOracleNft: interestOracleNft,
          redemptionRatio: rationalFromInt(2n),
          maintenanceRatio: { numerator: 150n, denominator: 100n },
          liquidationRatio: { numerator: 120n, denominator: 100n },
          minCollateralAmt: 1_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetOutput = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await runAndAwaitTx(
      context.lucid,
      executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetOutput.utxo,
        (
          await findAllCollateralAssetsOfIAsset(
            context.lucid,
            sysParams.validatorHashes.iassetHash,
            fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
            fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
          )
        ).map((o) => o.utxo),
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );

    const whitelistedCollateralAssets = await findAllCollateralAssetsOfIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
      fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    );

    expect(
      whitelistedCollateralAssets.length ==
        MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET,
      'Expected different number of whitelisted collateral assets',
    ).toBeTruthy();
  });

  test<MyContext>('Execute add ADA collateral asset proposal with (nth - 1) whitelisted assets succeeds', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const initialCollateralAssets = [];

    for (let i = 0; i < MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 1; i++) {
      const collateralAssetI: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText(i.toString())),
      };
      initialCollateralAssets.push(collateralAssetI);
    }

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'IUSD_INDY_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

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

    const [tx, pollId] = await createProposal(
      {
        AddCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: adaAssetClass,
          assetExtraDecimals: 0n,
          assetPriceInfo: { OracleNft: priceOranceNft },
          interestOracleNft: interestOracleNft,
          redemptionRatio: rationalFromInt(2n),
          maintenanceRatio: { numerator: 150n, denominator: 100n },
          liquidationRatio: { numerator: 120n, denominator: 100n },
          minCollateralAmt: 1_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetOutput = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await runAndAwaitTx(
      context.lucid,
      executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetOutput.utxo,
        (
          await findAllCollateralAssetsOfIAsset(
            context.lucid,
            sysParams.validatorHashes.iassetHash,
            fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
            fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
          )
        ).map((o) => o.utxo),
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );

    const whitelistedCollateralAssets = await findAllCollateralAssetsOfIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
      fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    );

    expect(
      whitelistedCollateralAssets.length ==
        MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET,
      'Expected different number of whitelisted collateral assets',
    ).toBeTruthy();
  });

  test<MyContext>('Execute add (nth + 1) collateral asset proposal (ADA whitelisted) fails', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

    const initialCollateralAssets = [adaAssetClass];

    for (let i = 0; i < MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET - 1; i++) {
      const collateralAssetI: AssetClass = {
        currencySymbol: fromHex(
          '00000000000000000000000000000000000000000000000000000000',
        ),
        tokenName: fromHex(fromText(i.toString())),
      };
      initialCollateralAssets.push(collateralAssetI);
    }

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'IUSD_INDY_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    const whitelistedCollateralAssets = await findAllCollateralAssetsOfIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
      fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
    );

    expect(
      whitelistedCollateralAssets.length ==
        MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET,
      'Expected different number of whitelisted collateral assets',
    ).toBeTruthy();

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

    const collateralAssetN: AssetClass = {
      currencySymbol: fromHex(
        '00000000000000000000000000000000000000000000000000000000',
      ),
      tokenName: fromHex(
        fromText(MAX_COLLATERAL_ASSETS_COUNT_PER_IASSET.toString()),
      ),
    };

    const [tx, pollId] = await createProposal(
      {
        AddCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: collateralAssetN,
          assetExtraDecimals: 0n,
          assetPriceInfo: { OracleNft: priceOranceNft },
          interestOracleNft: interestOracleNft,
          redemptionRatio: rationalFromInt(2n),
          maintenanceRatio: { numerator: 150n, denominator: 100n },
          liquidationRatio: { numerator: 120n, denominator: 100n },
          minCollateralAmt: 1_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetOutput = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.executeParams.iAssetToken),
      iusdAssetInfo.iassetTokenNameAscii,
    );

    await expectScriptFailure(
      'Max collateral assets reached',
      executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetOutput.utxo,
        (
          await findAllCollateralAssetsOfIAsset(
            context.lucid,
            sysParams.validatorHashes.iassetHash,
            fromSystemParamsAsset(sysParams.executeParams.collateralAssetToken),
            fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)),
          )
        ).map((o) => o.utxo),
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );
  });

  test<MyContext>('Execute update collateral asset proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'NEW_IUSD_ADA_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: iusdAssetInfo.collateralAssets[0].collateralAsset,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: { OracleNft: priceOranceNft },
          newInterestOracleNft: interestOracleNft,
          newLiquidationRatio: { numerator: 120n, denominator: 100n },
          newMaintenanceRatio: { numerator: 150n, denominator: 100n },
          newRedemptionRatio: { numerator: 150n, denominator: 100n },
          newMinCollateralAmt: 10_000_000n,
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Gov - Execute update collateral asset proposal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        null,
        null,
        (
          await findCollateralAssetNew(
            context,
            sysParams,
            iusdAssetInfo.iassetTokenNameAscii,
            iusdAssetInfo.collateralAssets[0].collateralAsset,
          )
        ).utxo,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute update collateral asset proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

    const [startInterestTx, interestOracleNft] = await startInterestOracle(
      0n,
      0n,
      0n,
      {
        biasTime: 120_000n,
        owner: pkh.hash,
      },
      context.lucid,
    );
    await runAndAwaitTxBuilder(context.lucid, startInterestTx);

    const [priceOracleTx, priceOranceNft] = await startPriceOracleTx(
      context.lucid,
      'NEW_IUSD_ADA_ORACLE',
      rationalFromInt(1n),
      { biasTime: 120_000n, expirationPeriod: 1_800_000n, owner: pkh.hash },
      context.emulator.slot,
    );
    await runAndAwaitTxBuilder(context.lucid, priceOracleTx);

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

    const allIassetOrefs = (
      await findAllIAssets(
        context.lucid,
        sysParams.validatorHashes.iassetHash,
        fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      )
    ).map((iasset) => iasset.utxo);

    const [tx, pollId] = await createProposal(
      {
        UpdateCollateralAsset: {
          correspondingIAsset: fromHex(
            fromText(iusdAssetInfo.iassetTokenNameAscii),
          ),
          collateralAsset: iusdAssetInfo.collateralAssets[0].collateralAsset,
          newAssetExtraDecimals: 0n,
          newAssetPriceInfo: { OracleNft: priceOranceNft },
          newInterestOracleNft: interestOracleNft,
          newLiquidationRatio: { numerator: 120n, denominator: 100n },
          newMaintenanceRatio: { numerator: 150n, denominator: 100n },
          newRedemptionRatio: { numerator: 150n, denominator: 100n },
          newMinCollateralAmt: 10_000_000n,
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      allIassetOrefs,
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const [__, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        await benchmarkAndAwaitTx(
          'Gov - Execute update collateral asset proposal with treasury withdrawal',
          await executeProposal(
            executeUtxo.utxo,
            (
              await findGov(
                context.lucid,
                sysParams.validatorHashes.govHash,
                fromSystemParamsAsset(sysParams.govParams.govNFT),
              )
            ).utxo,
            treasuryWithdrawalUtxo,
            null,
            null,
            null,
            (
              await findCollateralAssetNew(
                context,
                sysParams,
                iusdAssetInfo.iassetTokenNameAscii,
                iusdAssetInfo.collateralAssets[0].collateralAsset,
              )
            ).utxo,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    expect(
      assetClassValueOf(
        newVal,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === withdrawalIndyAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });

  test<MyContext>('Execute create stableswap pool proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const feeManager = fromHex(
      paymentCredentialOf(context.users.admin.address).hash,
    );

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

    const [tx, pollId] = await createProposal(
      {
        ProposeStableswapPool: {
          iasset: fromHex(fromText('iUSD')),
          collateralAsset: EXAMPLE_TOKEN_1,
          collateralToIassetRatio: rationalFromInt(1n),
          mintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          redemptionFeeRatio: { numerator: 5n, denominator: 1_000n },
          feeManager: feeManager,
          minMintingAmount: 0n,
          minRedemptionAmount: 0n,
          stableswapValHash: fromHex(sysParams.validatorHashes.stableswapHash),
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetReference = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      'iUSD',
    );

    const cdpCreatorUtxo = await findRandomCdpCreator(
      context.lucid,
      sysParams.validatorHashes.cdpCreatorHash,
      fromSystemParamsAsset(sysParams.cdpCreatorParams.cdpCreatorNft),
    );

    await benchmarkAndAwaitTx(
      'Execute create Stableswap proposal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetReference.utxo,
        null,
        null,
        cdpCreatorUtxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute create stableswap pool proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const feeManager = fromHex(
      paymentCredentialOf(context.users.admin.address).hash,
    );

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

    const [tx, pollId] = await createProposal(
      {
        ProposeStableswapPool: {
          iasset: fromHex(fromText('iUSD')),
          collateralAsset: EXAMPLE_TOKEN_1,
          collateralToIassetRatio: rationalFromInt(1n),
          mintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          redemptionFeeRatio: { numerator: 5n, denominator: 1_000n },
          feeManager: feeManager,
          minMintingAmount: 0n,
          minRedemptionAmount: 0n,
          stableswapValHash: fromHex(sysParams.validatorHashes.stableswapHash),
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const iassetReference = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      'iUSD',
    );

    const cdpCreatorUtxo = await findRandomCdpCreator(
      context.lucid,
      sysParams.validatorHashes.cdpCreatorHash,
      fromSystemParamsAsset(sysParams.cdpCreatorParams.cdpCreatorNft),
    );

    await benchmarkAndAwaitTx(
      'Execute create Stableswap proposal with treasury withdrawal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        treasuryWithdrawalUtxo,
        null,
        iassetReference.utxo,
        null,
        null,
        cdpCreatorUtxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute modify stableswap pool proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const feeManager = fromHex(
      paymentCredentialOf(context.users.admin.address).hash,
    );
    const newFeeManager = fromHex(
      paymentCredentialOf(context.users.user.address).hash,
    );

    const iassetStableswapPool = fromHex(fromText('iUSD'));
    const collateralAssetStableswapPool = EXAMPLE_TOKEN_1;

    const [tx1, pollId1] = await createProposal(
      {
        ProposeStableswapPool: {
          iasset: iassetStableswapPool,
          collateralAsset: collateralAssetStableswapPool,
          collateralToIassetRatio: rationalFromInt(1n),
          mintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          redemptionFeeRatio: { numerator: 5n, denominator: 1_000n },
          feeManager: feeManager,
          minMintingAmount: 0n,
          minRedemptionAmount: 0n,
          stableswapValHash: fromHex(sysParams.validatorHashes.stableswapHash),
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx1);

    await runCreateAllShards(pollId1, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId1, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId1, sysParams, context);

    await runMergeAllShards(pollId1, sysParams, context);

    await runEndProposal(pollId1, sysParams, context);

    const iassetReference = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      'iUSD',
    );

    const cdpCreatorUtxo = await findRandomCdpCreator(
      context.lucid,
      sysParams.validatorHashes.cdpCreatorHash,
      fromSystemParamsAsset(sysParams.cdpCreatorParams.cdpCreatorNft),
    );

    await runAndAwaitTx(
      context.lucid,
      executeProposal(
        (
          await findExecute(
            context.lucid,
            sysParams.validatorHashes.executeHash,
            fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
            pollId1,
          )
        ).utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetReference.utxo,
        null,
        null,
        cdpCreatorUtxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );

    const [tx2, pollId2] = await createProposal(
      {
        ModifyStableswapPool: {
          iasset: iassetStableswapPool,
          collateralAsset: collateralAssetStableswapPool,
          newCollateralToIassetRatio: rationalFromInt(1n),
          newMintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          newRedemptionFeeRatio: { numerator: 5n, denominator: 1_000n },
          newMintingEnabled: false,
          newRedemptionEnabled: true,
          newFeeManager: newFeeManager,
          newMinMintingAmount: 1n,
          newMinRedemptionAmount: 1n,
          newStableswapValHash: fromHex(
            fromText(sysParams.validatorHashes.stableswapHash),
          ),
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx2);

    await runCreateAllShards(pollId2, sysParams, context);

    await runVote(pollId2, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId2, sysParams, context);

    await runMergeAllShards(pollId2, sysParams, context);

    await runEndProposal(pollId2, sysParams, context);

    const stableswapPoolUtxo = await findStableswapPool(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      toHex(iassetStableswapPool),
      collateralAssetStableswapPool,
    );

    await benchmarkAndAwaitTx(
      'Execute modify Stableswap proposal',
      await executeProposal(
        (
          await findExecute(
            context.lucid,
            sysParams.validatorHashes.executeHash,
            fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
            pollId2,
          )
        ).utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        null,
        null,
        null,
        stableswapPoolUtxo.utxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute modify stableswap pool proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const feeManager = fromHex(
      paymentCredentialOf(context.users.admin.address).hash,
    );
    const newFeeManager = fromHex(
      paymentCredentialOf(context.users.user.address).hash,
    );

    const iassetStableswapPool = fromHex(fromText('iUSD'));
    const collateralAssetStableswapPool = EXAMPLE_TOKEN_1;

    const [tx1, pollId1] = await createProposal(
      {
        ProposeStableswapPool: {
          iasset: iassetStableswapPool,
          collateralAsset: collateralAssetStableswapPool,
          collateralToIassetRatio: rationalFromInt(1n),
          mintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          redemptionFeeRatio: { numerator: 5n, denominator: 1_000n },
          feeManager: feeManager,
          minMintingAmount: 0n,
          minRedemptionAmount: 0n,
          stableswapValHash: fromHex(sysParams.validatorHashes.stableswapHash),
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx1);

    await runCreateAllShards(pollId1, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId1, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId1, sysParams, context);

    await runMergeAllShards(pollId1, sysParams, context);

    await runEndProposal(pollId1, sysParams, context);

    let executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId1,
    );

    const iassetReference = await findIAsset(
      context.lucid,
      sysParams.validatorHashes.iassetHash,
      fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
      'iUSD',
    );

    const cdpCreatorUtxo = await findRandomCdpCreator(
      context.lucid,
      sysParams.validatorHashes.cdpCreatorHash,
      fromSystemParamsAsset(sysParams.cdpCreatorParams.cdpCreatorNft),
    );

    await runAndAwaitTx(
      context.lucid,
      executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        iassetReference.utxo,
        null,
        null,
        cdpCreatorUtxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
    );

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

    const [tx2, pollId2] = await createProposal(
      {
        ModifyStableswapPool: {
          iasset: iassetStableswapPool,
          collateralAsset: collateralAssetStableswapPool,
          newCollateralToIassetRatio: rationalFromInt(1n),
          newMintingFeeRatio: { numerator: 5n, denominator: 1_000n },
          newRedemptionFeeRatio: { numerator: 5n, denominator: 1_000n },
          newMintingEnabled: false,
          newRedemptionEnabled: true,
          newFeeManager: newFeeManager,
          newMinMintingAmount: 1n,
          newMinRedemptionAmount: 1n,
          newStableswapValHash: fromHex(
            fromText(sysParams.validatorHashes.stableswapHash),
          ),
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      (
        await findGov(
          context.lucid,
          sysParams.validatorHashes.govHash,
          fromSystemParamsAsset(sysParams.govParams.govNFT),
        )
      ).utxo,
      (
        await findAllIAssets(
          context.lucid,
          sysParams.validatorHashes.iassetHash,
          fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
        )
      ).map((iasset) => iasset.utxo),
    );

    await runAndAwaitTxBuilder(context.lucid, tx2);

    await runCreateAllShards(pollId2, sysParams, context);

    await runVote(pollId2, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId2, sysParams, context);

    await runMergeAllShards(pollId2, sysParams, context);

    await runEndProposal(pollId2, sysParams, context);

    const stableswapPoolUtxo = await findStableswapPool(
      context.lucid,
      sysParams.validatorHashes.cdpHash,
      fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
      toHex(iassetStableswapPool),
      collateralAssetStableswapPool,
    );

    executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId2,
    );

    await benchmarkAndAwaitTx(
      'Execute modify Stableswap proposal with treasury withdrawal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        treasuryWithdrawalUtxo,
        null,
        null,
        null,
        null,
        stableswapPoolUtxo.utxo,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute modify protocol params proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      {
        ModifyProtocolParams: {
          newParams: {
            proposalDeposit: govUtxo.datum.protocolParams.proposalDeposit * 2n,
            votingPeriod: ONE_DAY * 2n,
            effectiveDelay: govUtxo.datum.protocolParams.effectiveDelay,
            expirationPeriod: ONE_DAY * 2n,
            proposingPeriod: ONE_DAY,
            /// Total numer of shards used for voting.
            totalShards: govUtxo.datum.protocolParams.totalShards,
            /// The minimum number of votes (yes + no votes) for a proposal to be possible to pass.
            minimumQuorum: govUtxo.datum.protocolParams.minimumQuorum,
            /// Maximum amount of lovelaces that can be spent at once from the treasury.
            maxTreasuryLovelaceSpend:
              govUtxo.datum.protocolParams.maxTreasuryLovelaceSpend,
            /// Maximum amount of INDY that can be spent at once from the treasury.
            maxTreasuryIndySpend:
              govUtxo.datum.protocolParams.maxTreasuryIndySpend,
            cdpRedemptionRequiredSignature:
              govUtxo.datum.protocolParams.cdpRedemptionRequiredSignature,
            electorate: govUtxo.datum.protocolParams.electorate,
            foundationMultisig: govUtxo.datum.protocolParams.foundationMultisig,
          },
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Gov - Execute modify protocol params proposal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        null,
        null,
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute modify protocol params proposal with treasury withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

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

    const [tx, pollId] = await createProposal(
      {
        ModifyProtocolParams: {
          newParams: {
            proposalDeposit: govUtxo.datum.protocolParams.proposalDeposit * 2n,
            votingPeriod: ONE_DAY * 2n,
            effectiveDelay: govUtxo.datum.protocolParams.effectiveDelay,
            expirationPeriod: ONE_DAY * 2n,
            proposingPeriod: ONE_DAY,
            /// Total numer of shards used for voting.
            totalShards: govUtxo.datum.protocolParams.totalShards,
            /// The minimum number of votes (yes + no votes) for a proposal to be possible to pass.
            minimumQuorum: govUtxo.datum.protocolParams.minimumQuorum,
            /// Maximum amount of lovelaces that can be spent at once from the treasury.
            maxTreasuryLovelaceSpend:
              govUtxo.datum.protocolParams.maxTreasuryLovelaceSpend,
            /// Maximum amount of INDY that can be spent at once from the treasury.
            maxTreasuryIndySpend:
              govUtxo.datum.protocolParams.maxTreasuryIndySpend,
            cdpRedemptionRequiredSignature:
              govUtxo.datum.protocolParams.cdpRedemptionRequiredSignature,
            electorate: govUtxo.datum.protocolParams.electorate,
            foundationMultisig: govUtxo.datum.protocolParams.foundationMultisig,
          },
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const [__, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        benchmarkAndAwaitTx(
          'Execute modify protocol params proposal with treasury withdrawal',
          await executeProposal(
            executeUtxo.utxo,
            (
              await findGov(
                context.lucid,
                sysParams.validatorHashes.govHash,
                fromSystemParamsAsset(sysParams.govParams.govNFT),
              )
            ).utxo,
            treasuryWithdrawalUtxo,
            null,
            null,
            null,
            null,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    expect(
      assetClassValueOf(
        newVal,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === withdrawalIndyAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });

  test<MyContext>('Execute upgrade protocol proposal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

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

    const [tx, pollId] = await createProposal(
      {
        UpgradeProtocol: {
          content: {
            upgradeId: govUtxo.datum.currentVersion + 1n,
            upgradePaths: [
              [
                fromHex(sysParams.validatorHashes.cdpHash),
                // NOTICE: this is just a placeholder, in real scenario it needs upgrade minting policy hash
                {
                  upgradeSymbol: fromHex(
                    sysParams.validatorHashes.cdpCreatorHash,
                  ),
                },
              ],
            ],
          },
        },
      },
      null,
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    await benchmarkAndAwaitTx(
      'Gov - Execute upgrade protocol proposal',
      await executeProposal(
        executeUtxo.utxo,
        (
          await findGov(
            context.lucid,
            sysParams.validatorHashes.govHash,
            fromSystemParamsAsset(sysParams.govParams.govNFT),
          )
        ).utxo,
        null,
        null,
        null,
        null,
        null,
        null,
        sysParams,
        context.lucid,
        context.emulator.slot,
      ),
      context.lucid,
      context.emulator,
    );
  });

  test<MyContext>('Execute upgrade protocol proposal with withdrawal', async (context: MyContext) => {
    context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

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

    const withdrawalIndyAmt = 1_000n;
    const treasuryWithdrawalUtxo = await createIndyUtxoAtTreasury(
      withdrawalIndyAmt,
      sysParams,
      context,
    );

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

    const [tx, pollId] = await createProposal(
      {
        UpgradeProtocol: {
          content: {
            upgradeId: govUtxo.datum.currentVersion + 1n,
            upgradePaths: [
              [
                fromHex(sysParams.validatorHashes.cdpHash),
                // NOTICE: this is just a placeholder, in real scenario it needs upgrade minting policy hash
                {
                  upgradeSymbol: fromHex(
                    sysParams.validatorHashes.cdpCreatorHash,
                  ),
                },
              ],
            ],
          },
        },
      },
      {
        destination: addressFromBech32(context.users.withdrawalAccount.address),
        value: [
          {
            currencySymbol: fromHex(
              sysParams.govParams.indyAsset[0].unCurrencySymbol,
            ),
            tokenName: fromHex(
              fromText(sysParams.govParams.indyAsset[1].unTokenName),
            ),
            amount: withdrawalIndyAmt,
          },
        ],
      },
      sysParams,
      context.lucid,
      context.emulator.slot,
      govUtxo.utxo,
      [],
    );

    await runAndAwaitTxBuilder(context.lucid, tx);

    await runCreateAllShards(pollId, sysParams, context);

    await runAndAwaitTx(
      context.lucid,
      openStakingPosition(100_000_000_000n, sysParams, context.lucid),
    );

    await runVote(pollId, 'Yes', sysParams, context);

    await waitForVotingEnd(pollId, sysParams, context);

    await runMergeAllShards(pollId, sysParams, context);

    await runEndProposal(pollId, sysParams, context);

    const executeUtxo = await findExecute(
      context.lucid,
      sysParams.validatorHashes.executeHash,
      fromSystemParamsAsset(sysParams.executeParams.upgradeToken),
      pollId,
    );

    const [__, newVal] = await getValueChangeAtAddressAfterAction(
      context.lucid,
      context.users.withdrawalAccount.address,
      async () =>
        benchmarkAndAwaitTx(
          'Gov - Execute upgrade protocol proposal with withdrawal',
          await executeProposal(
            executeUtxo.utxo,
            (
              await findGov(
                context.lucid,
                sysParams.validatorHashes.govHash,
                fromSystemParamsAsset(sysParams.govParams.govNFT),
              )
            ).utxo,
            treasuryWithdrawalUtxo,
            null,
            null,
            null,
            null,
            null,
            sysParams,
            context.lucid,
            context.emulator.slot,
          ),
          context.lucid,
          context.emulator,
        ),
    );

    expect(
      assetClassValueOf(
        newVal,
        fromSystemParamsAsset(sysParams.govParams.indyAsset),
      ) === withdrawalIndyAmt,
      'Unexpected withdrawn indy amt',
    ).toBeTruthy();
  });
});
