@indigo-labs/indigo-sdk
Version:
Indigo SDK for interacting with Indigo endpoints via lucid-evolution
349 lines (310 loc) • 9.88 kB
text/typescript
import {
addAssets,
Data,
fromHex,
LucidEvolution,
OutRef,
sortUTxOs,
toHex,
TxBuilder,
UTxO,
} from '@lucid-evolution/lucid';
import {
parseStableswapOrderDatumOrThrow,
serialiseStableswapOrderRedeemer,
StableswapOrderDatum,
} from '../../src/contracts/stableswap/types-new';
import {
fromSystemParamsScriptRef,
SystemParams,
treasuryFeeTx,
} from '../../src';
import {
addressToBech32,
assetClassToUnit,
getInlineDatumOrThrow,
lovelacesAmt,
matchSingle,
mkAssetsOf,
mkLovelacesOf,
} from '@3rd-eye-labs/cardano-offchain-common';
import { isEmpty } from 'fp-ts/lib/Array';
import { array as A, function as F } from 'fp-ts';
import { calculateFeeFromRatio } from '../../src/utils/indigo-helpers';
import {
parseStableswapPoolDatumOrThrow,
serialiseCdpRedeemer,
serialiseStableswapPoolDatum,
} from '../../src/contracts/cdp/types-new';
import { createDestinationDatum } from '../../src/contracts/stableswap/helpers';
import {
rationalDiv,
rationalFloor,
rationalFromInt,
rationalMul,
} from '../../src/types/rational';
type StableswapOrderInfo = {
utxo: UTxO;
datum: StableswapOrderDatum;
suppliedIasset: bigint;
suppliedCollateralAsset: bigint;
};
export type MutatedBatchProcessStableswapOrdersType = {
type: 'exceed-max-execution-fee';
maxExecutionFee: bigint;
};
export async function mutatedBatchProcessStableswapOrders(
stableswapOrderOrefs: OutRef[],
stableswapPoolOref: OutRef,
treasuryOref: OutRef,
sysParams: SystemParams,
lucid: LucidEvolution,
type: MutatedBatchProcessStableswapOrdersType,
): Promise<TxBuilder> {
const stableswapScriptRefUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.stableswapValidatorRef,
),
]),
(_) => new Error('Expected a single Stableswap Ref Script UTXO'),
);
const cdpScriptRefUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(sysParams.scriptReferences.cdpValidatorRef),
]),
(_) => new Error('Expected a single CDP Ref Script UTXO'),
);
const iAssetTokenPolicyRefScriptUtxo = matchSingle(
await lucid.utxosByOutRef([
fromSystemParamsScriptRef(
sysParams.scriptReferences.iAssetTokenPolicyRef,
),
]),
(_) => new Error('Expected a single iasset token policy Ref Script UTXO'),
);
if (isEmpty(stableswapOrderOrefs)) {
throw new Error('At least one order must be provided.');
}
const stableswapOrderUtxos = await lucid.utxosByOutRef(stableswapOrderOrefs);
const sortedStableswapOrderUtxos = sortUTxOs(
stableswapOrderUtxos,
'Canonical',
);
if (sortedStableswapOrderUtxos.length !== stableswapOrderOrefs.length) {
throw new Error('Expected certain number of orders');
}
const mainOrderUtxo = sortedStableswapOrderUtxos[0];
const mainOrderDatum = parseStableswapOrderDatumOrThrow(
getInlineDatumOrThrow(mainOrderUtxo),
);
const iassetAc = {
currencySymbol: fromHex(
sysParams.stableswapParams.iassetSymbol.unCurrencySymbol,
),
tokenName: mainOrderDatum.iasset,
};
const collateralAc = mainOrderDatum.collateralAsset;
const ordersInfo: StableswapOrderInfo[] = sortedStableswapOrderUtxos.map(
(orderUtxo) => {
const orderDatum = parseStableswapOrderDatumOrThrow(
getInlineDatumOrThrow(orderUtxo),
);
if (
toHex(orderDatum.iasset) != toHex(mainOrderDatum.iasset) ||
toHex(orderDatum.collateralAsset.currencySymbol) !=
toHex(mainOrderDatum.collateralAsset.currencySymbol) ||
toHex(orderDatum.collateralAsset.tokenName) !=
toHex(mainOrderDatum.collateralAsset.tokenName)
) {
throw new Error('Wrong batch of orders');
}
const suppliedIasset = orderUtxo.assets[assetClassToUnit(iassetAc)] ?? 0n;
const suppliedCollateralAsset =
orderUtxo.assets[assetClassToUnit(collateralAc)] ?? 0n;
if (
(suppliedIasset != 0n && suppliedCollateralAsset != 0n) ||
(suppliedIasset == 0n && suppliedCollateralAsset == 0n)
) {
throw new Error(
'An order must supply either iAsset or collateral asset',
);
}
return {
utxo: orderUtxo,
datum: orderDatum,
suppliedIasset: suppliedIasset,
suppliedCollateralAsset: suppliedCollateralAsset,
};
},
);
const totalSuppliedAssets = F.pipe(
ordersInfo,
A.reduce<StableswapOrderInfo, [bigint, bigint]>(
[0n, 0n],
(acc, orderInfo) => [
acc[0] + orderInfo.suppliedIasset,
acc[1] + orderInfo.suppliedCollateralAsset,
],
),
);
const totalSuppliedIasset = totalSuppliedAssets[0];
const totalSuppliedCollateralAsset = totalSuppliedAssets[1];
const stableswapPoolUtxo = matchSingle(
await lucid.utxosByOutRef([stableswapPoolOref]),
(_) => new Error('Expected a single cdp UTXO'),
);
const stableswapPoolDatum = parseStableswapPoolDatumOrThrow(
getInlineDatumOrThrow(stableswapPoolUtxo),
);
const redemptionFee = calculateFeeFromRatio(
stableswapPoolDatum.redemptionFeeRatio,
totalSuppliedIasset,
);
const totalEffectiveSuppliedIasset = totalSuppliedIasset - redemptionFee;
const collateralAmtChangePool =
totalSuppliedCollateralAsset -
rationalFloor(
rationalMul(
rationalFromInt(totalEffectiveSuppliedIasset),
stableswapPoolDatum.collateralToIassetRatio,
),
);
const amountToMint =
rationalFloor(
rationalDiv(
rationalFromInt(totalSuppliedCollateralAsset),
stableswapPoolDatum.collateralToIassetRatio,
),
) - totalEffectiveSuppliedIasset;
const mintingFee = calculateFeeFromRatio(
stableswapPoolDatum.mintingFeeRatio,
amountToMint,
);
const tx = lucid
.newTx()
.readFrom([
stableswapScriptRefUtxo,
cdpScriptRefUtxo,
iAssetTokenPolicyRefScriptUtxo,
])
.collectFrom([stableswapPoolUtxo], {
kind: 'selected',
makeRedeemer: (inputIndices) =>
serialiseCdpRedeemer({
Stableswap: {
forwardingInputIndex: inputIndices[0],
},
}),
inputs: [mainOrderUtxo],
})
.pay.ToContract(
stableswapPoolUtxo.address,
{
kind: 'inline',
value: serialiseStableswapPoolDatum(stableswapPoolDatum),
},
collateralAmtChangePool != 0n
? addAssets(
stableswapPoolUtxo.assets,
mkAssetsOf(collateralAc, collateralAmtChangePool),
)
: stableswapPoolUtxo.assets,
);
const fee = mintingFee + redemptionFee;
if (fee > 0) {
await treasuryFeeTx(
iassetAc,
fee,
0n,
lucid,
sysParams,
tx,
stableswapPoolOref,
treasuryOref,
);
}
if (amountToMint != 0n) {
tx.mintAssets(mkAssetsOf(iassetAc, amountToMint), Data.void());
}
F.pipe(
ordersInfo,
A.reduce<StableswapOrderInfo, TxBuilder>(tx, (acc, orderInfo) => {
const iassetSupplied =
orderInfo.utxo.assets[assetClassToUnit(iassetAc)] ?? 0n;
const collateralAssetSupplied =
orderInfo.utxo.assets[assetClassToUnit(collateralAc)] ?? 0n;
const isMinting = iassetSupplied > 0n ? false : true;
const iAssetEquivalentSupplied = isMinting
? rationalFloor(
rationalDiv(
rationalFromInt(collateralAssetSupplied),
stableswapPoolDatum.collateralToIassetRatio,
),
)
: iassetSupplied;
const effectiveiAssetEquivalentSupplied = isMinting
? iAssetEquivalentSupplied -
calculateFeeFromRatio(
stableswapPoolDatum.mintingFeeRatio,
iAssetEquivalentSupplied,
)
: iAssetEquivalentSupplied -
calculateFeeFromRatio(
stableswapPoolDatum.redemptionFeeRatio,
iAssetEquivalentSupplied,
);
const amountToReceive = isMinting
? effectiveiAssetEquivalentSupplied
: rationalFloor(
rationalMul(
rationalFromInt(effectiveiAssetEquivalentSupplied),
stableswapPoolDatum.collateralToIassetRatio,
),
);
const maxExecutionFee =
type.type === 'exceed-max-execution-fee'
? type.maxExecutionFee
: orderInfo.datum.maxExecutionFee;
return acc
.collectFrom(
[orderInfo.utxo],
orderInfo.utxo == mainOrderUtxo
? serialiseStableswapOrderRedeemer('BatchProcessStableswapOrders')
: {
kind: 'selected',
makeRedeemer: (inputIndices: bigint[]) => {
return serialiseStableswapOrderRedeemer({
BatchAuxiliary: {
ownInputIndex: inputIndices[0],
mainOrderInputIndex: inputIndices[1],
},
});
},
inputs: [orderInfo.utxo, mainOrderUtxo],
},
)
.pay.ToAddressWithData(
addressToBech32(orderInfo.datum.destination, lucid.config().network!),
{
kind: 'inline',
value: createDestinationDatum(
orderInfo.datum.destinationInlineDatum ?? null,
orderInfo.utxo,
),
},
addAssets(
// Currently, we always take the max execution fee from the order utxo.
// This can be improved so that we take the actual execution fee.
mkLovelacesOf(
lovelacesAmt(orderInfo.utxo.assets) - maxExecutionFee,
),
isMinting
? mkAssetsOf(iassetAc, amountToReceive)
: mkAssetsOf(collateralAc, amountToReceive),
),
);
}),
);
return tx;
}