Skip to content
Draft
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
55 changes: 42 additions & 13 deletions src/contracts/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../types/system-params';
import { IAssetHelpers, IAssetOutput } from '../helpers/asset-helpers';
import { CollectorContract } from './collector';
import { TreasuryContract } from './treasury';
import { treasuryFeeTx } from './treasury';
import { addrDetails, scriptRef } from '../helpers/lucid-utils';
import {
calculateFeeFromPercentage,
Expand All @@ -43,6 +43,7 @@ import {
calculateUnitaryInterestSinceOracleLastUpdated,
} from '../helpers/interest-oracle';
import { oracleExpirationAwareValidity } from '../helpers/price-oracle-helpers';
import { mkTreasuryAddress, mkTreasuryParamsFromSP } from '../scripts/treasury-validator';

export class CDPContract {
static async openPosition(
Expand Down Expand Up @@ -480,12 +481,26 @@ export class CDPContract {
const interestTreasuryPayment = interestPayment - interestCollectorPayment;

if (interestTreasuryPayment > 0) {
await TreasuryContract.feeTx(
interestTreasuryPayment,
lucid,
params,
tx,
treasuryRef,
const treasuryUtxos = (await lucid.utxosAt(
mkTreasuryAddress(
mkTreasuryParamsFromSP(params.treasuryParams), lucid.config().network!
)
)).filter((utxo) => utxo.datum === Data.void());

const treasuryRef = getRandomElement(treasuryUtxos);
if (!treasuryRef) throw new Error('Unable to find Treasury UTXO');

const treasuryScriptRef: OutRef = {
txHash: params.scriptReferences.treasuryValidatorRef.input.transactionId,
outputIndex: Number(params.scriptReferences.treasuryValidatorRef.input.index),
};
tx.compose(
await treasuryFeeTx(
interestTreasuryPayment,
lucid,
treasuryRef,
treasuryScriptRef,
)
);
}

Expand Down Expand Up @@ -620,12 +635,26 @@ export class CDPContract {
const interestTreasuryPayment = interestPayment - interestCollectorPayment;

if (interestTreasuryPayment > 0) {
await TreasuryContract.feeTx(
interestTreasuryPayment,
lucid,
params,
tx,
treasuryRef,
const treasuryUtxos = (await lucid.utxosAt(
mkTreasuryAddress(
mkTreasuryParamsFromSP(params.treasuryParams), lucid.config().network!
)
)).filter((utxo) => utxo.datum === Data.void());

const treasuryRef = getRandomElement(treasuryUtxos);
if (!treasuryRef) throw new Error('Unable to find Treasury UTXO');

const treasuryScriptRef: OutRef = {
txHash: params.scriptReferences.treasuryValidatorRef.input.transactionId,
outputIndex: Number(params.scriptReferences.treasuryValidatorRef.input.index),
};
tx.compose(
await treasuryFeeTx(
interestTreasuryPayment,
lucid,
treasuryRef,
treasuryScriptRef,
)
);
}

Expand Down
172 changes: 77 additions & 95 deletions src/contracts/treasury.ts
Original file line number Diff line number Diff line change
@@ -1,112 +1,94 @@
import {
Address,
applyParamsToScript,
addAssets,
Constr,
Data,
fromText,
LucidEvolution,
OutRef,
SpendingValidator,
paymentCredentialOf,
TxBuilder,
UTxO,
validatorToAddress,
validatorToScriptHash,
} from '@lucid-evolution/lucid';
import { _treasuryValidator } from '../scripts/treasury-validator';
import {
ScriptReferences,
SystemParams,
TreasuryParams,
} from '../types/system-params';
import { scriptRef } from '../helpers/lucid-utils';
import { getRandomElement } from '../helpers/helpers';
import { serialiseTreasuryRedeemer, TreasuryParams } from '../types/indigo/treasury';
import { mkLovelacesOf } from '../helpers/value-helpers';
import { mkTreasuryAddress } from '../scripts/treasury-validator';

export class TreasuryContract {
static async feeTx(
fee: bigint,
lucid: LucidEvolution,
params: SystemParams,
tx: TxBuilder,
treasuryRef?: OutRef,
): Promise<void> {
const treasuryUtxo: UTxO = treasuryRef
? getRandomElement(await lucid.utxosByOutRef([treasuryRef]))
: getRandomElement(
await lucid.utxosAt(
TreasuryContract.address(params.treasuryParams, lucid),
),
);
export async function treasuryFeeTx(
fee: bigint,
lucid: LucidEvolution,
treasuryRef: OutRef,
treasuryScriptRef: OutRef,
): Promise<TxBuilder> {
const [
treasuryUtxo,
treasuryScriptRefUtxo,
] = await lucid.utxosByOutRef([
treasuryRef,
treasuryScriptRef,
]);

const treasuryScriptRefUtxo = await TreasuryContract.scriptRef(
params.scriptReferences,
lucid,
);
return lucid.newTx().collectFrom([treasuryUtxo], serialiseTreasuryRedeemer('CollectAda'))
.pay.ToContract(
treasuryUtxo.address,
{ kind: 'inline', value: treasuryUtxo.datum || '' },
{
...treasuryUtxo.assets,
lovelace: treasuryUtxo.assets['lovelace'] + fee,
},
)
.readFrom([treasuryScriptRefUtxo]);
}

tx.collectFrom([treasuryUtxo], Data.to(new Constr(4, [])))
.pay.ToContract(
treasuryUtxo.address,
{ kind: 'inline', value: treasuryUtxo.datum || '' },
{
...treasuryUtxo.assets,
lovelace: treasuryUtxo.assets['lovelace'] + fee,
},
)
.readFrom([treasuryScriptRefUtxo]);
}
// TODO: Add a function to prepare a withdrawal from the treasury
// export async function prepareWithdrawal()

// treasury Validator
static validator(params: TreasuryParams): SpendingValidator {
return {
type: 'PlutusV2',
script: applyParamsToScript(_treasuryValidator.cborHex, [
new Constr(0, [
new Constr(0, [
params.upgradeToken[0].unCurrencySymbol,
fromText(params.upgradeToken[1].unTokenName),
]),
new Constr(0, [
params.versionRecordToken[0].unCurrencySymbol,
fromText(params.versionRecordToken[1].unTokenName),
]),
params.treasuryUtxosStakeCredential
? new Constr(0, [
new Constr(0, [
new Constr(1, [
params.treasuryUtxosStakeCredential.contents.contents,
]),
]),
])
: new Constr(1, []),
]),
]),
};
}
export async function treasuryMerge(
treasuryInputs: OutRef[],
lucid: LucidEvolution,
treasuryScriptRef: OutRef,
treasuryParams: TreasuryParams,
): Promise<TxBuilder> {
const [treasuryScriptRefUtxo] = await lucid.utxosByOutRef([treasuryScriptRef]);
const treasuryInputsUtxos = await lucid.utxosByOutRef(treasuryInputs);

static validatorHash(params: TreasuryParams): string {
return validatorToScriptHash(TreasuryContract.validator(params));
}
const totalAssets = treasuryInputsUtxos.reduce((acc, utxo) => {
return addAssets(acc, utxo.assets);
}, mkLovelacesOf(0n));

const treasuryAddress = mkTreasuryAddress(treasuryParams, lucid.config().network!);

static address(params: TreasuryParams, lucid: LucidEvolution): Address {
const network = lucid.config().network;
if (!network) {
throw new Error('Network configuration is undefined');
}
return validatorToAddress(
network,
TreasuryContract.validator(params),
params.treasuryUtxosStakeCredential
? {
type: 'Script',
hash: params.treasuryUtxosStakeCredential.contents.contents,
}
: undefined,
return lucid.newTx()
.collectFrom(treasuryInputsUtxos, serialiseTreasuryRedeemer('Merge'))
.pay.ToContract(
treasuryAddress,
{ kind: 'inline', value: Data.void() },
totalAssets
)
.readFrom([treasuryScriptRefUtxo]);
}

export async function treasurySplit(
treasuryInput: OutRef,
lucid: LucidEvolution,
treasuryScriptRef: OutRef,
treasuryParams: TreasuryParams,
): Promise<TxBuilder> {
const [treasuryScriptRefUtxo] = await lucid.utxosByOutRef([treasuryScriptRef]);
const [treasuryInputsUtxo] = await lucid.utxosByOutRef([treasuryInput]);
const assets = Object.keys(treasuryInputsUtxo.assets);

const treasuryAddress = mkTreasuryAddress(treasuryParams, lucid.config().network!);

const tx = lucid.newTx()
.collectFrom([treasuryInputsUtxo], serialiseTreasuryRedeemer('Split'))
.readFrom([treasuryScriptRefUtxo]);

for (const asset of assets) {
tx.pay.ToContract(
treasuryAddress,
{ kind: 'inline', value: Data.void() },
{ [asset]: treasuryInputsUtxo.assets[asset] }
);
}

static async scriptRef(
params: ScriptReferences,
lucid: LucidEvolution,
): Promise<UTxO> {
return scriptRef(params.treasuryValidatorRef, lucid);
}
}
return tx;
}
11 changes: 11 additions & 0 deletions src/helpers/treasury-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Data, LucidEvolution, UTxO } from "@lucid-evolution/lucid";
import { TreasuryParams } from "../types/indigo/treasury";
import { mkTreasuryAddress } from "../scripts/treasury-validator";

export async function findTreasuryOutputs(
lucid: LucidEvolution,
treasuryParams: TreasuryParams,
): Promise<UTxO[]> {
return (await lucid.utxosAt(mkTreasuryAddress(treasuryParams, lucid.config().network!)))
.filter((utxo) => utxo.datum === Data.void());
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './helpers/asset-helpers';
export * from './helpers/helpers';
export * from './helpers/lucid-utils';
export * from './helpers/time-helpers';
export * from './helpers/treasury-helpers';
export * from './scripts/cdp-creator-validator';
export * from './scripts/cdp-validator';
export * from './scripts/collector-validator';
Expand Down
78 changes: 76 additions & 2 deletions src/scripts/treasury-validator.ts

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions src/types/indigo/treasury.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Data, Datum, Redeemer } from '@lucid-evolution/lucid';
import { AssetClassSchema, StakeCredentialSchema } from '../generic';

export const TreasuryParamsSchema = Data.Object({
upgradeToken: AssetClassSchema,
versionRecordToken: AssetClassSchema,
treasuryUtxosStakeCredential: Data.Nullable(StakeCredentialSchema),
});
export type TreasuryParams = Data.Static<typeof TreasuryParamsSchema>;
export const TreasuryParams = TreasuryParamsSchema as unknown as TreasuryParams;

const TreasuryRedeemerSchema = Data.Enum([
Data.Literal('Withdraw'),
Data.Literal('PrepareWithdraw'),
Data.Literal('Split'),
Data.Literal('Merge'),
Data.Literal('CollectAda'),
Data.Literal('UpgradeVersion'),
]);
export type TreasuryRedeemer = Data.Static<typeof TreasuryRedeemerSchema>;
const TreasuryRedeemer = TreasuryRedeemerSchema as unknown as TreasuryRedeemer;

export function serialiseTreasuryRedeemer(redeemer: TreasuryRedeemer): Redeemer {
return Data.to<TreasuryRedeemer>(redeemer, TreasuryRedeemer);
}

export function serialiseTreasuryDatum(): Datum {
return Data.void();
}

export function castTreasuryParams(params: TreasuryParams): Data {
return Data.castTo(params, TreasuryParams);
}
17 changes: 14 additions & 3 deletions src/types/system-params.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fromText, OutRef, toText } from '@lucid-evolution/lucid';
import { AssetClass, CurrencySymbol, TokenName } from './generic';
import { AssetClass, CredentialD, CredentialSchema, CurrencySymbol, TokenName } from './generic';

/**
* AssetClassSP used in System Params
Expand All @@ -9,7 +9,7 @@ export type AssetClassSP = [CurrencySymbol, TokenName];
export interface SystemParams {
versionRecordParams: VersionRecordParams;
validatorHashes: ValidatorHashes;
treasuryParams: TreasuryParams;
treasuryParams: TreasuryParamsSP;
startTime: StartTime;
stakingParams: StakingParams;
stabilityPoolParams: StabilityPoolParamsSP;
Expand Down Expand Up @@ -57,7 +57,7 @@ export interface PubKeyHash {
export interface VersionRecordParams {
upgradeToken: AssetClassSP;
}
export interface TreasuryParams {
export interface TreasuryParamsSP {
upgradeToken: AssetClassSP;
versionRecordToken: AssetClassSP;
treasuryUtxosStakeCredential?: ScriptCredential;
Expand Down Expand Up @@ -256,6 +256,17 @@ export function fromSystemParamsAsset(asset: AssetClassSP): AssetClass {
};
}

export function fromSystemParamsCredential(credential: ScriptCredential): CredentialD {
if (credential.tag === 'ScriptCredential') {
return {
ScriptCredential: [credential.contents.contents],
};
}
return {
PublicKeyCredential: [credential.contents.contents],
};
}

export function fromSystemParamsScriptRef(ref: ScriptReference): OutRef {
return { outputIndex: ref.input.index, txHash: ref.input.transactionId };
}
Loading