@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
510 lines (467 loc) • 13.3 kB
text/typescript
import { beforeEach, describe, test, expect } from 'vitest';
import { IndigoTestContext, runAndAwaitTx } from '../test-helpers';
import {
EXAMPLE_TOKEN_1,
EXAMPLE_TOKEN_2,
EXAMPLE_TOKEN_3,
MAINNET_PROTOCOL_PARAMETERS,
} from '../indigo-test-helpers';
import {
addAssets,
Emulator,
fromHex,
fromText,
generateEmulatorAccount,
generateEmulatorAccountFromPrivateKey,
Lucid,
slotToUnixTime,
toHex,
} from '@lucid-evolution/lucid';
import {
adaAssetClass,
AssetClass,
mkAssetsOf,
mkLovelacesOf,
} from '@3rd-eye-labs/cardano-offchain-common';
import {
DEFAULT_INTEREST,
DEFAULT_PRICE,
ibtcInitialAssetCfgWithPyth,
iethInitialAssetCfgWithPyth,
iusdInitialAssetCfgWithPyth,
mkBaseCollateralAsset,
} from '../mock/assets-mock';
import { runFreezeCdp, runOpenCdp, runWithdrawCdp } from '../cdp/actions';
import { benchmarkAndAwaitTx } from '../utils/benchmark-utils';
import { addrDetails, MIN_ROB_COLLATERAL_AMT, openRob } from '../../src';
import { ParsedFeedPayload } from '@pythnetwork/pyth-lazer-sdk';
import { createPythMessage } from './helpers';
import { findSingleRob } from '../rob/rob-queries';
import { expectValue } from '../utils/asserts';
import { runRedeemRob } from '../rob/actions';
import { rationalFromInt } from '../../src/types/rational';
import { init } from '../endpoints/initialize';
const collateralAssetA: AssetClass = {
currencySymbol: fromHex(
// random generated
'cc072059ae741791b7b9c23d9baea6a0b0d764dec617ce7e027a8dea',
),
tokenName: fromHex(fromText('A')),
};
describe('Pyth > Indigo', () => {
beforeEach<IndigoTestContext>(async (context: IndigoTestContext) => {
context.users = {
admin: generateEmulatorAccount(
addAssets(
mkLovelacesOf(100_000_000_000_000n),
mkAssetsOf(EXAMPLE_TOKEN_1, 1_000_000_000_000_000n),
mkAssetsOf(collateralAssetA, 100_000_000n),
),
),
user: generateEmulatorAccount(
addAssets(
mkLovelacesOf(100_000_000_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),
),
),
user2: generateEmulatorAccount({
lovelace: BigInt(100_000_000_000_000),
}),
withdrawalAccount: generateEmulatorAccountFromPrivateKey({}),
};
context.emulator = new Emulator(
[
context.users.admin,
context.users.user,
context.users.user2,
context.users.withdrawalAccount,
],
MAINNET_PROTOCOL_PARAMETERS,
);
context.lucid = await Lucid(context.emulator, 'Custom');
context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);
const [systemParams, assetConfigs] = await init(
context.lucid,
[],
context.emulator.slot,
(pythStateNft: AssetClass) => [
iusdInitialAssetCfgWithPyth(pythStateNft, (pythStatePolicyId) => [
mkBaseCollateralAsset(
adaAssetClass,
DEFAULT_INTEREST,
DEFAULT_PRICE,
0n,
pythStatePolicyId,
{
tag: 'value',
val: { priceFeedId: 1 },
},
),
]),
iethInitialAssetCfgWithPyth(pythStateNft, (pythStatePolicyId) => [
mkBaseCollateralAsset(
collateralAssetA,
DEFAULT_INTEREST,
DEFAULT_PRICE,
0n,
pythStatePolicyId,
{
tag: 'value',
val: { priceFeedId: 2 },
},
),
]),
ibtcInitialAssetCfgWithPyth(pythStateNft, (pythStatePolicyId) => [
mkBaseCollateralAsset(
adaAssetClass,
DEFAULT_INTEREST,
DEFAULT_PRICE,
0n,
pythStatePolicyId,
{
tag: 'divide',
val: [
{ tag: 'value', val: { priceFeedId: 1 } },
{ tag: 'value', val: { priceFeedId: 2 } },
],
},
),
]),
],
);
context.systemParams = systemParams;
context.assetConfigs = assetConfigs;
});
test('Open CDP - Direct Price Value (ada collateral)', async (context: IndigoTestContext) => {
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
const iUsdFeed: ParsedFeedPayload = {
priceFeedId: 1,
price: '1000000',
exponent: -6,
};
const message = await createPythMessage([iUsdFeed], currentTime * 1_000n);
await benchmarkAndAwaitTx(
'Pyth - CDP - Direct Price Value (ADA collateral)',
await runOpenCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
10_000_000n,
500_000n,
toHex(message),
),
context.lucid,
context.emulator,
);
});
test('Open CDP - Direct Price Value (non-ADA collateral)', async (context: IndigoTestContext) => {
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
const iEthFeed: ParsedFeedPayload = {
priceFeedId: 2,
price: '1000000',
exponent: -6,
feedUpdateTimestamp: Number(currentTime) * 1000,
};
const message = await createPythMessage([iEthFeed], currentTime * 1_000n);
await benchmarkAndAwaitTx(
'Pyth - CDP - Direct Price Value (non-ADA collateral)',
await runOpenCdp(
context,
context.systemParams,
'iETH',
collateralAssetA,
10_000_000n,
500_000n,
toHex(message),
),
context.lucid,
context.emulator,
);
});
test('Open CDP - Divide Price Value', async (context: IndigoTestContext) => {
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
const feed1: ParsedFeedPayload = {
priceFeedId: 1,
price: '1000000',
exponent: -6,
};
const feed2: ParsedFeedPayload = {
priceFeedId: 2,
price: '10',
exponent: -1,
};
const message = await createPythMessage(
[feed1, feed2],
currentTime * 1_000n,
);
await benchmarkAndAwaitTx(
'Pyth - CDP - Divide Price Value',
await runOpenCdp(
context,
context.systemParams,
'iBTC',
adaAssetClass,
10_000_000n,
500_000n,
toHex(message),
),
context.lucid,
context.emulator,
);
});
test('Open CDP - Fails if missing feed price', async (context: IndigoTestContext) => {
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
// This feed doesn't include a price for feedId 2, which is used by the iBTC asset
const feed1: ParsedFeedPayload = {
priceFeedId: 1,
price: '1000000',
exponent: -6,
};
const message = await createPythMessage([feed1], currentTime * 1_000n);
await expect(
runOpenCdp(
context,
context.systemParams,
'iBTC',
adaAssetClass,
10_000_000n,
500_000n,
toHex(message),
),
).rejects.toThrowError('Pyth payload not found for feed ID: 2');
});
test('Withdraw CDP - Direct Price Value', async (context: IndigoTestContext) => {
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
const iUsdFeed: ParsedFeedPayload = {
priceFeedId: 1,
price: '1000000',
exponent: -6,
feedUpdateTimestamp: Number(currentTime) * 1000,
};
const message = await createPythMessage([iUsdFeed], currentTime * 1_000n);
await runAndAwaitTx(
context.lucid,
runOpenCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
11_000_000n,
500_000n,
toHex(message),
),
);
await benchmarkAndAwaitTx(
'Pyth - CDP - Withdraw CDP - Direct Price Value',
await runWithdrawCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
1_000_000n,
toHex(message),
),
context.lucid,
context.emulator,
);
});
test('Freeze CDP - Direct Price Value', async (context: IndigoTestContext) => {
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
{
const iUsdFeed: ParsedFeedPayload = {
priceFeedId: 1,
price: '1000000',
exponent: -6,
};
const message = await createPythMessage([iUsdFeed], currentTime * 1_000n);
await runAndAwaitTx(
context.lucid,
runOpenCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
11_000_000n,
500_000n,
toHex(message),
),
);
}
{
const iUsdFeed: ParsedFeedPayload = {
priceFeedId: 1,
price: '100000000',
exponent: -6,
};
const message = await createPythMessage([iUsdFeed], currentTime * 1_000n);
const [pkh, skh] = await addrDetails(context.lucid);
await benchmarkAndAwaitTx(
'Pyth - CDP - Freeze CDP - Direct Price Value',
await runFreezeCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
pkh.hash,
skh,
toHex(message),
),
context.lucid,
context.emulator,
);
}
});
test<IndigoTestContext>('ROB redemption on buy order', async (context: IndigoTestContext) => {
const [ownPkh, _] = await addrDetails(context.lucid);
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
const iUsdFeed: ParsedFeedPayload = {
priceFeedId: 1,
price: '1000000',
exponent: -6,
feedUpdateTimestamp: Number(currentTime) * 1000,
};
const message = await createPythMessage([iUsdFeed], currentTime * 1_000n);
await runAndAwaitTx(
context.lucid,
runOpenCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
100_000_000n,
30_000_000n,
toHex(message),
),
);
const initialDeposit = 20_000_000n;
await runAndAwaitTx(
context.lucid,
openRob(
'iUSD',
initialDeposit,
{
BuyIAssetOrder: {
collateralAsset: adaAssetClass,
maxPrice: rationalFromInt(1n),
},
},
context.lucid,
context.systemParams,
),
);
const robUtxo = await findSingleRob(
context,
context.systemParams,
'iUSD',
ownPkh,
);
const redemptionIAssetAmt = 7_500_000n;
await benchmarkAndAwaitTx(
'Pyth - ROB - Redemption on buy order',
await runRedeemRob(
context,
context.systemParams,
[[robUtxo, redemptionIAssetAmt]],
'iUSD',
adaAssetClass,
context.emulator.slot,
toHex(message),
),
context.lucid,
context.emulator,
);
});
test<IndigoTestContext>('ROB redemption on sell order', async (context: IndigoTestContext) => {
const [ownPkh, _] = await addrDetails(context.lucid);
const currentTime = BigInt(
slotToUnixTime(context.lucid.config().network!, context.emulator.slot),
);
const iUsdFeed: ParsedFeedPayload = {
priceFeedId: 1,
price: '1250000',
exponent: -6,
feedUpdateTimestamp: Number(currentTime) * 1000,
};
const message = await createPythMessage([iUsdFeed], currentTime * 1_000n);
await runAndAwaitTx(
context.lucid,
runOpenCdp(
context,
context.systemParams,
'iUSD',
adaAssetClass,
100_000_000n,
30_000_000n,
toHex(message),
),
);
const initialDeposit = 20_000_000n;
await runAndAwaitTx(
context.lucid,
openRob(
'iUSD',
initialDeposit,
{
SellIAssetOrder: {
allowedCollateralAssets: [[adaAssetClass, rationalFromInt(1n)]],
},
},
context.lucid,
context.systemParams,
),
);
const robUtxo = await findSingleRob(
context,
context.systemParams,
'iUSD',
ownPkh,
);
const iassetAc: AssetClass = {
currencySymbol: fromHex(
context.systemParams.robParams.iassetPolicyId.unCurrencySymbol,
),
tokenName: fromHex(fromText('iUSD')),
};
expectValue(
robUtxo.utxo.assets,
'Wrong ROB value before redemption',
).toEqual(
addAssets(
mkLovelacesOf(MIN_ROB_COLLATERAL_AMT),
mkAssetsOf(iassetAc, initialDeposit),
),
);
const payoutCollateralAmt = 7_500_000n;
await benchmarkAndAwaitTx(
'Pyth - ROB - Redemption on sell order',
await runRedeemRob(
context,
context.systemParams,
[[robUtxo, payoutCollateralAmt]],
'iUSD',
adaAssetClass,
context.emulator.slot,
toHex(message),
),
context.lucid,
context.emulator,
);
});
});