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

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