@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
1,818 lines (1,579 loc) • 156 kB
text/typescript
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),
);