builder pattern for edit ixs (#387)

* builder pattern for edit ixs

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2023-01-14 14:09:26 +01:00 committed by GitHub
parent 0da1d5d1ba
commit 13dc5c061d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 279 additions and 321 deletions

95
ts/client/src/builder.ts Normal file
View File

@ -0,0 +1,95 @@
// https://github.com/Vincent-Pang/builder-pattern
export type IBuilder<T> = {
[k in keyof T]-?: ((arg: T[k]) => IBuilder<T>) & (() => T[k]);
} & {
build(): T;
};
type Clazz<T> = new (...args: unknown[]) => T;
/**
* Create a Builder for a class. Returned objects will be of the class type.
*
* e.g. let obj: MyClass = Builder(MyClass).setA(5).setB("str").build();
*
* @param type the name of the class to instantiate.
* @param template optional class partial which the builder will derive initial params from.
* @param override optional class partial which the builder will override params from when calling build().
*/
export function Builder<T>(
type: Clazz<T>,
template?: Partial<T> | null,
override?: Partial<T> | null,
): IBuilder<T>;
/**
* Create a Builder for an interface. Returned objects will be untyped.
*
* e.g. let obj: Interface = Builder<Interface>().setA(5).setB("str").build();
*
* @param template optional partial object which the builder will derive initial params from.
* @param override optional partial object which the builder will override params from when calling build().
*/
export function Builder<T>(
template?: Partial<T> | null,
override?: Partial<T> | null,
): IBuilder<T>;
export function Builder<T>(
typeOrTemplate?: Clazz<T> | Partial<T> | null,
templateOrOverride?: Partial<T> | null,
override?: Partial<T> | null,
): IBuilder<T> {
let type: Clazz<T> | undefined;
let template: Partial<T> | null | undefined;
let overrideValues: Partial<T> | null | undefined;
if (typeOrTemplate instanceof Function) {
type = typeOrTemplate;
template = templateOrOverride;
overrideValues = override;
} else {
template = typeOrTemplate;
overrideValues = templateOrOverride;
}
const built: Record<string, unknown> = template
? Object.assign({}, template)
: {};
const builder = new Proxy(
{},
{
get(target, prop) {
if ('build' === prop) {
if (overrideValues) {
Object.assign(built, overrideValues);
}
if (type) {
// A class name (identified by the constructor) was passed. Instantiate it with props.
const obj: T = new type();
return () =>
Object.assign(obj as T & Record<string, unknown>, { ...built });
} else {
// No type information - just return the object.
return () => built;
}
}
return (...args: unknown[]): unknown => {
// If no arguments passed return current value.
if (0 === args.length) {
return built[prop.toString()];
}
built[prop.toString()] = args[0];
return builder;
};
},
},
);
return builder as IBuilder<T>;
}

View File

@ -45,6 +45,7 @@ import {
Serum3Side,
generateSerum3MarketExternalVaultSignerAddress,
} from './accounts/serum3';
import { PerpEditParams, TokenEditParams } from './clientIxParamBuilder';
import { OPENBOOK_PROGRAM_ID } from './constants';
import { Id } from './ids';
import { IDL, MangoV4 } from './mango_v4';
@ -310,64 +311,43 @@ export class MangoClient {
public async tokenEdit(
group: Group,
mintPk: PublicKey,
oracle: PublicKey | null,
oracleConfig: OracleConfigParams | null,
groupInsuranceFund: boolean | null,
interestRateParams: InterestRateParams | null,
loanFeeRate: number | null,
loanOriginationFeeRate: number | null,
maintAssetWeight: number | null,
initAssetWeight: number | null,
maintLiabWeight: number | null,
initLiabWeight: number | null,
liquidationFee: number | null,
stablePriceDelayIntervalSeconds: number | null,
stablePriceDelayGrowthLimit: number | null,
stablePriceGrowthLimit: number | null,
minVaultToDepositsRatio: number | null,
netBorrowLimitPerWindowQuote: number | null,
netBorrowLimitWindowSizeTs: number | null,
borrowWeightScaleStartQuote: number | null,
depositWeightScaleStartQuote: number | null,
resetStablePrice: boolean | null,
resetNetBorrowLimit: boolean | null,
reduceOnly: boolean | null,
params: TokenEditParams,
): Promise<TransactionSignature> {
const bank = group.getFirstBankByMint(mintPk);
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
return await this.program.methods
.tokenEdit(
oracle,
oracleConfig,
groupInsuranceFund,
interestRateParams,
loanFeeRate,
loanOriginationFeeRate,
maintAssetWeight,
initAssetWeight,
maintLiabWeight,
initLiabWeight,
liquidationFee,
stablePriceDelayIntervalSeconds,
stablePriceDelayGrowthLimit,
stablePriceGrowthLimit,
minVaultToDepositsRatio,
netBorrowLimitPerWindowQuote !== null
? new BN(netBorrowLimitPerWindowQuote)
params.oracle,
params.oracleConfig,
params.groupInsuranceFund,
params.interestRateParams,
params.loanFeeRate,
params.loanOriginationFeeRate,
params.maintAssetWeight,
params.initAssetWeight,
params.maintLiabWeight,
params.initLiabWeight,
params.liquidationFee,
params.stablePriceDelayIntervalSeconds,
params.stablePriceDelayGrowthLimit,
params.stablePriceGrowthLimit,
params.minVaultToDepositsRatio,
params.netBorrowLimitPerWindowQuote !== null
? new BN(params.netBorrowLimitPerWindowQuote)
: null,
netBorrowLimitWindowSizeTs !== null
? new BN(netBorrowLimitWindowSizeTs)
params.netBorrowLimitWindowSizeTs !== null
? new BN(params.netBorrowLimitWindowSizeTs)
: null,
borrowWeightScaleStartQuote,
depositWeightScaleStartQuote,
resetStablePrice ?? false,
resetNetBorrowLimit ?? false,
reduceOnly,
params.borrowWeightScaleStartQuote,
params.depositWeightScaleStartQuote,
params.resetStablePrice ?? false,
params.resetNetBorrowLimit ?? false,
params.reduceOnly,
)
.accounts({
group: group.publicKey,
oracle: oracle ?? bank.oracle,
oracle: params.oracle ?? bank.oracle,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
mintInfo: mintInfo.publicKey,
})
@ -1653,67 +1633,43 @@ export class MangoClient {
public async perpEditMarket(
group: Group,
perpMarketIndex: PerpMarketIndex,
oracle: PublicKey | null, // TODO: stable price resetting should be a separate flag
oracleConfig: OracleConfigParams | null,
baseDecimals: number | null,
maintAssetWeight: number | null,
initAssetWeight: number | null,
maintLiabWeight: number | null,
initLiabWeight: number | null,
liquidationFee: number | null,
makerFee: number | null,
takerFee: number | null,
feePenalty: number | null,
minFunding: number | null,
maxFunding: number | null,
impactQuantity: number | null,
groupInsuranceFund: boolean | null,
trustedMarket: boolean | null,
settleFeeFlat: number | null,
settleFeeAmountThreshold: number | null,
settleFeeFractionLowHealth: number | null,
stablePriceDelayIntervalSeconds: number | null,
stablePriceDelayGrowthLimit: number | null,
stablePriceGrowthLimit: number | null,
settlePnlLimitFactor: number | null,
settlePnlLimitWindowSize: number | null,
reduceOnly: boolean | null,
params: PerpEditParams,
): Promise<TransactionSignature> {
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
return await this.program.methods
.perpEditMarket(
oracle,
oracleConfig,
baseDecimals,
maintAssetWeight,
initAssetWeight,
maintLiabWeight,
initLiabWeight,
liquidationFee,
makerFee,
takerFee,
minFunding,
maxFunding,
impactQuantity !== null ? new BN(impactQuantity) : null,
groupInsuranceFund,
trustedMarket,
feePenalty,
settleFeeFlat,
settleFeeAmountThreshold,
settleFeeFractionLowHealth,
stablePriceDelayIntervalSeconds,
stablePriceDelayGrowthLimit,
stablePriceGrowthLimit,
settlePnlLimitFactor,
settlePnlLimitWindowSize !== null
? new BN(settlePnlLimitWindowSize)
params.oracle,
params.oracleConfig,
params.baseDecimals,
params.maintAssetWeight,
params.initAssetWeight,
params.maintLiabWeight,
params.initLiabWeight,
params.liquidationFee,
params.makerFee,
params.takerFee,
params.minFunding,
params.maxFunding,
params.impactQuantity !== null ? new BN(params.impactQuantity) : null,
params.groupInsuranceFund,
params.trustedMarket,
params.feePenalty,
params.settleFeeFlat,
params.settleFeeAmountThreshold,
params.settleFeeFractionLowHealth,
params.stablePriceDelayIntervalSeconds,
params.stablePriceDelayGrowthLimit,
params.stablePriceGrowthLimit,
params.settlePnlLimitFactor,
params.settlePnlLimitWindowSize !== null
? new BN(params.settlePnlLimitWindowSize)
: null,
reduceOnly,
params.reduceOnly,
)
.accounts({
group: group.publicKey,
oracle: oracle ?? perpMarket.oracle,
oracle: params.oracle ?? perpMarket.oracle,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
perpMarket: perpMarket.publicKey,
})

View File

@ -0,0 +1,108 @@
import { PublicKey } from '@solana/web3.js';
import { InterestRateParams, OracleConfigParams } from './types';
export interface TokenEditParams {
oracle: PublicKey | null;
oracleConfig: OracleConfigParams | null;
groupInsuranceFund: boolean | null;
interestRateParams: InterestRateParams | null;
loanFeeRate: number | null;
loanOriginationFeeRate: number | null;
maintAssetWeight: number | null;
initAssetWeight: number | null;
maintLiabWeight: number | null;
initLiabWeight: number | null;
liquidationFee: number | null;
stablePriceDelayIntervalSeconds: number | null;
stablePriceDelayGrowthLimit: number | null;
stablePriceGrowthLimit: number | null;
minVaultToDepositsRatio: number | null;
netBorrowLimitPerWindowQuote: number | null;
netBorrowLimitWindowSizeTs: number | null;
borrowWeightScaleStartQuote: number | null;
depositWeightScaleStartQuote: number | null;
resetStablePrice: boolean | null;
resetNetBorrowLimit: boolean | null;
reduceOnly: boolean | null;
}
export const NullTokenEditParams: TokenEditParams = {
oracle: null,
oracleConfig: null,
groupInsuranceFund: null,
interestRateParams: null,
loanFeeRate: null,
loanOriginationFeeRate: null,
maintAssetWeight: null,
initAssetWeight: null,
maintLiabWeight: null,
initLiabWeight: null,
liquidationFee: null,
stablePriceDelayIntervalSeconds: null,
stablePriceDelayGrowthLimit: null,
stablePriceGrowthLimit: null,
minVaultToDepositsRatio: null,
netBorrowLimitPerWindowQuote: null,
netBorrowLimitWindowSizeTs: null,
borrowWeightScaleStartQuote: null,
depositWeightScaleStartQuote: null,
resetStablePrice: null,
resetNetBorrowLimit: null,
reduceOnly: null,
};
export interface PerpEditParams {
oracle: PublicKey | null;
oracleConfig: OracleConfigParams | null;
baseDecimals: number | null;
maintAssetWeight: number | null;
initAssetWeight: number | null;
maintLiabWeight: number | null;
initLiabWeight: number | null;
liquidationFee: number | null;
makerFee: number | null;
takerFee: number | null;
feePenalty: number | null;
minFunding: number | null;
maxFunding: number | null;
impactQuantity: number | null;
groupInsuranceFund: boolean | null;
trustedMarket: boolean | null;
settleFeeFlat: number | null;
settleFeeAmountThreshold: number | null;
settleFeeFractionLowHealth: number | null;
stablePriceDelayIntervalSeconds: number | null;
stablePriceDelayGrowthLimit: number | null;
stablePriceGrowthLimit: number | null;
settlePnlLimitFactor: number | null;
settlePnlLimitWindowSize: number | null;
reduceOnly: boolean | null;
}
export const NullPerpEditParams: PerpEditParams = {
oracle: null,
oracleConfig: null,
baseDecimals: null,
maintAssetWeight: null,
initAssetWeight: null,
maintLiabWeight: null,
initLiabWeight: null,
liquidationFee: null,
makerFee: null,
takerFee: null,
feePenalty: null,
minFunding: null,
maxFunding: null,
impactQuantity: null,
groupInsuranceFund: null,
trustedMarket: null,
settleFeeFlat: null,
settleFeeAmountThreshold: null,
settleFeeFractionLowHealth: null,
stablePriceDelayIntervalSeconds: null,
stablePriceDelayGrowthLimit: null,
stablePriceGrowthLimit: null,
settlePnlLimitFactor: null,
settlePnlLimitWindowSize: null,
reduceOnly: null,
};

View File

@ -22,7 +22,9 @@ import {
Serum3SelfTradeBehavior,
Serum3Side,
} from '../accounts/serum3';
import { Builder } from '../builder';
import { MangoClient } from '../client';
import { NullPerpEditParams } from '../clientIxParamBuilder';
import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../constants';
import { buildVersionedTx, toNative } from '../utils';
@ -481,31 +483,7 @@ async function makePerpMarketReduceOnly() {
await client.perpEditMarket(
group,
perpMarket.perpMarketIndex,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true,
Builder(NullPerpEditParams).reduceOnly(true).build(),
);
}
@ -520,31 +498,7 @@ async function makePerpMarketUntrusted() {
await client.perpEditMarket(
group,
perpMarket.perpMarketIndex,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Builder(NullPerpEditParams).trustedMarket(false).build(),
);
}

View File

@ -1,81 +0,0 @@
import { AnchorProvider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair } from '@solana/web3.js';
import * as dotenv from 'dotenv';
import fs from 'fs';
import { PerpMarket } from '../accounts/perp';
import { MangoClient } from '../client';
import { MANGO_V4_ID } from '../constants';
dotenv.config();
//
// (untested?) script which closes a mango account cleanly, first closes all positions, withdraws all tokens and then closes it
//
async function editPerpMarket(perpMarketName: string) {
const options = AnchorProvider.defaultOptions();
const connection = new Connection(process.env.MB_CLUSTER_URL!, options);
// admin
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.MB_PAYER_KEYPAIR!, 'utf-8')),
),
);
const adminWallet = new Wallet(admin);
const adminProvider = new AnchorProvider(connection, adminWallet, options);
const client = await MangoClient.connect(
adminProvider,
'mainnet-beta',
MANGO_V4_ID['mainnet-beta'],
);
console.log(`Admin ${admin.publicKey.toBase58()}`);
// fetch group
const group = await client.getGroupForCreator(admin.publicKey, 2);
console.log(`Found group ${group.publicKey.toBase58()}`);
const pm: PerpMarket = group.getPerpMarketByName(perpMarketName);
const signature = await client.perpEditMarket(
group,
pm.perpMarketIndex,
pm.oracle,
{
confFilter: pm.oracleConfig.confFilter.toNumber(),
maxStalenessSlots: null,
},
pm.baseDecimals,
pm.maintAssetWeight.toNumber(),
pm.initAssetWeight.toNumber(),
pm.maintLiabWeight.toNumber(),
pm.initLiabWeight.toNumber(),
pm.liquidationFee.toNumber(),
pm.makerFee.toNumber(),
pm.takerFee.toNumber(),
pm.feePenalty,
pm.minFunding.toNumber(),
pm.maxFunding.toNumber(),
// pm.impactQuantity.toNumber(),
1,
pm.groupInsuranceFund,
pm.trustedMarket,
pm.settleFeeFlat,
pm.settleFeeAmountThreshold,
pm.settleFeeFractionLowHealth,
null,
null,
null,
null,
null,
null,
);
console.log('Tx Successful:', signature);
process.exit();
}
async function main() {
await editPerpMarket('BTC-PERP');
}
main();

View File

@ -9,7 +9,12 @@ import {
Serum3SelfTradeBehavior,
Serum3Side,
} from '../accounts/serum3';
import { Builder } from '../builder';
import { MangoClient } from '../client';
import {
NullPerpEditParams,
NullTokenEditParams,
} from '../clientIxParamBuilder';
import { MANGO_V4_ID } from '../constants';
//
@ -102,28 +107,7 @@ async function main() {
await client.tokenEdit(
group,
bank.mint,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
true,
null,
null,
Builder(NullTokenEditParams).resetStablePrice(true).build(),
);
}
async function setPerpPrice(
@ -135,31 +119,7 @@ async function main() {
await client.perpEditMarket(
group,
perpMarket.perpMarketIndex,
perpMarket.oracle,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Builder(NullPerpEditParams).oracle(perpMarket.oracle).build(),
);
}
@ -243,28 +203,11 @@ async function main() {
await client.tokenEdit(
group,
buyMint,
group.getFirstBankByMint(buyMint).oracle,
null,
null,
null,
null,
null,
1.0,
1.0,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Builder(NullTokenEditParams)
.oracle(group.getFirstBankByMint(buyMint).oracle)
.maintAssetWeight(1.0)
.initAssetWeight(1.0)
.build(),
);
try {
// At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have.
@ -286,28 +229,11 @@ async function main() {
await client.tokenEdit(
group,
buyMint,
group.getFirstBankByMint(buyMint).oracle,
null,
null,
null,
null,
null,
0.9,
0.8,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Builder(NullTokenEditParams)
.oracle(group.getFirstBankByMint(buyMint).oracle)
.maintAssetWeight(0.9)
.initAssetWeight(0.8)
.build(),
);
}
}