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