@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
1,730 lines (1,551 loc) • 48.5 kB
text/typescript
import {
addAssets,
Data,
fromHex,
LucidEvolution,
Credential,
OutRef,
slotToUnixTime,
toHex,
TxBuilder,
UTxO,
getInputIndices,
credentialToAddress,
validatorToScriptHash,
credentialToRewardAddress,
scriptHashToCredential,
} from '@lucid-evolution/lucid';
import {
fromSystemParamsAsset,
fromSystemParamsScriptRef,
SystemParams,
} from '../../src/types/system-params';
import {
addrDetails,
calculateMinCollateralCappedIAssetRedemptionAmt,
createScriptAddress,
getInlineDatumOrThrow,
matchSingle,
mkPriceOracleValidator,
mkTreasuryValidatorFromSP,
ONE_SECOND,
PriceOracleParams,
treasuryFeeTx,
} from '../../src';
import {
parseCdpDatumOrThrow,
serialiseCdpDatum,
serialiseCdpRedeemer,
serialiseRedeemCdpWithdrawalRedeemer,
} from '../../src/contracts/cdp/types-new';
import {
parseCollateralAssetDatumOrThrow,
parseIAssetDatumOrThrow,
} from '../../src/contracts/iasset/types';
import {
parsePriceOracleDatum,
serialisePriceOracleDatum,
serialisePriceOracleRedeemer,
} from '../../src/contracts/price-oracle/types-new';
import { parseInterestOracleDatum } from '../../src/contracts/interest-oracle/types-new';
import { parseGovDatumOrThrow } from '../../src/contracts/gov/types-new';
import { match, P } from 'ts-pattern';
import {
calculateAccruedInterest,
calculateUnitaryInterestSinceOracleLastUpdated,
} from '../../src/contracts/interest-oracle/helpers';
import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers';
import {
AssetClass,
assetClassValueOf,
mkAssetsOf,
mkLovelacesOf,
} from '@3rd-eye-labs/cardano-offchain-common';
import { bigintMin } from '../../src/utils/bigint-utils';
import { oracleExpirationAwareValidity } from '../../src/contracts/price-oracle/helpers';
import {
findAllNecessaryOrefs,
findCdp,
findPriceOracleFromCollateralAsset,
} from './cdp-queries';
import { collectInterestTx } from '../../src/contracts/interest-collection/transactions';
import { findCollateralAsset, findIAsset } from '../queries/iasset-queries';
import { findInterestOracle } from '../queries/interest-oracle-queries';
import { AssetInfo } from '../endpoints/initialize';
import { LucidContext } from '../test-helpers';
import { option as O, function as F } from 'fp-ts';
import { findPriceOracle } from '../price-oracle/price-oracle-queries';
import { findRandomNonAdminInterestCollector } from '../interest-collection/interest-collector-queries';
import {
serialiseCDPCreatorDatum,
serialiseCDPCreatorRedeemer,
} from '../../src/contracts/cdp-creator/types-new';
import {
CollectInterestVariation,
testCollectInterest,
} from '../interest-collection/transactions-mutated';
import { findRandomTreasuryUtxoWithOnlyAda } from '../treasury/treasury-queries';
import {
Rational,
rationalFloor,
rationalFromInt,
rationalMul,
} from '../../src/types/rational';
import { retrieveAdjustedPrice } from '../../src/utils/oracle-helpers';
import * as Core from '@evolution-sdk/evolution';
export async function mutatedRedeemCdp(
/**
* When the goal is to redeem the maximum possible, just pass in the total minted amount of the CDP.
* The logic will automatically cap the amount to the max.
*/
attemptedRedemptionIAssetAmt: bigint,
cdpOref: OutRef,
iassetOref: OutRef,
collateralAssetOref: OutRef,
priceOracleOref: OutRef | undefined,
interestOracleOref: OutRef,
interestCollectorOref: OutRef,
govOref: OutRef,
sysParams: SystemParams,
lucid: LucidEvolution,
currentSlot: number,
pythMessage?: string,
): Promise<TxBuilder> {
const network = lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, currentSlot));
const cdpRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
]),
(_) => new Error('Expected a single cdp Ref Script UTXO'),
);
const cdpRedeemRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.cdpRedeemValidatorRef,
),
]),
(_) => new Error('Expected a single cdp redeem Ref Script UTXO'),
);
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const cdpUtxo = matchSingle(
await lucid.utxosByOutRef([cdpOref]),
(_) => new Error('Expected a single cdp UTXO'),
);
const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));
const iassetUtxo = matchSingle(
await lucid.utxosByOutRef([iassetOref]),
(_) => new Error('Expected a single iasset UTXO'),
);
const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);
const collateralAssetUtxo = matchSingle(
await lucid.utxosByOutRef([collateralAssetOref]),
(_) => new Error('Expected a single iasset UTXO'),
);
const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
getInlineDatumOrThrow(collateralAssetUtxo),
);
const isDelisted = match(collateralAssetDatum.priceInfo)
.with({ Delisted: P.any }, () => true)
.otherwise(() => false);
if (!isDelisted && priceOracleOref === undefined) {
throw new Error('Missing price oracle');
}
const [adjustedPrice, priceOracleUtxo] = await retrieveAdjustedPrice(
iassetDatum.assetName,
collateralAssetDatum.collateralAsset,
collateralAssetDatum.priceInfo,
collateralAssetDatum.extraDecimals,
priceOracleOref,
pythMessage,
sysParams.pythConfig,
lucid,
);
const interestOracleUtxo = matchSingle(
await lucid.utxosByOutRef([interestOracleOref]),
(_) => new Error('Expected a single interest oracle UTXO'),
);
const interestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(interestOracleUtxo),
);
const govUtxo = matchSingle(
await lucid.utxosByOutRef([govOref]),
(_) => new Error('Expected a single gov UTXO'),
);
const govDatum = parseGovDatumOrThrow(getInlineDatumOrThrow(govUtxo));
const interestAmt = match(cdpDatum.cdpFees)
.with({ FrozenCDPAccumulatedFees: P.any }, () => {
throw new Error('CDP fees wrong');
})
.with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
return calculateAccruedInterest(
currentTime,
interest.unitaryInterestSnapshot,
cdpDatum.mintedAmt,
interest.lastSettled,
interestOracleDatum,
);
})
.exhaustive();
const collateralAmt = assetClassValueOf(
cdpUtxo.assets,
cdpDatum.collateralAsset,
);
const totalCdpDebt = cdpDatum.mintedAmt + interestAmt;
const [isPartial, redemptionIAssetAmt] = (() => {
const res = calculateMinCollateralCappedIAssetRedemptionAmt(
collateralAmt,
totalCdpDebt,
adjustedPrice,
collateralAssetDatum.redemptionRatio,
iassetDatum.redemptionReimbursementRatio,
BigInt(collateralAssetDatum.minCollateralAmt),
);
const redemptionAmt = bigintMin(
attemptedRedemptionIAssetAmt,
res.cappedIAssetRedemptionAmt,
);
return [redemptionAmt < res.cappedIAssetRedemptionAmt, redemptionAmt];
})();
if (redemptionIAssetAmt <= 0) {
throw new Error("There's no iAssets available for redemption.");
}
const redemptionCollateralAmt = rationalFloor(
rationalMul(adjustedPrice, rationalFromInt(redemptionIAssetAmt)),
);
const isPublicRedemption =
!govDatum.protocolParams.cdpRedemptionRequiredSignature;
const partialRedemptionFee =
isPartial && isPublicRedemption
? BigInt(sysParams.cdpRedeemParams.partialRedemptionExtraFeeLovelace)
: 0n;
const processingFee = calculateFeeFromRatio(
iassetDatum.redemptionProcessingFeeRatio,
redemptionIAssetAmt,
);
const reimburstmentFee = calculateFeeFromRatio(
iassetDatum.redemptionReimbursementRatio,
redemptionCollateralAmt,
);
const referenceScripts = [
cdpRefScriptUtxo,
iAssetTokenPolicyRefScriptUtxo,
cdpRedeemRefScriptUtxo,
];
const referenceInputs = [
iassetUtxo,
collateralAssetUtxo,
interestOracleUtxo,
govUtxo,
];
const tx = lucid
.newTx()
// Ref Script
.readFrom(referenceScripts)
// Ref inputs
.readFrom(referenceInputs)
.collectFrom([cdpUtxo], serialiseCdpRedeemer('RedeemCdp'))
.mintAssets(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
},
interestAmt - redemptionIAssetAmt,
),
Data.void(),
)
.pay.ToContract(
cdpUtxo.address,
{
kind: 'inline',
value: serialiseCdpDatum({
...cdpDatum,
mintedAmt: totalCdpDebt - redemptionIAssetAmt,
cdpFees: {
ActiveCDPInterestTracking: {
lastSettled: currentTime,
unitaryInterestSnapshot:
interestOracleDatum.unitaryInterest +
calculateUnitaryInterestSinceOracleLastUpdated(
currentTime,
interestOracleDatum,
),
},
},
}),
},
addAssets(
cdpUtxo.assets,
mkLovelacesOf(-redemptionCollateralAmt),
mkLovelacesOf(reimburstmentFee),
),
);
if (priceOracleUtxo !== undefined) {
const priceOracleDatum = parsePriceOracleDatum(
getInlineDatumOrThrow(priceOracleUtxo),
);
const txValidity = oracleExpirationAwareValidity(
currentSlot,
Number(sysParams.cdpCreatorParams.biasTime),
Number(priceOracleDatum.expirationTime),
network,
);
referenceInputs.push(priceOracleUtxo);
tx.validFrom(txValidity.validFrom)
.validTo(txValidity.validTo)
.readFrom([priceOracleUtxo]);
} else {
const validateFrom = slotToUnixTime(network, currentSlot - 1);
const validateTo =
validateFrom + Number(sysParams.cdpCreatorParams.biasTime);
tx.validFrom(validateFrom).validTo(validateTo);
}
// Trigger CDP Redeem Withdrawal validator
tx.withdraw(
credentialToRewardAddress(
lucid.config().network!,
scriptHashToCredential(sysParams.cdpParams.cdpRedeemValHash),
),
0n,
serialiseRedeemCdpWithdrawalRedeemer({
cdpOutReference: {
txHash: fromHex(cdpUtxo.txHash),
outputIndex: BigInt(cdpUtxo.outputIndex),
},
currentTime: currentTime,
priceOracleIdx: priceOracleUtxo
? {
OracleRefInputIdx: getInputIndices(
[priceOracleUtxo],
[...referenceInputs, ...referenceScripts],
)[0],
}
: 'OracleVoid',
}),
);
//TODO: Use a treasury input to save on ADA.
tx.pay.ToContract(
credentialToAddress(lucid.config().network!, {
hash: validatorToScriptHash(
mkTreasuryValidatorFromSP(sysParams.treasuryParams),
),
type: 'Script',
}),
{ kind: 'inline', value: Data.void() },
addAssets(
mkAssetsOf(cdpDatum.collateralAsset, processingFee),
mkLovelacesOf(partialRedemptionFee),
),
);
if (interestAmt > 0n) {
await collectInterestTx(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
},
interestAmt,
),
lucid,
sysParams,
tx,
interestCollectorOref,
);
}
return tx;
}
export async function runOpenCdpDelisted(
context: LucidContext,
sysParams: SystemParams,
asset: string,
collateralAsset: AssetClass,
initialCollateral: bigint,
initialMint: bigint,
): Promise<TxBuilder> {
const orefs = await findAllNecessaryOrefs(
context.lucid,
sysParams,
asset,
collateralAsset,
);
const network = context.lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, context.emulator.slot));
const [pkh, skh] = await addrDetails(context.lucid);
const cdpCreatorRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.cdpCreatorValidatorRef,
),
]),
(_) => new Error('Expected a single cdp creator Ref Script UTXO'),
);
const cdpAuthTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef,
),
]),
(_) => new Error('Expected a single cdp auth token policy Ref Script UTXO'),
);
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const iassetUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.iasset.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);
const collateralAssetUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.collateralAsset.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
getInlineDatumOrThrow(collateralAssetUtxo),
);
const interestOracleUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.interestOracleUtxo]),
(_) => new Error('Expected a single interest oracle UTXO'),
);
const interestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(interestOracleUtxo),
);
const cdpCreatorUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.cdpCreatorUtxo]),
(_) => new Error('Expected a single CDP creator UTXO'),
);
const cdpNftVal = mkAssetsOf(
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1n,
);
const iassetClass = {
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
};
const iassetTokensVal = mkAssetsOf(iassetClass, initialMint);
const refScripts: UTxO[] = [
cdpCreatorRefScriptUtxo,
cdpAuthTokenPolicyRefScriptUtxo,
iAssetTokenPolicyRefScriptUtxo,
];
const referenceInputs: UTxO[] = [
interestOracleUtxo,
iassetUtxo,
collateralAssetUtxo,
];
const tx = context.lucid
.newTx()
.readFrom(refScripts)
.readFrom(referenceInputs)
.mintAssets(cdpNftVal, Data.void())
.mintAssets(iassetTokensVal, Data.void())
.pay.ToContract(
createScriptAddress(network, sysParams.validatorHashes.cdpHash, skh),
{
kind: 'inline',
value: serialiseCdpDatum({
cdpOwner: fromHex(pkh.hash),
iasset: iassetDatum.assetName,
collateralAsset: collateralAssetDatum.collateralAsset,
mintedAmt: initialMint,
cdpFees: {
ActiveCDPInterestTracking: {
lastSettled: currentTime,
unitaryInterestSnapshot:
calculateUnitaryInterestSinceOracleLastUpdated(
currentTime,
interestOracleDatum,
) + interestOracleDatum.unitaryInterest,
},
},
}),
},
addAssets(
cdpNftVal,
mkAssetsOf(collateralAssetDatum.collateralAsset, initialCollateral),
),
)
.pay.ToContract(
cdpCreatorUtxo.address,
{
kind: 'inline',
value: serialiseCDPCreatorDatum({
creatorInputOref: {
outputIndex: BigInt(cdpCreatorUtxo.outputIndex),
txHash: fromHex(cdpCreatorUtxo.txHash),
},
}),
},
cdpCreatorUtxo.assets,
);
const debtMintingFee = calculateFeeFromRatio(
iassetDatum.debtMintingFeeRatio,
initialMint,
);
const treasuryRefScriptUtxo =
debtMintingFee > 0
? await treasuryFeeTx(
iassetClass,
debtMintingFee,
0n,
context.lucid,
sysParams,
tx,
cdpCreatorUtxo,
orefs.treasuryUtxo,
)
: undefined;
const validFrom = slotToUnixTime(network, context.emulator.slot - 1);
const validTo = validFrom + Number(sysParams.cdpCreatorParams.biasTime);
tx.validFrom(validFrom).validTo(validTo);
// We need to take into account the treasury ref script.
const refInputsIndices = getInputIndices(referenceInputs, [
...referenceInputs,
...refScripts,
...(treasuryRefScriptUtxo != null ? [treasuryRefScriptUtxo] : []),
]);
tx.collectFrom([cdpCreatorUtxo], {
kind: 'self',
makeRedeemer: (inputIdx) => {
return serialiseCDPCreatorRedeemer({
CreateCDP: {
cdpOwner: fromHex(pkh.hash),
minted: initialMint,
collateralAmt: initialCollateral,
currentTime: currentTime,
creatorInputIdx: inputIdx,
creatorOutputIdx: 1n,
cdpOutputIdx: 0n,
iassetRefInputIdx: refInputsIndices[1],
collateralAssetRefInputIdx: refInputsIndices[2],
interestOracleRefInputIdx: refInputsIndices[0],
priceOracleIdx: 'OracleVoid',
},
});
},
});
return tx;
}
export async function runOpenCdpAndUpdateOracle(
context: LucidContext,
sysParams: SystemParams,
asset: string,
collateralAsset: AssetClass,
initialCollateral: bigint,
initialMint: bigint,
oracleParams: PriceOracleParams,
newPrice: Rational,
): Promise<TxBuilder> {
const orefs = await findAllNecessaryOrefs(
context.lucid,
sysParams,
asset,
collateralAsset,
);
const network = context.lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, context.emulator.slot));
const [pkh, skh] = await addrDetails(context.lucid);
const oracleValidator = mkPriceOracleValidator(oracleParams);
const cdpCreatorRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.cdpCreatorValidatorRef,
),
]),
(_) => new Error('Expected a single cdp creator Ref Script UTXO'),
);
const cdpAuthTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef,
),
]),
(_) => new Error('Expected a single cdp auth token policy Ref Script UTXO'),
);
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const iassetUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.iasset.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);
const collateralAssetUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.collateralAsset.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
getInlineDatumOrThrow(collateralAssetUtxo),
);
const priceOracleUtxo = await findPriceOracleFromCollateralAsset(
context.lucid,
orefs.collateralAsset,
);
if (!priceOracleUtxo) throw new Error('Expected a price oracle');
const interestOracleUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.interestOracleUtxo]),
(_) => new Error('Expected a single interest oracle UTXO'),
);
const interestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(interestOracleUtxo),
);
const cdpCreatorUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.cdpCreatorUtxo]),
(_) => new Error('Expected a single CDP creator UTXO'),
);
const cdpNftVal = mkAssetsOf(
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
1n,
);
const iassetClass = {
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
};
const iassetTokensVal = mkAssetsOf(iassetClass, initialMint);
const refScripts: UTxO[] = [
cdpCreatorRefScriptUtxo,
cdpAuthTokenPolicyRefScriptUtxo,
iAssetTokenPolicyRefScriptUtxo,
];
const referenceInputs: UTxO[] = [
interestOracleUtxo,
iassetUtxo,
collateralAssetUtxo,
];
const tx = context.lucid
.newTx()
.validFrom(Number(currentTime - oracleParams.biasTime) + ONE_SECOND)
.validTo(Number(currentTime + oracleParams.biasTime) - ONE_SECOND)
.attach.SpendingValidator(oracleValidator)
.readFrom(refScripts)
.readFrom(referenceInputs)
.mintAssets(cdpNftVal, Data.void())
.mintAssets(iassetTokensVal, Data.void())
.collectFrom(
[priceOracleUtxo],
serialisePriceOracleRedeemer({
currentTime: currentTime,
newPrice: newPrice,
}),
)
.pay.ToContract(
priceOracleUtxo.address,
{
kind: 'inline',
value: serialisePriceOracleDatum({
price: newPrice,
expirationTime: currentTime + oracleParams.expirationPeriod,
auxiliaryData: Core.Data.fromCBORHex(Data.void()),
}),
},
priceOracleUtxo.assets,
)
.pay.ToContract(
createScriptAddress(network, sysParams.validatorHashes.cdpHash, skh),
{
kind: 'inline',
value: serialiseCdpDatum({
cdpOwner: fromHex(pkh.hash),
iasset: iassetDatum.assetName,
collateralAsset: collateralAssetDatum.collateralAsset,
mintedAmt: initialMint,
cdpFees: {
ActiveCDPInterestTracking: {
lastSettled: currentTime,
unitaryInterestSnapshot:
calculateUnitaryInterestSinceOracleLastUpdated(
currentTime,
interestOracleDatum,
) + interestOracleDatum.unitaryInterest,
},
},
}),
},
addAssets(
cdpNftVal,
mkAssetsOf(collateralAssetDatum.collateralAsset, initialCollateral),
),
)
.pay.ToContract(
cdpCreatorUtxo.address,
{
kind: 'inline',
value: serialiseCDPCreatorDatum({
creatorInputOref: {
outputIndex: BigInt(cdpCreatorUtxo.outputIndex),
txHash: fromHex(cdpCreatorUtxo.txHash),
},
}),
},
cdpCreatorUtxo.assets,
)
.addSignerKey(pkh.hash);
const debtMintingFee = calculateFeeFromRatio(
iassetDatum.debtMintingFeeRatio,
initialMint,
);
const treasuryRefScriptUtxo =
debtMintingFee > 0
? await treasuryFeeTx(
iassetClass,
debtMintingFee,
0n,
context.lucid,
sysParams,
tx,
cdpCreatorUtxo,
orefs.treasuryUtxo,
)
: undefined;
// We need to take into account the treasury ref script as well.
const refInputsIndices = getInputIndices(referenceInputs, [
...referenceInputs,
...refScripts,
...(treasuryRefScriptUtxo != null ? [treasuryRefScriptUtxo] : []),
]);
tx.collectFrom([cdpCreatorUtxo], {
kind: 'self',
makeRedeemer: (inputIdx) => {
return serialiseCDPCreatorRedeemer({
CreateCDP: {
cdpOwner: fromHex(pkh.hash),
minted: initialMint,
collateralAmt: initialCollateral,
currentTime: currentTime,
creatorInputIdx: inputIdx,
creatorOutputIdx: 2n,
cdpOutputIdx: 1n,
iassetRefInputIdx: refInputsIndices[1],
collateralAssetRefInputIdx: refInputsIndices[2],
interestOracleRefInputIdx: refInputsIndices[0],
priceOracleIdx: { OracleOutputIdx: 0n },
},
});
},
});
return tx;
}
export async function runTestAdjustCdpDelisted(
context: LucidContext,
sysParams: SystemParams,
asset: string,
collateralAsset: AssetClass,
collateralAdjustment: bigint,
debtAdjustment: bigint,
): Promise<TxBuilder> {
const network = context.lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, context.emulator.slot));
const [pkh, skh] = await addrDetails(context.lucid);
const cdp = await findCdp(
context.lucid,
sysParams.validatorHashes.cdpHash,
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
pkh.hash,
skh,
);
const cdpRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
]),
(_) => new Error('Expected a single cdp Ref Script UTXO'),
);
const cdpUtxo = matchSingle(
await context.lucid.utxosByOutRef([cdp.utxo]),
(_) => new Error('Expected a single cdp UTXO'),
);
const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));
const iassetOutput = await findIAsset(
context.lucid,
sysParams.validatorHashes.iassetHash,
fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
asset,
);
const iassetUtxo = matchSingle(
await context.lucid.utxosByOutRef([iassetOutput.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);
const collateralAssetOutput = await findCollateralAsset(
context.lucid,
sysParams,
fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
asset,
collateralAsset,
);
const collateralAssetUtxo = matchSingle(
await context.lucid.utxosByOutRef([collateralAssetOutput.utxo]),
(_) => new Error('Expected a single collateral asset UTXO'),
);
const collateralAssetDatum = parseCollateralAssetDatumOrThrow(
getInlineDatumOrThrow(collateralAssetUtxo),
);
const interestOracleUtxo = await findInterestOracle(
context.lucid,
collateralAssetDatum.interestOracleNft,
);
const interestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(interestOracleUtxo),
);
const interestCollectorUtxo = await findRandomNonAdminInterestCollector(
context.lucid,
sysParams.validatorHashes.interestCollectionHash,
fromSystemParamsAsset(sysParams.interestCollectionParams.multisigUtxoNft),
);
const validateFrom = slotToUnixTime(network, context.emulator.slot - 1);
const validateTo =
validateFrom + Number(sysParams.cdpParams.biasTime) - 60_000;
const interestAmt = match(cdpDatum.cdpFees)
.with({ FrozenCDPAccumulatedFees: P.any }, () => {
throw new Error('CDP fees wrong');
})
.with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
return calculateAccruedInterest(
currentTime,
interest.unitaryInterestSnapshot,
cdpDatum.mintedAmt,
interest.lastSettled,
interestOracleDatum,
);
})
.exhaustive();
const mintedAmountChange = debtAdjustment + interestAmt;
const tx = context.lucid
.newTx()
.validFrom(validateFrom)
.validTo(validateTo)
.collectFrom(
[cdpUtxo],
serialiseCdpRedeemer({
AdjustCdp: {
currentTime: currentTime,
debtAdjustment,
collateralAdjustment,
priceOracleIdx: 'OracleVoid',
},
}),
)
.readFrom([cdpRefScriptUtxo])
.readFrom([iassetUtxo, collateralAssetUtxo, interestOracleUtxo])
.pay.ToContract(
cdpUtxo.address,
{
kind: 'inline',
value: serialiseCdpDatum({
...cdpDatum,
mintedAmt: cdpDatum.mintedAmt + mintedAmountChange,
cdpFees: {
ActiveCDPInterestTracking: {
lastSettled: currentTime,
unitaryInterestSnapshot:
calculateUnitaryInterestSinceOracleLastUpdated(
currentTime,
interestOracleDatum,
) + interestOracleDatum.unitaryInterest,
},
},
}),
},
addAssets(
cdpUtxo.assets,
mkAssetsOf(cdpDatum.collateralAsset, collateralAdjustment),
),
);
if (!cdpDatum.cdpOwner) {
throw new Error('Expected active CDP');
}
tx.addSignerKey(toHex(cdpDatum.cdpOwner));
if (mintedAmountChange !== 0n) {
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const iassetTokensVal = mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
},
mintedAmountChange,
);
tx.readFrom([iAssetTokenPolicyRefScriptUtxo]).mintAssets(
iassetTokensVal,
Data.void(),
);
}
const iAssetAc = {
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
};
if (interestAmt > 0n) {
await collectInterestTx(
mkAssetsOf(iAssetAc, interestAmt),
context.lucid,
sysParams,
tx,
interestCollectorUtxo,
);
}
let treasuryFee = 0n;
if (debtAdjustment > 0n) {
const treasuryUtxo = await findRandomTreasuryUtxoWithOnlyAda(
context.lucid,
sysParams,
);
treasuryFee += calculateFeeFromRatio(
iassetDatum.debtMintingFeeRatio,
debtAdjustment,
);
if (treasuryFee > 0n) {
await treasuryFeeTx(
iAssetAc,
treasuryFee,
0n,
context.lucid,
sysParams,
tx,
cdpUtxo,
treasuryUtxo,
);
}
}
return tx;
}
export async function runTestDepositCdpWithInterestVar(
context: LucidContext,
sysParams: SystemParams,
iasset: string,
collateralAsset: AssetClass,
interestCollectorUtxo: UTxO,
interestVariation: CollectInterestVariation,
amount: bigint = 1_000_000n,
): Promise<TxBuilder> {
const network = context.lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, context.emulator.slot));
const [pkh, skh] = await addrDetails(context.lucid);
const cdp = await findCdp(
context.lucid,
sysParams.validatorHashes.cdpHash,
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
pkh.hash,
skh,
);
const cdpRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
]),
(_) => new Error('Expected a single cdp Ref Script UTXO'),
);
const cdpUtxo = matchSingle(
await context.lucid.utxosByOutRef([cdp.utxo]),
(_) => new Error('Expected a single cdp UTXO'),
);
const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));
const iassetOutput = await findIAsset(
context.lucid,
sysParams.validatorHashes.iassetHash,
fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
iasset,
);
const iassetUtxo = matchSingle(
await context.lucid.utxosByOutRef([iassetOutput.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);
const collateralAssetOutput = await findCollateralAsset(
context.lucid,
sysParams,
fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
iasset,
collateralAsset,
);
const collateralAssetUtxo = matchSingle(
await context.lucid.utxosByOutRef([collateralAssetOutput.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const interestOracleUtxo = await findInterestOracle(
context.lucid,
collateralAssetOutput.datum.interestOracleNft,
);
const interestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(interestOracleUtxo),
);
const validateFrom = slotToUnixTime(network, context.emulator.slot - 1);
const validateTo =
validateFrom + Number(sysParams.cdpParams.biasTime) - 60_000;
const interestAmt = match(cdpDatum.cdpFees)
.with({ FrozenCDPAccumulatedFees: P.any }, () => {
throw new Error('CDP fees wrong');
})
.with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
return calculateAccruedInterest(
currentTime,
interest.unitaryInterestSnapshot,
cdpDatum.mintedAmt,
interest.lastSettled,
interestOracleDatum,
);
})
.exhaustive();
const mintedAmountChange = interestAmt;
const tx = context.lucid
.newTx()
.validFrom(validateFrom)
.validTo(validateTo)
.collectFrom(
[cdpUtxo],
serialiseCdpRedeemer({
AdjustCdp: {
currentTime: currentTime,
debtAdjustment: 0n,
collateralAdjustment: amount,
priceOracleIdx: 'OracleVoid',
},
}),
)
.readFrom([cdpRefScriptUtxo])
.readFrom([iassetUtxo, collateralAssetUtxo, interestOracleUtxo])
.pay.ToContract(
cdpUtxo.address,
{
kind: 'inline',
value: serialiseCdpDatum({
...cdpDatum,
mintedAmt: cdpDatum.mintedAmt + mintedAmountChange,
cdpFees: {
ActiveCDPInterestTracking: {
lastSettled: currentTime,
unitaryInterestSnapshot:
calculateUnitaryInterestSinceOracleLastUpdated(
currentTime,
interestOracleDatum,
) + interestOracleDatum.unitaryInterest,
},
},
}),
},
addAssets(cdpUtxo.assets, mkLovelacesOf(amount)),
);
if (!cdpDatum.cdpOwner) {
throw new Error('Expected active CDP');
}
tx.addSignerKey(toHex(cdpDatum.cdpOwner));
if (mintedAmountChange !== 0n) {
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const iassetTokensVal = mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
},
mintedAmountChange,
);
tx.readFrom([iAssetTokenPolicyRefScriptUtxo]).mintAssets(
iassetTokensVal,
Data.void(),
);
}
if (interestAmt > 0n) {
await testCollectInterest(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
},
interestAmt,
),
context.lucid,
sysParams,
tx,
interestCollectorUtxo,
interestVariation,
);
}
return tx;
}
export async function runCloseCdpWrongOracle(
lucid: LucidEvolution,
currentSlot: number,
sysParams: SystemParams,
iasset: string,
wrongAsset: string,
collateralAsset: AssetClass,
): Promise<TxBuilder> {
const [pkh, skh] = await addrDetails(lucid);
const cdp = await findCdp(
lucid,
sysParams.validatorHashes.cdpHash,
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
pkh.hash,
skh,
);
const orefs = await findAllNecessaryOrefs(
lucid,
sysParams,
iasset,
collateralAsset,
);
const priceOracleOref = await findPriceOracleFromCollateralAsset(
lucid,
orefs.collateralAsset,
);
const network = lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, currentSlot));
const cdpRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
]),
(_) => new Error('Expected a single cdp Ref Script UTXO'),
);
const cdpAuthTokenPolicyRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.authTokenPolicies.cdpAuthTokenRef,
),
]),
(_) => new Error('Expected a single cdp auth token policy Ref Script UTXO'),
);
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const cdpUtxo = matchSingle(
await lucid.utxosByOutRef([cdp.utxo]),
(_) => new Error('Expected a single cdp UTXO'),
);
const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));
const iassetUtxo = matchSingle(
await lucid.utxosByOutRef([orefs.iasset.utxo]),
(_) => new Error('Expected a single iasset UTXO'),
);
const iassetDatum = parseIAssetDatumOrThrow(
getInlineDatumOrThrow(iassetUtxo),
);
const priceOracleUtxo = matchSingle(
await lucid.utxosByOutRef([priceOracleOref!]),
(_) => new Error('Expected a single price oracle UTXO'),
);
const priceOracleDatum = parsePriceOracleDatum(
getInlineDatumOrThrow(priceOracleUtxo),
);
const wrongCollateral = await findCollateralAsset(
lucid,
sysParams,
fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
wrongAsset,
collateralAsset,
);
const wrongInterestOracleUtxo = await findInterestOracle(
lucid,
wrongCollateral.datum.interestOracleNft,
);
const wrongInterestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(wrongInterestOracleUtxo),
);
const txValidity = oracleExpirationAwareValidity(
currentSlot,
Number(sysParams.cdpCreatorParams.biasTime),
Number(priceOracleDatum.expirationTime),
network,
);
const tx = lucid
.newTx()
.readFrom([
cdpRefScriptUtxo,
iAssetTokenPolicyRefScriptUtxo,
cdpAuthTokenPolicyRefScriptUtxo,
])
.readFrom([wrongCollateral.utxo, wrongInterestOracleUtxo])
.validFrom(txValidity.validFrom)
.validTo(txValidity.validTo)
.mintAssets(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: iassetDatum.assetName,
},
-cdpDatum.mintedAmt,
),
Data.void(),
)
.mintAssets(
mkAssetsOf(fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken), -1n),
Data.void(),
)
.collectFrom(
[cdpUtxo],
serialiseCdpRedeemer({ CloseCdp: { currentTime: currentTime } }),
);
if (!cdpDatum.cdpOwner) {
throw new Error('Expected active CDP');
}
tx.addSignerKey(toHex(cdpDatum.cdpOwner));
const interestAmt = match(cdpDatum.cdpFees)
.with({ FrozenCDPAccumulatedFees: P.any }, () => {
throw new Error('CDP fees wrong');
})
.with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
return calculateAccruedInterest(
currentTime,
interest.unitaryInterestSnapshot,
cdpDatum.mintedAmt,
interest.lastSettled,
wrongInterestOracleDatum,
);
})
.exhaustive();
if (interestAmt > 0n) {
await collectInterestTx(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: cdpDatum.iasset,
},
interestAmt,
),
lucid,
sysParams,
tx,
orefs.interestCollectorUtxo,
);
}
return tx;
}
export async function runRedeemCdpWrongOracle(
context: LucidContext,
sysParams: SystemParams,
assetInfo: AssetInfo,
wrongAssetInfo: AssetInfo,
collateralAsset: AssetClass,
pkh: string,
skh: Credential | undefined,
): Promise<TxBuilder> {
const cdp = await findCdp(
context.lucid,
sysParams.validatorHashes.cdpHash,
fromSystemParamsAsset(sysParams.cdpParams.cdpAuthToken),
pkh,
skh,
);
const cdpRedeemRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.cdpRedeemValidatorRef,
),
]),
(_) => new Error('Expected a single cdp redeem Ref Script UTXO'),
);
const orefs = await findAllNecessaryOrefs(
context.lucid,
sysParams,
assetInfo.iassetTokenNameAscii,
collateralAsset,
);
const wrongIasset = await findIAsset(
context.lucid,
sysParams.validatorHashes.iassetHash,
fromSystemParamsAsset(sysParams.cdpParams.iAssetAuthToken),
wrongAssetInfo.iassetTokenNameAscii,
);
const wrongCollateral = await findCollateralAsset(
context.lucid,
sysParams,
fromSystemParamsAsset(sysParams.cdpParams.collateralAssetAuthToken),
wrongAssetInfo.iassetTokenNameAscii,
collateralAsset,
);
const wrongInterestOracleUtxo = await findInterestOracle(
context.lucid,
wrongCollateral.datum.interestOracleNft,
);
const wrongInterestOracleDatum = parseInterestOracleDatum(
getInlineDatumOrThrow(wrongInterestOracleUtxo),
);
const wrongPriceOracleUtxo = await findPriceOracle(
context.lucid,
match(wrongCollateral.datum.priceInfo)
.with({ OracleNft: P.select() }, (oracleNft) => oracleNft)
.otherwise(() => {
throw new Error('Expected active oracle');
}),
);
const wrongPriceOracleDatum = parsePriceOracleDatum(
getInlineDatumOrThrow(wrongPriceOracleUtxo),
);
const network = context.lucid.config().network!;
const currentTime = BigInt(slotToUnixTime(network, context.emulator.slot));
const cdpRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
]),
(_) => new Error('Expected a single cdp Ref Script UTXO'),
);
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await context.lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
const cdpUtxo = matchSingle(
await context.lucid.utxosByOutRef([cdp.utxo]),
(_) => new Error('Expected a single cdp UTXO'),
);
const cdpDatum = parseCdpDatumOrThrow(getInlineDatumOrThrow(cdpUtxo));
const govUtxo = matchSingle(
await context.lucid.utxosByOutRef([orefs.govUtxo]),
(_) => new Error('Expected a single gov UTXO'),
);
const govDatum = parseGovDatumOrThrow(getInlineDatumOrThrow(govUtxo));
const interestAmt = match(cdpDatum.cdpFees)
.with({ FrozenCDPAccumulatedFees: P.any }, () => {
throw new Error('CDP fees wrong');
})
.with({ ActiveCDPInterestTracking: P.select() }, (interest) => {
return calculateAccruedInterest(
currentTime,
interest.unitaryInterestSnapshot,
cdpDatum.mintedAmt,
interest.lastSettled,
wrongInterestOracleDatum,
);
})
.exhaustive();
const collateralAmt = assetClassValueOf(
cdpUtxo.assets,
cdpDatum.collateralAsset,
);
const totalCdpDebt = cdpDatum.mintedAmt + interestAmt;
const [isPartial, redemptionIAssetAmt] = (() => {
const res = calculateMinCollateralCappedIAssetRedemptionAmt(
collateralAmt,
totalCdpDebt,
wrongPriceOracleDatum.price,
wrongCollateral.datum.redemptionRatio,
wrongIasset.datum.redemptionReimbursementRatio,
BigInt(wrongCollateral.datum.minCollateralAmt),
);
const redemptionAmt = bigintMin(
cdp.datum.mintedAmt,
res.cappedIAssetRedemptionAmt,
);
return [redemptionAmt < res.cappedIAssetRedemptionAmt, redemptionAmt];
})();
if (redemptionIAssetAmt <= 0) {
throw new Error("There's no iAssets available for redemption.");
}
const redemptionCollateralAmt = rationalFloor(
rationalMul(
wrongPriceOracleDatum.price,
rationalFromInt(redemptionIAssetAmt),
),
);
const processingFee = calculateFeeFromRatio(
wrongIasset.datum.redemptionProcessingFeeRatio,
redemptionCollateralAmt,
);
const reimburstmentFee = calculateFeeFromRatio(
wrongIasset.datum.redemptionReimbursementRatio,
redemptionCollateralAmt,
);
const txValidity = oracleExpirationAwareValidity(
context.emulator.slot,
Number(sysParams.cdpCreatorParams.biasTime),
Number(wrongPriceOracleDatum.expirationTime),
network,
);
const referenceInputs = [
wrongIasset.utxo,
wrongCollateral.utxo,
wrongPriceOracleUtxo,
wrongInterestOracleUtxo,
govUtxo,
];
const referenceScripts = [
cdpRefScriptUtxo,
iAssetTokenPolicyRefScriptUtxo,
cdpRedeemRefScriptUtxo,
];
const tx = context.lucid.newTx().readFrom(referenceScripts);
const interestCollectorRefScriptUtxo =
interestAmt > 0n
? await collectInterestTx(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: cdpDatum.iasset,
},
interestAmt,
),
context.lucid,
sysParams,
tx,
orefs.interestCollectorUtxo,
)
: undefined;
// We need to take into account the interest collector ref script as well.
const priceOracleIdx = getInputIndices(
[wrongPriceOracleUtxo],
[
...referenceInputs,
...referenceScripts,
...(interestCollectorRefScriptUtxo != null
? [interestCollectorRefScriptUtxo]
: []),
],
)[0];
tx.readFrom(referenceInputs)
// Trigger CDP Redeem Withdrawal validator
.withdraw(
credentialToRewardAddress(
context.lucid.config().network!,
scriptHashToCredential(sysParams.cdpParams.cdpRedeemValHash),
),
0n,
serialiseRedeemCdpWithdrawalRedeemer({
cdpOutReference: {
txHash: fromHex(cdpUtxo.txHash),
outputIndex: BigInt(cdpUtxo.outputIndex),
},
currentTime: currentTime,
priceOracleIdx: { OracleRefInputIdx: priceOracleIdx },
}),
)
.validFrom(txValidity.validFrom)
.validTo(txValidity.validTo)
.collectFrom([cdpUtxo], serialiseCdpRedeemer('RedeemCdp'))
.mintAssets(
mkAssetsOf(
{
currencySymbol: fromHex(
sysParams.cdpParams.cdpAssetSymbol.unCurrencySymbol,
),
tokenName: cdpDatum.iasset,
},
interestAmt - redemptionIAssetAmt,
),
Data.void(),
)
.pay.ToContract(
cdpUtxo.address,
{
kind: 'inline',
value: serialiseCdpDatum({
...cdpDatum,
mintedAmt: totalCdpDebt - redemptionIAssetAmt,
cdpFees: {
ActiveCDPInterestTracking: {
lastSettled: currentTime,
unitaryInterestSnapshot:
wrongInterestOracleDatum.unitaryInterest +
calculateUnitaryInterestSinceOracleLastUpdated(
currentTime,
wrongInterestOracleDatum,
),
},
},
}),
},
addAssets(
cdpUtxo.assets,
mkAssetsOf(
cdpDatum.collateralAsset,
-redemptionCollateralAmt + reimburstmentFee,
),
),
);
const partialRedemptionFee = F.pipe(
govDatum.protocolParams.cdpRedemptionRequiredSignature,
O.fromNullable,
O.match(
// When public redemptions
() => {
return isPartial
? BigInt(sysParams.cdpRedeemParams.partialRedemptionExtraFeeLovelace)
: 0n;
},
// When private redemptions
(requiredSignature) => {
tx.addSignerKey(toHex(requiredSignature));
return 0n;
},
),
);
//TODO: Use a treasury input to save on ADA.
tx.pay.ToContract(
credentialToAddress(context.lucid.config().network!, {
hash: validatorToScriptHash(
mkTreasuryValidatorFromSP(sysParams.treasuryParams),
),
type: 'Script',
}),
{ kind: 'inline', value: Data.void() },
addAssets(
mkAssetsOf(cdpDatum.collateralAsset, processingFee),
mkLovelacesOf(partialRedemptionFee),
),
);
return tx;
}