UNPKG

@indigo-labs/indigo-sdk

Version:

Indigo SDK for interacting with Indigo endpoints via lucid-evolution

1,841 lines (1,616 loc) 183 kB
import { addAssets, Data, Emulator, EmulatorAccount, fromHex, fromText, generateEmulatorAccount, Lucid, paymentCredentialOf, stakeCredentialOf, } from '@lucid-evolution/lucid'; import { assert, beforeEach, describe, expect, test } from 'vitest'; import { IndigoTestContext, LucidContext, runAndAwaitTx, runAndAwaitTxBuilder, } from '../test-helpers'; import { assetClassValueOf, isAssetsZero, lovelacesAmt, mkAssetsOf, mkLovelacesOf, negateAssets, adaAssetClass, AssetClass, assetClassToUnit, } from '@3rd-eye-labs/cardano-offchain-common'; import { addrDetails, createProposal, createScriptAddress, requestSpAccountCreation, feedInterestOracle, fromSystemParamsAsset, liquidateCdp, matchSingle, mergeCdps, openCdp, redeemCdp, } from '../../src'; import { findAllNecessaryOrefs, findCdp, findCdpCR, findFrozenCDPs, findOwnCdp, findPriceOracleFromCollateralAsset, } from './cdp-queries'; import { getValueChangeAtAddressAfterAction, getValueChangeAtAddressesAfterAction, } from '../utils'; import { feedPriceOracleTx } from '../../src/contracts/price-oracle/transactions'; import { assertValueInRange, expectScriptFailure } from '../utils/asserts'; import { benchmarkAndAwaitTx } from '../utils/benchmark-utils'; import { ieurInitialAssetCfg, iusdInitialAssetCfg, mkBaseCollateralAsset, } from '../mock/assets-mock'; import { init } from '../endpoints/initialize'; import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers'; import { mutatedRedeemCdp, runCloseCdpWrongOracle, runTestAdjustCdpDelisted, runRedeemCdpWrongOracle, runOpenCdpDelisted, runOpenCdpAndUpdateOracle, } from './transactions-mutated'; import { runBurnCdp, runCloseCdp, runCreateAndFreezeCdps, runDepositCdp, runFreezeCdp, runMintCdp, runOpenCdp, runRedeemCdp, runWithdrawCdp, } from './actions'; import { calculateCdpInterest } from './cdp-helpers'; import { runFeedPriceToOracle } from '../price-oracle/actions'; import { findGov } from '../gov/governance-queries'; import { findCollateralAsset } from '../queries/iasset-queries'; import { processSuccessfulProposal } from '../gov/actions'; import { runOpenCdpAndCreateSPAccount, runProcessSpRequest, } from '../stability-pool/actions'; import { createMultipleUtxosAtTreasury, createUtxoAtTreasury, } from '../endpoints/treasury'; import { mkTreasuryAddr } from '../../src/contracts/treasury/helpers'; import { MAINNET_PROTOCOL_PARAMETERS } from '../indigo-test-helpers'; import { rationalFloor, rationalFromInt, rationalMul, rationalToFloat, } from '../../src/types/rational'; import * as Core from '@evolution-sdk/evolution'; type MyContext = LucidContext<{ admin: EmulatorAccount; user: EmulatorAccount; }>; const collateralAssetA: AssetClass = { currencySymbol: fromHex( // random generated 'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dea', ), tokenName: fromHex(fromText('A')), }; const collateralAssetB: AssetClass = { currencySymbol: fromHex( 'e356735fbb1a674fec7db1e015ab31213f86320c94225577587d0197', ), tokenName: fromHex(fromText('B')), }; describe('CDP', () => { beforeEach<MyContext>(async (context: MyContext) => { context.users = { admin: generateEmulatorAccount({ lovelace: BigInt(100_000_000_000_000), [assetClassToUnit(collateralAssetA)]: 1_000_000_000_000n, [assetClassToUnit(collateralAssetB)]: 1_000_000_000_000n, }), user: generateEmulatorAccount( addAssets( mkLovelacesOf(200_000_000n), mkAssetsOf(collateralAssetA, 1_000_000_000_000n), mkAssetsOf(collateralAssetB, 1_000_000_000_000n), ), ), }; context.emulator = new Emulator( [context.users.admin, context.users.user], MAINNET_PROTOCOL_PARAMETERS, ); context.lucid = await Lucid(context.emulator, 'Custom'); }); test<MyContext>('Open CDP; ADA collateral', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction( context.lucid, mkTreasuryAddr(context.lucid, sysParams), async () => benchmarkAndAwaitTx( 'CDP - Open CDP; ADA collateral', await runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 5_000_000n, ), context.lucid, context.emulator, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; assert( // A 0.5% of 5,000,000 minted iAsset go to treasury. assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n && // This is still less than the minimum ADA per UTxO, but enough // to cover one more asset class in the treasury output. lovelacesAmt(addedValueInTreasury) < 700_000n, 'Unexpected value received by the treasury', ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 199, max: 200 }, ); }); test<MyContext>('Open CDP; ADA collateral - direct treasury payment', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction( context.lucid, mkTreasuryAddr(context.lucid, sysParams), async () => benchmarkAndAwaitTx( 'CDP - Open CDP; ADA collateral - direct treasury payment', await runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 5_000_000n, undefined, // Direct treasury payment. true, ), context.lucid, context.emulator, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; assert( // A 0.5% of 5,000,000 minted iAsset go to treasury. assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n && // Extra ADA had to be paid to cover the minimum. lovelacesAmt(addedValueInTreasury) > 1_000_000n, 'Unexpected value received by the treasury', ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 199, max: 200 }, ); }); test<MyContext>('Open CDP; non-ADA collateral asset', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction( context.lucid, mkTreasuryAddr(context.lucid, sysParams), async () => benchmarkAndAwaitTx( 'CDP - Open CDP; non-ADA collateral', await runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 10_000_000n, 5_000_000n, ), context.lucid, context.emulator, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; assert( // A 0.5% of 5,000,000 minted iAsset go to treasury. assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n && // This is still less than the minimum ADA per UTxO, but enough // to cover one more asset class in the treasury output. lovelacesAmt(addedValueInTreasury) < 700_000n, 'Unexpected value received by the treasury', ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 199, max: 200 }, ); }); test<MyContext>('Open CDP; non-ADA collateral asset - direct treasury payment', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction( context.lucid, mkTreasuryAddr(context.lucid, sysParams), async () => benchmarkAndAwaitTx( 'CDP - Open CDP; non-ADA collateral - direct treasury payment', await runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 10_000_000n, 5_000_000n, undefined, // Direct treasury payment. true, ), context.lucid, context.emulator, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; assert( // A 0.5% of 5,000,000 minted iAsset go to treasury. assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n && // Extra ADA had to be paid to cover the minimum. lovelacesAmt(addedValueInTreasury) > 1_000_000n, 'Unexpected value received by the treasury', ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 199, max: 200 }, ); }); // This tests opening a CDP with a collateral asset with 8 decimals, such as xBTC. test<MyContext>('Open CDP; non-ADA collateral asset with more decimals', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, _] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [ mkBaseCollateralAsset( collateralAssetA, 0n, rationalFromInt(1n), 2n, ), ], }, ], context.emulator.slot, ); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, // 150% CR 1_500_000_000n, 10_000_000n, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 149, max: 151 }, ); await expectScriptFailure( 'Undercollaterized', runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, // 149.9999999% CR 1_499_999_999n, 10_000_000n, ), ); }); // This tests opening a CDP with a collateral asset with 4 decimals. test<MyContext>('Open CDP; non-ADA collateral asset with less decimals', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, _] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [ mkBaseCollateralAsset( collateralAssetA, 0n, rationalFromInt(1n), -2n, ), ], }, ], context.emulator.slot, ); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, // 150% CR 15_000_000n, 1_000_000_000n, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 149, max: 151 }, ); await expectScriptFailure( 'Undercollaterized', runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, // 149.99999% CR 14_999_999n, 1_000_000_000n, ), ); }); test<IndigoTestContext>('Open CDP with large rational price oracle', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); const orefs = await findAllNecessaryOrefs( context.lucid, sysParams, iusdAssetInfo.iassetTokenNameAscii, adaAssetClass, ); const priceOracleUtxo = await findPriceOracleFromCollateralAsset( context.lucid, orefs.collateralAsset, ); context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const largeBigInt = BigInt(Number.MAX_SAFE_INTEGER) * BigInt(Number.MAX_SAFE_INTEGER); await runAndAwaitTx( context.lucid, feedPriceOracleTx( context.lucid, priceOracleUtxo!, { numerator: largeBigInt, denominator: largeBigInt }, iusdAssetInfo.collateralAssets[0].oracleParams!, context.emulator.slot, ), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await benchmarkAndAwaitTx( 'CDP - Open CDP with large rational price oracle', await runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 500_000n, ), context.lucid, context.emulator, ); }); test<IndigoTestContext>('Open CDP with large auxilary data oracle', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); const orefs = await findAllNecessaryOrefs( context.lucid, sysParams, iusdAssetInfo.iassetTokenNameAscii, adaAssetClass, ); const priceOracleUtxo = await findPriceOracleFromCollateralAsset( context.lucid, orefs.collateralAsset, ); context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); // Create a 1KB (1024 bytes) string in hex const oneKbBuf = Buffer.alloc(1024, 'A'); await runAndAwaitTx( context.lucid, feedPriceOracleTx( context.lucid, priceOracleUtxo!, { numerator: 1n, denominator: 1n }, iusdAssetInfo.collateralAssets[0].oracleParams!, context.emulator.slot, Core.Data.fromCBORHex( Data.to(Data.fromJson(oneKbBuf.toString('ascii'))), ), ), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await benchmarkAndAwaitTx( 'CDP - Open CDP with large auxilary data oracle', await runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 500_000n, ), context.lucid, context.emulator, ); }); test<IndigoTestContext>('Open CDP with expired oracle fails', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); //Await until oracle is expired context.emulator.awaitSlot( Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) / 1000, ); await expectScriptFailure( 'X', runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 500_000n, ), ); }); test<IndigoTestContext>('Open CDP with delisted iAsset fails', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iUsdInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); const govUtxo = await findGov( context.lucid, sysParams.validatorHashes.govHash, fromSystemParamsAsset(sysParams.govParams.govNFT), ); const collateralAssetToUpdate = await findCollateralAsset( context.lucid, sysParams, fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken), 'iUSD', iUsdInfo.collateralAssets[0].collateralAsset, ); context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [tx, pollId] = await createProposal( { UpdateCollateralAsset: { correspondingIAsset: fromHex(fromText('iUSD')), collateralAsset: collateralAssetA, newAssetExtraDecimals: 0n, newAssetPriceInfo: { Delisted: { price: rationalFromInt(1n) }, }, newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft, newLiquidationRatio: rationalFromInt(1n), newMaintenanceRatio: rationalFromInt(1n), newRedemptionRatio: rationalFromInt(100n), newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt, }, }, null, sysParams, context.lucid, context.emulator.slot, govUtxo.utxo, [], ); await runAndAwaitTxBuilder(context.lucid, tx); await processSuccessfulProposal( pollId, null, null, null, null, collateralAssetToUpdate.utxo, null, sysParams, context, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await expectScriptFailure( 'W', runOpenCdpDelisted( context, sysParams, 'iUSD', collateralAssetA, 10_000_000n, 500_000n, ), ); }); test<MyContext>('Open CDP with hybrid oracle', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); const [_, addedValueInTreasury] = await getValueChangeAtAddressAfterAction( context.lucid, mkTreasuryAddr(context.lucid, sysParams), async () => benchmarkAndAwaitTx( 'CDP - Open CDP with hybrid oracle', await runOpenCdpAndUpdateOracle( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 5_000_000n, iusdAssetInfo.collateralAssets[0].oracleParams!, rationalFromInt(1n), ), context.lucid, context.emulator, ), ); const cdp = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; assert( // A 0.5% of 5,000,000 minted iAsset go to treasury. assetClassValueOf(addedValueInTreasury, iAssetAc) == 25_000n && // This is still less than the minimum ADA per UTxO, but enough // to cover one more asset class in the treasury output. lovelacesAmt(addedValueInTreasury) < 700_000n, 'Unexpected value received by the treasury', ); assertValueInRange( await findCdpCR(context.lucid, sysParams, cdp, context.emulator.slot), { min: 199, max: 200 }, ); }); test<MyContext>('Deposit CDP; ADA collateral', 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); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 10_000_000n, 500_000n, ), ); await benchmarkAndAwaitTx( 'CDP - Deposit CDP; ADA collateral', await runDepositCdp(context, sysParams, 'iUSD', adaAssetClass), context.lucid, context.emulator, ); }); test<MyContext>('Deposit CDP; non-ADA collateral', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, _] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 10_000_000n, 500_000n, ), ); await benchmarkAndAwaitTx( 'CDP - Deposit CDP', await runDepositCdp(context, sysParams, 'iUSD', collateralAssetA), context.lucid, context.emulator, ); }); test<IndigoTestContext>('Deposit CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const [pkh, skh] = await addrDetails(context.lucid); const iasset = 'iUSD'; const initialCollateral = 10_000_000n; const initialMint = 500_000n; await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, iasset, adaAssetClass, initialCollateral, initialMint, ), ); context.emulator.awaitSlot(6500); await runFeedPriceToOracle( context, sysParams, iusdAssetInfo, adaAssetClass, rationalFromInt(1n), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const cdpBeforeAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); const cdpOwedInterest = await calculateCdpInterest( cdpBeforeAction.datum, context, sysParams, ); const [__, interestCollectorValChange] = await getValueChangeAtAddressAfterAction( context.lucid, createScriptAddress( context.lucid.config().network!, sysParams.validatorHashes.interestCollectionHash, ), async () => benchmarkAndAwaitTx( 'CDP - Deposit CDP w/ interest; ADA collateral', await runDepositCdp(context, sysParams, 'iUSD', adaAssetClass), context.lucid, context.emulator, ), ); const interestPaid = assetClassValueOf(interestCollectorValChange, { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }); assert( interestPaid > 0 && interestPaid == cdpOwedInterest, 'Expected interest paid to collector', ); const cdpAfterAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid); expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe( initialCollateral + 1_000_000n, ); }); test<MyContext>('Deposit CDP w/ interest; non-ADA collateral', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const [pkh, skh] = await addrDetails(context.lucid); const iasset = 'iUSD'; const initialCollateral = 10_000_000n; const initialMint = 500_000n; await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, iasset, collateralAssetA, initialCollateral, initialMint, ), ); context.emulator.awaitSlot(6500); await runFeedPriceToOracle( context, sysParams, iusdAssetInfo, collateralAssetA, rationalFromInt(1n), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const cdpBeforeAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); const cdpOwedInterest = await calculateCdpInterest( cdpBeforeAction.datum, context, sysParams, ); const [__, interestCollectorValChange] = await getValueChangeAtAddressAfterAction( context.lucid, createScriptAddress( context.lucid.config().network!, sysParams.validatorHashes.interestCollectionHash, ), async () => benchmarkAndAwaitTx( 'CDP - Deposit CDP w/ interest; non-ADA collateral', await runDepositCdp(context, sysParams, iasset, collateralAssetA), context.lucid, context.emulator, ), ); const interestPaid = assetClassValueOf(interestCollectorValChange, { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }); assert( interestPaid > 0 && interestPaid == cdpOwedInterest, 'Expected interest paid to collector', ); const cdpAfterAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid); expect( assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA), ).toBe(initialCollateral + 1_000_000n); }); test<IndigoTestContext>('Deposit CDP with expired oracle succeeds', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 11_000_000n, 500_000n, ), ); //Await until oracle is expired context.emulator.awaitSlot( Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) / 1000, ); await runAndAwaitTx( context.lucid, runDepositCdp(context, sysParams, 'iUSD', adaAssetClass), ); }); test<IndigoTestContext>('Deposit CDP with delisted iAsset fails', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iUsdInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 10_000_000n, 500_000n, ), ); const govUtxo = await findGov( context.lucid, sysParams.validatorHashes.govHash, fromSystemParamsAsset(sysParams.govParams.govNFT), ); const collateralAssetToUpdate = await findCollateralAsset( context.lucid, sysParams, fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken), iUsdInfo.iassetTokenNameAscii, iUsdInfo.collateralAssets[0].collateralAsset, ); context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [tx, pollId] = await createProposal( { UpdateCollateralAsset: { correspondingIAsset: fromHex(fromText('iUSD')), collateralAsset: collateralAssetA, newAssetExtraDecimals: 0n, newAssetPriceInfo: { Delisted: { price: rationalFromInt(1n) }, }, newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft, newLiquidationRatio: rationalFromInt(1n), newMaintenanceRatio: rationalFromInt(1n), newRedemptionRatio: rationalFromInt(100n), newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt, }, }, null, sysParams, context.lucid, context.emulator.slot, govUtxo.utxo, [], ); await runAndAwaitTxBuilder(context.lucid, tx); await processSuccessfulProposal( pollId, null, null, null, null, collateralAssetToUpdate.utxo, null, sysParams, context, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await expectScriptFailure( 'G', runTestAdjustCdpDelisted( context, sysParams, 'iUSD', collateralAssetA, 1_000_000n, 0n, ), ); }); test<MyContext>('Withdraw CDP; ADA collateral', 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); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 12_000_000n, 500_000n, ), ); await benchmarkAndAwaitTx( 'CDP - Withdraw CDP; ADA collateral', await runWithdrawCdp(context, sysParams, 'iUSD', adaAssetClass), context.lucid, context.emulator, ); }); test<MyContext>('Withdraw CDP; non-ADA collateral', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, _] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 12_000_000n, 500_000n, ), ); await benchmarkAndAwaitTx( 'CDP - Withdraw CDP; non-ADA collateral', await runWithdrawCdp(context, sysParams, 'iUSD', collateralAssetA), context.lucid, context.emulator, ); }); test<IndigoTestContext>('Withdraw CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const iasset = 'iUSD'; const initialCollateral = 12_000_000n; const initialMint = 500_000n; await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, iasset, adaAssetClass, initialCollateral, initialMint, ), ); context.emulator.awaitSlot(6500); await runFeedPriceToOracle( context, sysParams, iusdAssetInfo, adaAssetClass, rationalFromInt(1n), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const [pkh, skh] = await addrDetails(context.lucid); const cdpBeforeAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); const cdpOwedInterest = await calculateCdpInterest( cdpBeforeAction.datum, context, sysParams, ); const [__, interestCollectorValChange] = await getValueChangeAtAddressAfterAction( context.lucid, createScriptAddress( context.lucid.config().network!, sysParams.validatorHashes.interestCollectionHash, ), async () => benchmarkAndAwaitTx( 'CDP - Withdraw CDP w/ interest; ADA collateral', await runWithdrawCdp(context, sysParams, 'iUSD', adaAssetClass), context.lucid, context.emulator, ), ); const interestPaid = assetClassValueOf(interestCollectorValChange, { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }); assert( interestPaid > 0 && interestPaid == cdpOwedInterest, 'Expected interest paid to collector', ); const cdpAfterAction = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid); expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe( initialCollateral - 1_000_000n, ); }); test<MyContext>('Withdraw CDP w/ interest; non-ADA collateral', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const iasset = 'iUSD'; const initialCollateral = 12_000_000n; const initialMint = 500_000n; await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, iasset, collateralAssetA, 12_000_000n, 500_000n, ), ); context.emulator.awaitSlot(6500); await runFeedPriceToOracle( context, sysParams, iusdAssetInfo, collateralAssetA, rationalFromInt(1n), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const [pkh, skh] = await addrDetails(context.lucid); const cdpBeforeAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); const cdpOwedInterest = await calculateCdpInterest( cdpBeforeAction.datum, context, sysParams, ); const [__, interestCollectorValChange] = await getValueChangeAtAddressAfterAction( context.lucid, createScriptAddress( context.lucid.config().network!, sysParams.validatorHashes.interestCollectionHash, ), async () => benchmarkAndAwaitTx( 'CDP - Withdraw CDP w/ interest; non-ADA collateral', await runWithdrawCdp(context, sysParams, iasset, collateralAssetA), context.lucid, context.emulator, ), ); const interestPaid = assetClassValueOf(interestCollectorValChange, { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }); assert( interestPaid > 0 && interestPaid == cdpOwedInterest, 'Expected interest paid to collector', ); const cdpAfterAction = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); expect(cdpAfterAction.datum.mintedAmt).toBe(initialMint + interestPaid); expect( assetClassValueOf(cdpAfterAction.utxo.assets, collateralAssetA), ).toBe(initialCollateral - 1_000_000n); }); test<IndigoTestContext>('Withdraw CDP with expired oracle fails', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 12_000_000n, 500_000n, ), ); //Await until oracle is expired context.emulator.awaitSlot( Number(iusdAssetInfo.collateralAssets[0].oracleParams!.expirationPeriod) / 1000, ); await expectScriptFailure( 'X', runWithdrawCdp(context, sysParams, 'iUSD', adaAssetClass), ); }); test<IndigoTestContext>('Withdraw from CDP with delisted iAsset succeeds', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iUsdInfo]] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 11_000_000n, 500_000n, ), ); const govUtxo = await findGov( context.lucid, sysParams.validatorHashes.govHash, fromSystemParamsAsset(sysParams.govParams.govNFT), ); const collateralAssetToUpdate = await findCollateralAsset( context.lucid, sysParams, fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken), iUsdInfo.iassetTokenNameAscii, iUsdInfo.collateralAssets[0].collateralAsset, ); context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [tx, pollId] = await createProposal( { UpdateCollateralAsset: { correspondingIAsset: fromHex(fromText('iUSD')), collateralAsset: collateralAssetA, newAssetExtraDecimals: 0n, newAssetPriceInfo: { Delisted: { price: rationalFromInt(1n) }, }, newInterestOracleNft: collateralAssetToUpdate.datum.interestOracleNft, newLiquidationRatio: rationalFromInt(1n), newMaintenanceRatio: rationalFromInt(1n), newRedemptionRatio: rationalFromInt(100n), newMinCollateralAmt: collateralAssetToUpdate.datum.minCollateralAmt, }, }, null, sysParams, context.lucid, context.emulator.slot, govUtxo.utxo, [], ); await runAndAwaitTxBuilder(context.lucid, tx); await processSuccessfulProposal( pollId, null, null, null, null, collateralAssetToUpdate.utxo, null, sysParams, context, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runWithdrawCdp(context, sysParams, 'iUSD', collateralAssetA), ); }); test<MyContext>('Mint CDP; ADA collateral', 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); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', adaAssetClass, 12_000_000n, 500_000n, ), ); await benchmarkAndAwaitTx( 'CDP - Mint CDP; ADA collateral', await runMintCdp(context, sysParams, 'iUSD', adaAssetClass), context.lucid, context.emulator, ); }); test<MyContext>('Mint CDP; non-ADA collateral', async (context: MyContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, _] = await init( context.lucid, [ { ...iusdInitialAssetCfg(), collateralAssets: [mkBaseCollateralAsset(collateralAssetA)], }, ], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, 'iUSD', collateralAssetA, 12_000_000n, 500_000n, ), ); await benchmarkAndAwaitTx( 'CDP - Mint CDP; non-ADA collateral', await runMintCdp(context, sysParams, 'iUSD', collateralAssetA), context.lucid, context.emulator, ); }); test<IndigoTestContext>('Mint CDP w/ interest; ADA collateral', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const iasset = 'iUSD'; const initialCollateral = 12_000_000n; const initialMint = 500_000n; await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, iasset, adaAssetClass, initialCollateral, initialMint, ), ); context.emulator.awaitSlot(60500); await runFeedPriceToOracle( context, sysParams, iusdAssetInfo, adaAssetClass, rationalFromInt(1n), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const debtAdjustment = 100_000n; const [pkh, skh] = await addrDetails(context.lucid); const cdpBeforeAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); const cdpOwedInterest = await calculateCdpInterest( cdpBeforeAction.datum, context, sysParams, ); const [__, [interestCollectorValChange, treasuryValChange]] = await getValueChangeAtAddressesAfterAction( context.lucid, [ createScriptAddress( context.lucid.config().network!, sysParams.validatorHashes.interestCollectionHash, ), mkTreasuryAddr(context.lucid, sysParams), ], async () => benchmarkAndAwaitTx( 'CDP - Mint CDP w/ interest; ADA collateral', await runMintCdp( context, sysParams, 'iUSD', adaAssetClass, debtAdjustment, ), context.lucid, context.emulator, ), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; const interestPaid = assetClassValueOf( interestCollectorValChange, iAssetAc, ); assert( interestPaid > 0 && interestPaid == cdpOwedInterest, 'Expected interest paid to collector', ); assert( assetClassValueOf(treasuryValChange, iAssetAc) == rationalFloor( rationalMul( iusdInitialAssetCfg().debtMintingFeeRatio, rationalFromInt(debtAdjustment), ), ) && lovelacesAmt(treasuryValChange) < 700_000n, 'Unexpected value received by the treasury', ); const cdpAfterAction = await findOwnCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), ); expect(cdpAfterAction.datum.mintedAmt).toBe( initialMint + 100_000n + interestPaid, ); expect(lovelacesAmt(cdpAfterAction.utxo.assets)).toBe(initialCollateral); }); test<IndigoTestContext>('Mint CDP w/ interest; ADA collateral - direct treasury payment', async (context: IndigoTestContext) => { context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase); const [sysParams, [iusdAssetInfo]] = await init( context.lucid, [iusdInitialAssetCfg()], context.emulator.slot, ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const iasset = 'iUSD'; const initialCollateral = 12_000_000n; const initialMint = 500_000n; await runAndAwaitTx( context.lucid, runOpenCdp( context, sysParams, iasset, adaAssetClass, initialCollateral, initialMint, ), ); context.emulator.awaitSlot(60500); await runFeedPriceToOracle( context, sysParams, iusdAssetInfo, adaAssetClass, rationalFromInt(1n), ); context.lucid.selectWallet.fromSeed(context.users.user.seedPhrase); const debtAdjustment = 100_000n; const [pkh, skh] = await addrDetails(context.lucid); const cdpBeforeAction = await findCdp( context.lucid, sysParams.validatorHashes.cdpHash, fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), pkh.hash, skh, ); const cdpOwedInterest = await calculateCdpInterest( cdpBeforeAction.datum, context, sysParams, ); const [__, [interestCollectorValChange, treasuryValChange]] = await getValueChangeAtAddressesAfterAction( context.lucid, [ createScriptAddress( context.lucid.config().network!, sysParams.validatorHashes.interestCollectionHash, ), mkTreasuryAddr(context.lucid, sysParams), ], async () => benchmarkAndAwaitTx( 'CDP - Mint CDP w/ interest; ADA collateral - direct treasury payment', await runMintCdp( context, sysParams, 'iUSD', adaAssetClass, debtAdjustment, undefined, true, ), context.lucid, context.emulator, ), ); const iAssetAc = { currencySymbol: fromHex( sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol, ), tokenName: fromHex(fromText(iusdAssetInfo.iassetTokenNameAscii)), }; const interestPaid = assetClassValueOf( interestCollectorValChange, iAssetAc, ); assert( interestPaid > 0 && interestPaid == cdpOwedInterest, 'Expected interest