UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

1,818 lines (1,579 loc) 156 kB
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), );