Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions tests/lrp-leverage.test.ts → tests/lrp/lrp-leverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
parseInterestOracleDatum,
parsePriceOracleDatum,
SystemParams,
} from '../src';
} from '../../src';
import {
addAssets,
Emulator,
Expand All @@ -21,31 +21,31 @@ import {
toText,
UTxO,
} from '@lucid-evolution/lucid';
import { findAllNecessaryOrefs, findCdp } from './queries/cdp-queries';
import { LucidContext, runAndAwaitTx } from './test-helpers';
import { findAllNecessaryOrefs, findCdp } from '../queries/cdp-queries';
import { LucidContext, runAndAwaitTx } from '../test-helpers';
import { describe } from 'vitest';
import {
assetClassValueOf,
lovelacesAmt,
mkLovelacesOf,
} from '../src/utils/value-helpers';
import { init } from './endpoints/initialize';
import { iusdInitialAssetCfg } from './mock/assets-mock';
import { findAllLrps } from './queries/lrp-queries';
import { ocdFloor, OnChainDecimal } from '../src/types/on-chain-decimal';
import { assertValueInRange } from './utils/asserts';
} from '../../src/utils/value-helpers';
import { init } from '../endpoints/initialize';
import { iusdInitialAssetCfg } from '../mock/assets-mock';
import { findAllLrps } from './lrp-queries';
import { ocdFloor, OnChainDecimal } from '../../src/types/on-chain-decimal';
import { assertValueInRange } from '../utils/asserts';

import {
calculateLeverageFromCollateralRatio,
MAX_REDEMPTIONS_WITH_CDP_OPEN,
} from '../src/contracts/leverage/helpers';
import { leverageCdpWithLrp } from '../src/contracts/leverage/transactions';
} from '../../src/contracts/leverage/helpers';
import { leverageCdpWithLrp } from '../../src/contracts/leverage/transactions';
import {
calculateTotalAdaForRedemption,
lrpRedeemableLovelacesInclReimb,
MIN_LRP_COLLATERAL_AMT,
randomLrpsSubsetSatisfyingTargetLovelaces,
} from '../src/contracts/lrp/helpers';
} from '../../src/contracts/lrp/helpers';

type MyContext = LucidContext<{
admin: EmulatorAccount;
Expand Down
File renamed without changes.
97 changes: 86 additions & 11 deletions tests/lrp.test.ts → tests/lrp/lrp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,33 @@ import {
toText,
UTxO,
} from '@lucid-evolution/lucid';
import { parseLrpDatumOrThrow } from '../src/contracts/lrp/types';
import { parseLrpDatumOrThrow } from '../../src/contracts/lrp/types';
import {
adjustLrp,
cancelLrp,
claimLrp,
openLrp,
redeemLrp,
} from '../src/contracts/lrp/transactions';
import { findLrp } from './queries/lrp-queries';
import { addrDetails, getInlineDatumOrThrow } from '../src/utils/lucid-utils';
import { LucidContext, runAndAwaitTx } from './test-helpers';
import { matchSingle } from '../src/utils/utils';
import { AssetClass, openCdp, SystemParams } from '../src';
} from '../../src/contracts/lrp/transactions';
import { findLrp } from './lrp-queries';
import {
addrDetails,
getInlineDatumOrThrow,
} from '../../src/utils/lucid-utils';
import { LucidContext, runAndAwaitTx } from '../test-helpers';
import { matchSingle } from '../../src/utils/utils';
import { AssetClass, openCdp, SystemParams } from '../../src';
import {
assetClassValueOf,
lovelacesAmt,
mkLovelacesOf,
} from '../src/utils/value-helpers';
} from '../../src/utils/value-helpers';
import { strictEqual } from 'assert';
import { init } from './endpoints/initialize';
import { iusdInitialAssetCfg } from './mock/assets-mock';
import { findAllNecessaryOrefs } from './queries/cdp-queries';
import { init } from '../endpoints/initialize';
import { iusdInitialAssetCfg } from '../mock/assets-mock';
import { findAllNecessaryOrefs } from '../queries/cdp-queries';
import { redeemLrpMutated } from './transactions-mutated';
import { expectScriptFailure } from '../utils/asserts';

type MyContext = LucidContext<{
admin: EmulatorAccount;
Expand Down Expand Up @@ -434,6 +439,76 @@ describe('LRP', () => {
);
});

test<MyContext>('redemption without adaptive OCD replace fails', async (context: MyContext) => {
context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

const [sysParams, __] = await init(context.lucid, [iusdInitialAssetCfg]);

const iasset = fromText(iusdInitialAssetCfg.name);

const [ownPkh, _] = await addrDetails(context.lucid);

{
const orefs = await findAllNecessaryOrefs(
context.lucid,
sysParams,
toText(iasset),
);

await runAndAwaitTx(
context.lucid,
openCdp(
100_000_000n,
30_000_000n,
sysParams,
orefs.cdpCreatorUtxo,
orefs.iasset.utxo,
orefs.priceOracleUtxo,
orefs.interestOracleUtxo,
orefs.collectorUtxo,
context.lucid,
context.emulator.slot,
),
);
}

await runAndAwaitTx(
context.lucid,
openLrp(
iasset,
20_000_000n,
{ getOnChainInt: 1_000_000n },
context.lucid,
sysParams,
),
true,
);

const lrpUtxo = await findSingleLrp(context, sysParams, iasset, ownPkh);

const redemptionIAssetAmt = 11_000_000n;

{
const orefs = await findAllNecessaryOrefs(
context.lucid,
sysParams,
toText(iasset),
);

await expectScriptFailure(
'Wrong continuing output',
redeemLrpMutated(
[[lrpUtxo, redemptionIAssetAmt]],
orefs.priceOracleUtxo,
orefs.iasset.utxo,
context.lucid,
sysParams,
{ type: 'ignore-adaptive-replace' },
),
);
}
});

test<MyContext>('redeem, redeem again and cancel', async (context: MyContext) => {
context.lucid.selectWallet.fromSeed(context.users.admin.seedPhrase);

Expand Down
212 changes: 212 additions & 0 deletions tests/lrp/transactions-mutated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import {
LucidEvolution,
TxBuilder,
OutRef,
unixTimeToSlot,
slotToUnixTime,
UTxO,
addAssets,
} from '@lucid-evolution/lucid';
import { unzip, zip } from 'fp-ts/lib/Array';
import {
getInlineDatumOrThrow,
parsePriceOracleDatum,
parseIAssetDatumOrThrow,
matchSingle,
fromSystemParamsScriptRef,
SystemParams,
serialiseLrpDatum,
parseLrpDatumOrThrow,
serialiseLrpRedeemer,
lovelacesAmt,
MIN_LRP_COLLATERAL_AMT,
mkLovelacesOf,
mkAssetsOf,
} from '../../src';
import { ocdMul, OnChainDecimal } from '../../src/types/on-chain-decimal';
import { match, P } from 'ts-pattern';
import { array as A, function as F } from 'fp-ts';
import { calculateFeeFromPercentage } from '../../src/utils/indigo-helpers';

export function buildRedemptionsTx(
/** The tuple represents the LRP UTXO and the amount of iAssets to redeem against it. */
redemptions: [UTxO, bigint][],
price: OnChainDecimal,
redemptionReimbursementPercentage: OnChainDecimal,
sysParams: SystemParams,
tx: TxBuilder,
/**
* The number of Tx outputs before these.
*/
txOutputsBeforeCount: bigint,
withoutAdaptiveReplace: boolean = false,
): TxBuilder {
const [[mainLrpUtxo, _], __] = match(redemptions)
.with(
[P._, ...P.array()],
([[firstLrp, _], ...rest]): [[UTxO, bigint], [UTxO, bigint][]] => [
[firstLrp, _],
rest,
],
)
.otherwise(() => {
throw new Error('Expects at least 1 UTXO to redeem.');
});

const mainLrpDatum = parseLrpDatumOrThrow(getInlineDatumOrThrow(mainLrpUtxo));

return F.pipe(
redemptions,
A.reduceWithIndex<[UTxO, bigint], TxBuilder>(
tx,
(idx, acc, [lrpUtxo, redeemIAssetAmt]) => {
const lovelacesForRedemption = ocdMul(
{
getOnChainInt: redeemIAssetAmt,
},
price,
).getOnChainInt;
const reimburstmentLovelaces = calculateFeeFromPercentage(
redemptionReimbursementPercentage,
lovelacesForRedemption,
);

const lrpRawInlineDatum = getInlineDatumOrThrow(lrpUtxo);
const lrpDatum = parseLrpDatumOrThrow(lrpRawInlineDatum);

const resultVal = addAssets(
lrpUtxo.assets,
mkLovelacesOf(-lovelacesForRedemption + reimburstmentLovelaces),
mkAssetsOf(
{
currencySymbol:
sysParams.lrpParams.iassetPolicyId.unCurrencySymbol,
tokenName: mainLrpDatum.iasset,
},
redeemIAssetAmt,
),
);

if (lovelacesAmt(resultVal) < MIN_LRP_COLLATERAL_AMT) {
throw new Error('LRP was incorrectly initialised.');
}

return acc
.collectFrom(
[lrpUtxo],
serialiseLrpRedeemer(
idx === 0
? { Redeem: { continuingOutputIdx: txOutputsBeforeCount + 0n } }
: {
RedeemAuxiliary: {
continuingOutputIdx: txOutputsBeforeCount + BigInt(idx),
mainRedeemOutRef: {
txHash: { hash: mainLrpUtxo.txHash },
outputIndex: BigInt(mainLrpUtxo.outputIndex),
},
asset: mainLrpDatum.iasset,
assetPrice: price,
redemptionReimbursementPercentage:
redemptionReimbursementPercentage,
},
},
),
)
.pay.ToContract(
lrpUtxo.address,
{
kind: 'inline',
value: serialiseLrpDatum(
{
...lrpDatum,
lovelacesToSpend:
lrpDatum.lovelacesToSpend - lovelacesForRedemption,
},
withoutAdaptiveReplace
? undefined
: {
_tag: 'adaptiveReplace',
spentLrpDatum: lrpRawInlineDatum,
},
),
},
resultVal,
);
},
),
);
}

export type RedeemLrpMutatedType =
| { type: 'no-mutations' }
| { type: 'ignore-adaptive-replace' };

export async function redeemLrpMutated(
/** The tuple represents the LRP outref and the amount of iAssets to redeem against it. */
redemptionLrpsData: [OutRef, bigint][],
priceOracleOutRef: OutRef,
iassetOutRef: OutRef,
lucid: LucidEvolution,
sysParams: SystemParams,
redeemLrpMutatedType: RedeemLrpMutatedType = { type: 'no-mutations' },
): Promise<TxBuilder> {
const network = lucid.config().network!;

const lrpScriptRefUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.lrpValidatorRef),
]),
(_) => new Error('Expected a single LRP Ref Script UTXO'),
);

const priceOracleUtxo = matchSingle(
await lucid.utxosByOutRef([priceOracleOutRef]),
(_) => new Error('Expected a single price oracle UTXO'),
);

const iassetUtxo = matchSingle(
await lucid.utxosByOutRef([iassetOutRef]),
(_) => new Error('Expected a single IAsset UTXO'),
);

const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);

const [lrpsToRedeemOutRefs, lrpRedemptionIAssetAmt] =
unzip(redemptionLrpsData);

const priceOracleDatum = parsePriceOracleDatum(
getInlineDatumOrThrow(priceOracleUtxo),
);

const redemptionLrps = await lucid
.utxosByOutRef(lrpsToRedeemOutRefs)
.then((val) => zip(val, lrpRedemptionIAssetAmt));

const tx = buildRedemptionsTx(
redemptionLrps,
priceOracleDatum.price,
iassetDatum.redemptionReimbursementPercentage,
sysParams,
lucid.newTx(),
0n,
redeemLrpMutatedType.type === 'ignore-adaptive-replace',
);

return (
lucid
.newTx()
.validTo(
slotToUnixTime(
network,
unixTimeToSlot(network, Number(priceOracleDatum.expiration)) - 1,
),
)
// Ref script
.readFrom([lrpScriptRefUtxo])
// Ref inputs
.readFrom([iassetUtxo, priceOracleUtxo])
.compose(tx)
);
}
3 changes: 2 additions & 1 deletion tests/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export type LucidContext<T extends Record<string, EmulatorAccount>> = {
export async function runAndAwaitTx(
lucid: LucidEvolution,
transaction: Promise<TxBuilder>,
canonical: boolean = false,
): Promise<string> {
const txHash = await transaction
.then((tx) => tx.complete())
.then((tx) => tx.complete({ canonical }))
.then((tx) => tx.sign.withWallet().complete())
.then((tx) => tx.submit());

Expand Down
Loading
Loading