Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIN-1549 MIN-1548: LBE V2 and Expired Order Monitor #44

Merged
merged 43 commits into from
Nov 28, 2024
Merged
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
415d914
config+schema+constant
m1n999999 Oct 24, 2024
2640f6c
wip
m1n999999 Oct 24, 2024
a3b6c16
use custom schema
m1n999999 Oct 25, 2024
e045810
type
m1n999999 Oct 25, 2024
4c4b468
wip
m1n999999 Oct 28, 2024
ab163cb
finish deposit/withdraw order
m1n999999 Oct 29, 2024
f4d48fe
close event and add more seller
m1n999999 Oct 29, 2024
3260eb8
add type ProjectDetails+remove Dummy type
m1n999999 Oct 29, 2024
7cfdd85
create event example
m1n999999 Oct 30, 2024
8236737
lbe v2 adapter
m1n999999 Oct 30, 2024
59bca26
close lbe v2
m1n999999 Oct 30, 2024
e16020b
fix deposit
m1n999999 Oct 30, 2024
60bdb2c
example for deposit and withdraw
m1n999999 Oct 31, 2024
2ac00de
example for cancel event
m1n999999 Oct 31, 2024
ac55fec
example+wip of counting seller
m1n999999 Oct 31, 2024
bd4da1b
wip
m1n999999 Nov 5, 2024
375f37e
update pnpm-lock
m1n999999 Nov 5, 2024
abc0e5c
counting seller
m1n999999 Nov 6, 2024
6d42c86
collect manager
m1n999999 Nov 6, 2024
6df1fbe
Merge branch 'main' into lbe-v2
m1n999999 Nov 6, 2024
eef0461
collect order
m1n999999 Nov 6, 2024
8dc57b1
redeem orders
m1n999999 Nov 7, 2024
923feae
refund
m1n999999 Nov 7, 2024
460fa4e
validate create amm
m1n999999 Nov 7, 2024
8183a3d
create amm pool
m1n999999 Nov 8, 2024
924c4f6
wip
m1n999999 Nov 11, 2024
9ce8417
wip 1
m1n999999 Nov 12, 2024
2b01f37
finish implement worker
m1n999999 Nov 12, 2024
d67ba87
fix build
m1n999999 Nov 12, 2024
030673d
fix worker
m1n999999 Nov 13, 2024
a39b946
seperate lbe-v2 file
m1n999999 Nov 19, 2024
c211411
fix worker
m1n999999 Nov 21, 2024
b22a0c8
udpate order and example for it
m1n999999 Nov 22, 2024
501287b
remove redudant
m1n999999 Nov 22, 2024
4259587
update mainnet config
m1n999999 Nov 22, 2024
6f6a941
new version of lucid
m1n999999 Nov 25, 2024
322945b
lbe v2 specification
m1n999999 Nov 26, 2024
a4861df
move compareUTxO to calculate file
m1n999999 Nov 26, 2024
76664d4
update docs
m1n999999 Nov 26, 2024
b8b730b
remove personal info
m1n999999 Nov 26, 2024
cab955b
add check field length action in fromPlutusData+add dcos
m1n999999 Nov 26, 2024
829b973
docs
m1n999999 Nov 26, 2024
6898dfd
MIN-1548: Auto cancel expired orders worker (#47)
m1n999999 Nov 27, 2024
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
Prev Previous commit
Next Next commit
type
m1n999999 committed Oct 25, 2024
commit e045810b0b4ff627309172342258d562220ac684
281 changes: 281 additions & 0 deletions src/lbe-v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import invariant from "@minswap/tiny-invariant";
import {
Address,
Assets,
Constr,
Data,
Lucid,
TxComplete,
UTxO,
} from "lucid-cardano";

import {
FIXED_DEPOSIT_ADA,
LbeV2Constant,
MAX_POOL_V2_TRADING_FEE_NUMERATOR,
MetadataMessage,
MIN_POOL_V2_TRADING_FEE_NUMERATOR,
PoolV2,
StableOrder,
StableswapConstant,
} from ".";
import { calculateBatcherFee } from "./batcher-fee-reduction/calculate";
import { Asset } from "./types/asset";
import { LbeV2Types } from "./types/lbe-v2";
import { NetworkEnvironment, NetworkId } from "./types/network";
import { lucidToNetworkEnv } from "./utils/network.internal";
import { buildUtxoToStoreDatum } from "./utils/tx.internal";

export type CreateLbeV2EventOptions = {
networkEnv: NetworkEnvironment;
factoryUtxo: UTxO;
lbeV2Parameters: LbeV2Types.LbeV2Parameters;
currentSlot: number;
sellerOwner: Address;
sellerCount?: number;
projectDetails?: string[];
};

export class LbeV2 {
private readonly lucid: Lucid;
private readonly networkId: NetworkId;
private readonly networkEnv: NetworkEnvironment;

constructor(lucid: Lucid) {
this.lucid = lucid;
this.networkId =
lucid.network === "Mainnet" ? NetworkId.MAINNET : NetworkId.TESTNET;
this.networkEnv = lucidToNetworkEnv(lucid.network);
}

// MARK: Create Event
private validateCreateEvent(options: CreateLbeV2EventOptions): void {
const { lbeV2Parameters, currentSlot, factoryUtxo } = options;
const currentTime = this.lucid.utils.slotToUnixTime(currentSlot);
const { baseAsset, raiseAsset } = lbeV2Parameters;
const datum = factoryUtxo.datum;
invariant(datum, "Factory utxo must have inline datum");
const factory = LbeV2Types.FactoryDatum.fromPlutusData(Data.from(datum));
const config = LbeV2Constant.CONFIG[this.networkId];
invariant(
config.factoryAsset in factoryUtxo.assets,
"Factory utxo assets must have factory asset"
);
const lbeV2Id = PoolV2.computeLPAssetName(baseAsset, raiseAsset);
invariant(
factory.head < lbeV2Id && lbeV2Id < factory.tail,
"LBE ID name must be between factory head and tail"
);
this.validateLbeV2Parameters(lbeV2Parameters, currentTime);
}

validateLbeV2Parameters(
params: LbeV2Types.LbeV2Parameters,
currentTime: number
): void {
const {
poolBaseFee,
penaltyConfig,
reserveBase,
minimumRaise,
maximumRaise,
minimumOrderRaise,
poolAllocation,
startTime,
endTime,
baseAsset,
raiseAsset,
} = params;
invariant(
Asset.toString(baseAsset) !== Asset.toString(raiseAsset),
"Base Asset, Raise Asset must be different"
);
invariant(
Asset.toString(baseAsset) !== "lovelace",
"Base Asset must not equal ADA"
);
invariant(startTime >= BigInt(currentTime), "LBE must start in future");
invariant(startTime < endTime, "StartTime < EndTime");
invariant(
endTime - startTime <= LbeV2Constant.MAX_DISCOVERY_RANGE,
"Discovery Phase must in a month"
);
invariant(
poolAllocation >= LbeV2Constant.MIN_POOL_ALLOCATION_POINT,
`Pool Allocation must greater than ${LbeV2Constant.MIN_POOL_ALLOCATION_POINT}`
);
invariant(
poolAllocation <= LbeV2Constant.MAX_POOL_ALLOCATION_POINT,
`Pool Allocation must less than ${LbeV2Constant.MAX_POOL_ALLOCATION_POINT}`
);
if (minimumOrderRaise) {
invariant(minimumOrderRaise > 0n, "Minimum Order > 0");
}
if (maximumRaise) {
invariant(maximumRaise > 0n, "Maximum Raise > 0");
}
if (minimumRaise) {
invariant(minimumRaise > 0n, "Minimum Raise > 0");
if (maximumRaise !== undefined) {
invariant(minimumRaise < maximumRaise, "Minimum Raise < Maximum Raise");
}
}
invariant(reserveBase > 0n, "Reserve Base > 0");
if (penaltyConfig) {
const { penaltyStartTime, percent } = penaltyConfig;
invariant(
penaltyStartTime > startTime,
"Penalty Start Time > Start Time"
);
invariant(penaltyStartTime < endTime, "Penalty Start Time < End Time");
invariant(
penaltyStartTime >= endTime - LbeV2Constant.MAX_PENALTY_RANGE,
"Maximum penalty period of 2 final days"
);
invariant(percent > 0n, "Penalty Percent > 0");
invariant(
percent <= LbeV2Constant.MAX_PENALTY_RATE,
`Penalty Percent <= ${LbeV2Constant.MAX_PENALTY_RATE}`
);
}
const poolBaseFeeMin = MIN_POOL_V2_TRADING_FEE_NUMERATOR;
const poolBaseFeeMax = MAX_POOL_V2_TRADING_FEE_NUMERATOR;
invariant(
poolBaseFee >= poolBaseFeeMin && poolBaseFee <= poolBaseFeeMax,
`Pool Base Fee must in range ${poolBaseFeeMin} - ${poolBaseFeeMax}`
);
}

async createTreasury(options: CreateLbeV2EventOptions): Promise<TxComplete> {
const {
lbeV2Parameters,
factoryUtxo,
sellerOwner,
projectDetails,
networkEnv,
} = options;
const sellerCount: number =
options.sellerCount ?? Number(LbeV2Constant.DEFAULT_SELLER_COUNT);
this.validateCreateEvent(options);
const factory = Result.unwrap(LbeV2Factory.fromUtxo(factoryUtxo));

Check failure on line 160 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'Result'.

Check failure on line 160 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2Factory'.
const warehouse = LbeV2Warehouse.getInstance(networkEnv);

Check failure on line 161 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2Warehouse'.
const factoryRedeemer = LbeV2FactoryRedeemer.toPlutusJson({

Check failure on line 162 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2FactoryRedeemer'. Did you mean 'factoryRedeemer'?
type: LbeV2FactoryRedeemerType.CREATE_TREASURY,

Check failure on line 163 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2FactoryRedeemerType'.
baseAsset: lbeV2Parameters.baseAsset,
raiseAsset: lbeV2Parameters.raiseAsset,
});
const wrapperFactoryRedeemer =
RedeemerWrapper.wrapRedeemer(factoryRedeemer);

Check failure on line 168 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'RedeemerWrapper'.

const treasuryValue = warehouse
.getDefaultTreasuryValue()
.add(lbeV2Parameters.baseAsset, lbeV2Parameters.reserveBase);

const treasuryDatum: LbeV2TreasuryDatum =

Check failure on line 174 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2TreasuryDatum'.
LbeV2TreasuryDatum.fromLbeV2Parameters(lbeV2Parameters);

Check failure on line 175 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2TreasuryDatum'. Did you mean 'treasuryDatum'?
const treasuryOutput = new TxOut(

Check failure on line 176 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'TxOut'.
warehouse.getTreasuryAddress(),
treasuryValue,
LbeV2TreasuryDatum.toDatumSource(treasuryDatum)

Check failure on line 179 in src/lbe-v2.ts

GitHub Actions / build-and-test

Cannot find name 'LbeV2TreasuryDatum'. Did you mean 'treasuryDatum'?
);
const lbeV2Id = PoolV2.computeLPAssetName(
lbeV2Parameters.baseAsset,
lbeV2Parameters.raiseAsset
);
const factoryHeadOutput = new TxOut(
warehouse.getFactoryAddress(),
warehouse.getDefaultFactoryValue(),
LbeV2FactoryDatum.toDatumSource({
head: factory.head,
tail: lbeV2Id,
})
);

const factoryTailOutput = new TxOut(
warehouse.getFactoryAddress(),
warehouse.getDefaultFactoryValue(),
LbeV2FactoryDatum.toDatumSource({
head: lbeV2Id,
tail: factory.tail,
})
);

const managerOutput = new TxOut(
warehouse.getManagerAddress(),
warehouse.getDefaultManagerValue(),
LbeV2ManagerDatum.toDatumSource({
factoryPolicyId: warehouse.getFactoryHash(),
baseAsset: lbeV2Parameters.baseAsset,
raiseAsset: lbeV2Parameters.raiseAsset,
sellerCount: BigInt(sellerCount),
reserveRaise: 0n,
totalPenalty: 0n,
})
);

const sellerOutputs: TxOut[] = [];
for (let i = 0; i < sellerCount; i++) {
const txOut = new TxOut(
warehouse.getSellerAddress(),
warehouse.getDefaultSellerValue(),
LbeV2SellerDatum.toDatumSource({
factoryPolicyId: warehouse.getFactoryHash(),
owner: sellerOwner,
baseAsset: lbeV2Parameters.baseAsset,
raiseAsset: lbeV2Parameters.raiseAsset,
amount: 0n,
penaltyAmount: 0n,
})
);
sellerOutputs.push(txOut);
}

const mintValue = new Value()
.add(warehouse.getFactoryAsset(), 1n)
.add(warehouse.getTreasuryAsset(), 1n)
.add(warehouse.getManagerAsset(), 1n)
.add(warehouse.getSellerAsset(), BigInt(sellerCount));

const validTo = Math.min(
Number(lbeV2Parameters.startTime) - 1,
Date.now() + Duration.newHours(3).milliseconds
);

const txb: TxBuilderV2 = new TxBuilderV2(networkEnv);
txb
.readFrom(warehouse.getFactoryRefInput())
.collectFromPlutusContract([factoryUtxo], wrapperFactoryRedeemer)
.payTo(factoryHeadOutput)
.payTo(factoryTailOutput)
.payTo(treasuryOutput)
.payTo(managerOutput)
.payTo(...sellerOutputs)
.mintAssets(mintValue, factoryRedeemer)
.validToUnixTime(validTo)
.addMessageMetadata("msg", [MetadataMessage.LBE_V2_CREATE_EVENT])
.addMessageMetadata("extraData", projectDetails ?? []);

if (Maybe.isJust(treasuryDatum.owner.toPubKeyHash())) {
txb.addSigner(treasuryDatum.owner);
}

if (sponsorship) {
const sellerFees = new Value();
for (let i = 0; i < sellerCount; i++) {
sellerFees.add(ADA, warehouse.getDefaultSellerValue().get(ADA));
}
const sResult = Sponsorship.validate({
sponsorship,
requiredValue: sellerFees,
});
if (sResult.type === "ok") {
txb.collectFromPubKey(...sponsorship.utxos);
txb.payTo(...sponsorship.changeOutputs);
} else {
throw sResult.error;
}
}

return txb;
}
}
77 changes: 77 additions & 0 deletions src/types/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Constr, Data } from "lucid-cardano";

export namespace Options {
export function toPlutusData<T>(
data: T | undefined,
toPlutusDataFn: (data: T) => Data
): Constr<Data> {
return data !== undefined
? new Constr(0, [toPlutusDataFn(data)])
: new Constr(1, []);
}

export function fromPlutusData<T>(
data: Constr<Data>,
fromPlutusDataFn: (data: Constr<Data>) => T
): T | undefined {
switch (data.index) {
case 0: {
return fromPlutusDataFn(data.fields[0] as Constr<Data>);
}
case 1: {
return undefined;
}
default: {
throw Error(`Index of Options must be 0 or 1, actual: ${data.index}`);
}
}
}
}

export namespace Dummy {
export function toPlutusData(x: Data): Data {
return x;
}
export function fromPlutusData<T>(x: Data): T {
return x as T;
}
}

export namespace Bool {
export function toPlutusData(data: boolean): Constr<Data> {
return data ? new Constr(1, []) : new Constr(0, []);
}

export function fromPlutusData(data: Constr<Data>): boolean {
switch (data.index) {
case 0: {
return false;
}
case 1: {
return true;
}
default: {
throw Error(`Index of Bool must be 0 or 1, actual: ${data.index}`);
}
}
}
}

export namespace RedeemerWrapper {
export function toPlutusData(d: Data): Data {
return new Constr(1, [d]);
}

export function fromPlutusData(data: Constr<Data>): Data {
switch (data.index) {
case 1: {
return data.fields[0];
}
default: {
throw Error(
`Index of RedeemerWrapper must be 1, actual: ${data.index}`
);
}
}
}
}
6 changes: 4 additions & 2 deletions src/types/constants.ts
Original file line number Diff line number Diff line change
@@ -579,8 +579,10 @@ export namespace LbeV2Constant {
export const MINIMUM_ORDER_COLLECTED = 30n;
export const MINIMUM_ORDER_REDEEMED = 30n;

export const MAX_DISCOVERY_RANGE = "30D in ms";
export const MAX_PENALTY_RANGE = "2D in ms";
export const MAX_DISCOVERY_RANGE = 2592000000n;
export const MAX_PENALTY_RANGE = 172800000n;

export const DEFAULT_SELLER_COUNT = 20n;

export type Config = {
factoryAsset: string;
194 changes: 137 additions & 57 deletions src/types/lbe-v2.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import { Address, Constr, Data } from "lucid-cardano";

import { Asset, LbeV2Constant, NetworkId } from "..";
import { AddressPlutusData } from "./address.internal";
import { Bool, Dummy, Options } from "./common";
export namespace LbeV2Types {
export enum ReceiverDatumType {
NO_DATUM = 0,
@@ -118,63 +119,6 @@ export namespace LbeV2Types {
isManagerCollected: boolean;
};

export namespace Options {
export function toPlutusData<T>(
data: T | undefined,
toPlutusDataFn: (data: T) => Data
): Constr<Data> {
return data !== undefined
? new Constr(0, [toPlutusDataFn(data)])
: new Constr(1, []);
}

export function fromPlutusData<T>(
data: Constr<Data>,
fromPlutusDataFn: (data: Constr<Data>) => T
): T | undefined {
switch (data.index) {
case 0: {
return fromPlutusDataFn(data.fields[0] as Constr<Data>);
}
case 1: {
return undefined;
}
default: {
throw Error(`Index of Options must be 0 or 1, actual: ${data.index}`);
}
}
}
}

export namespace Dummy {
export function toPlutusData(x: Data): Data {
return x;
}
export function fromPlutusData<T>(x: Data): T {
return x as T;
}
}

export namespace Bool {
export function toPlutusData(data: boolean): Constr<Data> {
return data ? new Constr(1, []) : new Constr(0, []);
}

export function fromPlutusData(data: Constr<Data>): boolean {
switch (data.index) {
case 0: {
return false;
}
case 1: {
return true;
}
default: {
throw Error(`Index of Bool must be 0 or 1, actual: ${data.index}`);
}
}
}
}

export namespace TreasuryDatum {
export function toPlutusData(datum: TreasuryDatum): Constr<Data> {
const {
@@ -304,6 +248,55 @@ export namespace LbeV2Types {
}
}

export enum TreasuryRedeemerType {
COLLECT_MANAGER = 0,
COLLECT_ORDERS = 1,
CREATE_AMM_POOL = 2,
REDEEM_ORDERS = 3,
CLOSE_EVENT = 4,
CANCEL_LBE = 5,
UPDATE_LBE = 6,
}

export enum CancelReason {
CREATED_POOL = 0,
BY_OWNER = 1,
NOT_REACH_MINIMUM = 2,
}

export type TreasuryRedeemer =
| {
type:
| TreasuryRedeemerType.COLLECT_MANAGER
| TreasuryRedeemerType.COLLECT_ORDERS
| TreasuryRedeemerType.CREATE_AMM_POOL
| TreasuryRedeemerType.REDEEM_ORDERS
| TreasuryRedeemerType.CLOSE_EVENT
| TreasuryRedeemerType.UPDATE_LBE;
}
| {
type: TreasuryRedeemerType.CANCEL_LBE;
reason: CancelReason;
};

export namespace TreasuryRedeemer {
export function toPlutusData(data: TreasuryRedeemer): Constr<Data> {
switch (data.type) {
case TreasuryRedeemerType.COLLECT_MANAGER:
case TreasuryRedeemerType.COLLECT_ORDERS:
case TreasuryRedeemerType.CREATE_AMM_POOL:
case TreasuryRedeemerType.REDEEM_ORDERS:
case TreasuryRedeemerType.CLOSE_EVENT:
case TreasuryRedeemerType.UPDATE_LBE: {
return new Constr(data.type, []);
}
case TreasuryRedeemerType.CANCEL_LBE: {
return new Constr(data.type, [new Constr(data.reason, [])]);
}
}
}
}

export type LbeV2Parameters = {
baseAsset: Asset;
reserveBase: bigint;
@@ -388,6 +381,59 @@ export namespace LbeV2Types {
}
}
}
export enum FactoryRedeemerType {
INITIALIZATION = 0,
CREATE_TREASURY = 1,
CLOSE_TREASURY = 2,
MINT_MANAGER = 3,
MINT_SELLER = 4,
BURN_SELLER = 5,
MINT_ORDER = 6,
MINT_REDEEM_ORDERS = 7,
MANAGE_ORDER = 8,
}

export type FactoryRedeemer =
| {
type:
| FactoryRedeemerType.INITIALIZATION
| FactoryRedeemerType.MINT_MANAGER
| FactoryRedeemerType.MINT_SELLER
| FactoryRedeemerType.BURN_SELLER
| FactoryRedeemerType.MINT_ORDER
| FactoryRedeemerType.MINT_REDEEM_ORDERS
| FactoryRedeemerType.MANAGE_ORDER;
}
| {
type:
| FactoryRedeemerType.CREATE_TREASURY
| FactoryRedeemerType.CLOSE_TREASURY;
baseAsset: Asset;
raiseAsset: Asset;
};

export namespace FactoryRedeemer {
export function toPlutusData(data: FactoryRedeemer): Constr<Data> {
switch (data.type) {
case FactoryRedeemerType.INITIALIZATION:
case FactoryRedeemerType.MINT_MANAGER:
case FactoryRedeemerType.MINT_SELLER:
case FactoryRedeemerType.BURN_SELLER:
case FactoryRedeemerType.MINT_ORDER:
case FactoryRedeemerType.MINT_REDEEM_ORDERS:
case FactoryRedeemerType.MANAGE_ORDER: {
return new Constr(data.type, []);
}
case FactoryRedeemerType.CREATE_TREASURY:
case FactoryRedeemerType.CLOSE_TREASURY: {
return new Constr(data.type, [
Asset.toPlutusData(data.baseAsset),
Asset.toPlutusData(data.raiseAsset),
]);
}
}
}
}

export type ManagerDatum = {
factoryPolicyId: string;
@@ -430,6 +476,17 @@ export namespace LbeV2Types {
}
}

export enum ManagerRedeemer {
ADD_SELLERS = 0,
COLLECT_SELLERS = 1,
SPEND_MANAGER = 2,
}
export namespace ManagerRedeemer {
export function toPlutusData(data: ManagerRedeemer): Constr<Data> {
return new Constr(data, []);
}
}

export type SellerDatum = {
factoryPolicyId: string;
owner: Address;
@@ -477,6 +534,17 @@ export namespace LbeV2Types {
}
}

export enum SellerRedeemer {
USING_SELLER = 0,
COUNTING_SELLERS = 1,
}

export namespace SellerRedeemer {
export function toPlutusData(data: SellerRedeemer): Constr<Data> {
return new Constr(data, []);
}
}

export type OrderDatum = {
factoryPolicyId: string;
baseAsset: Asset;
@@ -526,4 +594,16 @@ export namespace LbeV2Types {
}
}
}

export enum OrderRedeemer {
UPDATE_ORDER = 0,
COLLECT_ORDER = 1,
REDEEM_ORDER = 2,
}

export namespace OrderRedeemer {
export function toPlutusData(data: OrderRedeemer): Constr<Data> {
return new Constr(data, []);
}
}
}