From 953595cebc4f659a7f8e9c6dddb8b1e37c063f62 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 13 Mar 2023 11:10:51 +0100 Subject: [PATCH 01/48] Fix script Signed-off-by: microwavedcola1 --- ts/client/scripts/mb-create-gov-ix.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/client/scripts/mb-create-gov-ix.ts b/ts/client/scripts/mb-create-gov-ix.ts index 95f6f7401..e2e6ac8af 100644 --- a/ts/client/scripts/mb-create-gov-ix.ts +++ b/ts/client/scripts/mb-create-gov-ix.ts @@ -144,6 +144,7 @@ async function tokenEdit(): Promise { params.resetStablePrice ?? false, params.resetNetBorrowLimit ?? false, params.reduceOnly, + params.name, ) .accounts({ group: group.publicKey, From 29f41c275ae7def6ab28af3b492b35bf61fe2e8e Mon Sep 17 00:00:00 2001 From: riordanp Date: Tue, 14 Mar 2023 15:07:52 +0000 Subject: [PATCH 02/48] Fix docs build job (#501) --- .github/workflows/ci-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index f7e4981e5..9fe426da2 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -30,7 +30,7 @@ jobs: run: yarn typedoc ts/client/src/index.ts --readme none --out ./docs/ts/client/ - name: Build Rust Docs - run: cargo doc --no-deps --package client --package mango-v4 --target-dir ./docs/rs/ && rm -rf ./docs/rs/debug + run: cargo doc --no-deps --package mango-v4-client --package mango-v4 --target-dir ./docs/rs/ && rm -rf ./docs/rs/debug - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@4.1.5 From 00e467b2c025de81cfb631e9960e7d40acd655c2 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Tue, 14 Mar 2023 17:38:02 +0100 Subject: [PATCH 03/48] workaround where rpc rejects base58 encoded pubkeys (#502) Signed-off-by: microwavedcola1 --- lib/client/src/gpa.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/client/src/gpa.rs b/lib/client/src/gpa.rs index db5fece0b..5dbd1106c 100644 --- a/lib/client/src/gpa.rs +++ b/lib/client/src/gpa.rs @@ -16,12 +16,12 @@ pub async fn fetch_mango_accounts( ) -> anyhow::Result> { let config = RpcProgramAccountsConfig { filters: Some(vec![ - RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 0, - &MangoAccount::discriminator(), + MangoAccount::discriminator().to_vec(), )), - RpcFilterType::Memcmp(Memcmp::new_base58_encoded(8, &group.to_bytes())), - RpcFilterType::Memcmp(Memcmp::new_base58_encoded(40, &owner.to_bytes())), + RpcFilterType::Memcmp(Memcmp::new_raw_bytes(8, group.to_bytes().to_vec())), + RpcFilterType::Memcmp(Memcmp::new_raw_bytes(40, owner.to_bytes().to_vec())), ]), account_config: RpcAccountInfoConfig { encoding: Some(UiAccountEncoding::Base64), @@ -50,7 +50,7 @@ async fn fetch_anchor_accounts( filters: Vec, ) -> anyhow::Result> { let account_type_filter = - RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, &T::discriminator())); + RpcFilterType::Memcmp(Memcmp::new_raw_bytes(0, T::discriminator().to_vec())); let config = RpcProgramAccountsConfig { filters: Some([vec![account_type_filter], filters].concat()), account_config: RpcAccountInfoConfig { @@ -74,9 +74,9 @@ pub async fn fetch_banks( fetch_anchor_accounts::( rpc, program, - vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 8, - &group.to_bytes(), + group.to_bytes().to_vec(), ))], ) .await @@ -90,9 +90,9 @@ pub async fn fetch_mint_infos( fetch_anchor_accounts::( rpc, program, - vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 8, - &group.to_bytes(), + group.to_bytes().to_vec(), ))], ) .await @@ -106,9 +106,9 @@ pub async fn fetch_serum3_markets( fetch_anchor_accounts::( rpc, program, - vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 8, - &group.to_bytes(), + group.to_bytes().to_vec(), ))], ) .await @@ -122,9 +122,9 @@ pub async fn fetch_perp_markets( fetch_anchor_accounts::( rpc, program, - vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + vec![RpcFilterType::Memcmp(Memcmp::new_raw_bytes( 8, - &group.to_bytes(), + group.to_bytes().to_vec(), ))], ) .await From fc1341f731b82f021303582cc5d1aa65ea5b5813 Mon Sep 17 00:00:00 2001 From: riordanp Date: Tue, 14 Mar 2023 18:10:24 +0000 Subject: [PATCH 04/48] Rename settle-bot to settler, fix build (#492) * Fix settler build, rename due to heroku not liking '-' * Temporarily remove ref tag * Remove temporary branch trigger --- .github/workflows/ci-docker-publish.yml | 14 +++++--------- Dockerfile | 3 ++- bin/{settle-bot => settler}/.env.example | 0 bin/{settle-bot => settler}/.gitignore | 0 bin/{settle-bot => settler}/Cargo.toml | 4 ++-- .../Dockerfile.settler} | 2 +- bin/{settle-bot => settler}/src/main.rs | 0 bin/{settle-bot => settler}/src/metrics.rs | 0 bin/{settle-bot => settler}/src/settle.rs | 0 bin/{settle-bot => settler}/src/util.rs | 0 10 files changed, 10 insertions(+), 13 deletions(-) rename bin/{settle-bot => settler}/.env.example (100%) rename bin/{settle-bot => settler}/.gitignore (100%) rename bin/{settle-bot => settler}/Cargo.toml (96%) rename bin/{settle-bot/Dockerfile.settle-bot => settler/Dockerfile.settler} (92%) rename bin/{settle-bot => settler}/src/main.rs (100%) rename bin/{settle-bot => settler}/src/metrics.rs (100%) rename bin/{settle-bot => settler}/src/settle.rs (100%) rename bin/{settle-bot => settler}/src/util.rs (100%) diff --git a/.github/workflows/ci-docker-publish.yml b/.github/workflows/ci-docker-publish.yml index d830db933..d1e2172b1 100644 --- a/.github/workflows/ci-docker-publish.yml +++ b/.github/workflows/ci-docker-publish.yml @@ -10,7 +10,7 @@ on: 'lib/client/**', 'bin/keeper/**', 'bin/liquidator/**', - 'bin/settle-bot/**', + 'bin/settler/**', ] workflow_call: secrets: @@ -65,7 +65,6 @@ jobs: push: true tags: | us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}:${{ github.sha }} - us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}:${{ github.ref_name }} us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}:latest cache-from: type=gha cache-to: type=gha,mode=max @@ -78,19 +77,17 @@ jobs: push: true tags: | us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-liquidator:${{ github.sha }} - us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-liquidator:${{ github.ref_name }} us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-liquidator:latest - # Build and push the settle-bot runtime image + # Build and push the settler runtime image - name: Build and Push Settle Bot uses: docker/build-push-action@v2 with: - file: bin/settle-bot/Dockerfile.settle-bot + file: bin/settler/Dockerfile.settler context: . push: true tags: | - us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-settle-bot:${{ github.sha }} - us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-settle-bot:${{ github.ref_name }} - us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-settle-bot:latest + us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-settler:${{ github.sha }} + us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-settler:latest # Build and push the keeper runtime image - name: Build and Push Keeper uses: docker/build-push-action@v2 @@ -100,5 +97,4 @@ jobs: push: true tags: | us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-keeper:${{ github.sha }} - us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-keeper:${{ github.ref_name }} us-docker.pkg.dev/${{ env.PROJECT_ID }}/gcr.io/${{ env.IMAGE }}-keeper:latest diff --git a/Dockerfile b/Dockerfile index d448da10a..813c8ba04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,11 +20,12 @@ COPY --from=plan /app/recipe-*.json . COPY . . # RUN cargo chef cook --release --recipe-path recipe-keeper.json --bin keeper # RUN cargo chef cook --release --recipe-path recipe-liquidator.json --bin liquidator -RUN cargo build --release --bin keeper --bin liquidator +RUN cargo build --release --bin keeper --bin liquidator --bin settler FROM debian:bullseye-slim as run RUN apt-get update && apt-get -y install ca-certificates libc6 COPY --from=build /app/target/release/keeper /usr/local/bin/ COPY --from=build /app/target/release/liquidator /usr/local/bin/ +COPY --from=build /app/target/release/settler /usr/local/bin/ RUN adduser --system --group --no-create-home mangouser USER mangouser diff --git a/bin/settle-bot/.env.example b/bin/settler/.env.example similarity index 100% rename from bin/settle-bot/.env.example rename to bin/settler/.env.example diff --git a/bin/settle-bot/.gitignore b/bin/settler/.gitignore similarity index 100% rename from bin/settle-bot/.gitignore rename to bin/settler/.gitignore diff --git a/bin/settle-bot/Cargo.toml b/bin/settler/Cargo.toml similarity index 96% rename from bin/settle-bot/Cargo.toml rename to bin/settler/Cargo.toml index f7abd5715..3ef221d02 100644 --- a/bin/settle-bot/Cargo.toml +++ b/bin/settler/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "mango-v4-settle-bot" +name = "mango-v4-settler" version = "0.0.1" edition = "2021" [[bin]] -name = "settle-bot" +name = "settler" path = "src/main.rs" [dependencies] diff --git a/bin/settle-bot/Dockerfile.settle-bot b/bin/settler/Dockerfile.settler similarity index 92% rename from bin/settle-bot/Dockerfile.settle-bot rename to bin/settler/Dockerfile.settler index 2e915be4a..3a7a34854 100644 --- a/bin/settle-bot/Dockerfile.settle-bot +++ b/bin/settler/Dockerfile.settler @@ -4,4 +4,4 @@ ARG BASE_TAG=latest FROM us-docker.pkg.dev/mango-markets/gcr.io/mango-v4:$BASE_TAG USER mangouser -CMD ["liquidator"] +CMD ["settler"] diff --git a/bin/settle-bot/src/main.rs b/bin/settler/src/main.rs similarity index 100% rename from bin/settle-bot/src/main.rs rename to bin/settler/src/main.rs diff --git a/bin/settle-bot/src/metrics.rs b/bin/settler/src/metrics.rs similarity index 100% rename from bin/settle-bot/src/metrics.rs rename to bin/settler/src/metrics.rs diff --git a/bin/settle-bot/src/settle.rs b/bin/settler/src/settle.rs similarity index 100% rename from bin/settle-bot/src/settle.rs rename to bin/settler/src/settle.rs diff --git a/bin/settle-bot/src/util.rs b/bin/settler/src/util.rs similarity index 100% rename from bin/settle-bot/src/util.rs rename to bin/settler/src/util.rs From da473b00b1fdc7e6c6a2886decdaecf7a3877a34 Mon Sep 17 00:00:00 2001 From: riordanp Date: Wed, 15 Mar 2023 13:44:51 +0000 Subject: [PATCH 05/48] Add oracleProvider to Bank and PerpMarket (#491) * Add oracleProvider to Bank and PerpMarket --- ts/client/src/accounts/bank.ts | 12 ++++++++++++ ts/client/src/accounts/group.ts | 21 ++++++++++++++++----- ts/client/src/accounts/oracle.ts | 6 ++++++ ts/client/src/accounts/perp.ts | 12 ++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index 120cbc541..9dd09ec8b 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -3,6 +3,7 @@ import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'; import { PublicKey } from '@solana/web3.js'; import { I80F48, I80F48Dto, ZERO_I80F48 } from '../numbers/I80F48'; import { As, toUiDecimals } from '../utils'; +import { OracleProvider } from './oracle'; export type TokenIndex = number & As<'token-index'>; @@ -58,6 +59,7 @@ export class Bank implements BankForHealth { public _price: I80F48 | undefined; public _uiPrice: number | undefined; public _oracleLastUpdatedSlot: number | undefined; + public _oracleProvider: OracleProvider | undefined; public collectedFeesNative: I80F48; public loanFeeRate: I80F48; public loanOriginationFeeRate: I80F48; @@ -235,6 +237,7 @@ export class Bank implements BankForHealth { this._price = undefined; this._uiPrice = undefined; this._oracleLastUpdatedSlot = undefined; + this._oracleProvider = undefined; } toString(): string { @@ -360,6 +363,15 @@ export class Bank implements BankForHealth { return this._oracleLastUpdatedSlot; } + get oracleProvider(): OracleProvider { + if (!this._oracleProvider) { + throw new Error( + `Undefined oracleProvider for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`, + ); + } + return this._oracleProvider; + } + nativeDeposits(): I80F48 { return this.indexedDeposits.mul(this.depositIndex); } diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index 827b2a42b..6bb57c8e0 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -19,6 +19,7 @@ import { Bank, MintInfo, TokenIndex } from './bank'; import { isPythOracle, isSwitchboardOracle, + OracleProvider, parseSwitchboardOracle, } from './oracle'; import { BookSide, PerpMarket, PerpMarketIndex } from './perp'; @@ -331,7 +332,7 @@ export class Group { throw new Error( `Undefined accountInfo object in reloadBankOraclePrices for ${bank.oracle}!`, ); - const { price, uiPrice, lastUpdatedSlot } = + const { price, uiPrice, lastUpdatedSlot, provider } = await this.decodePriceFromOracleAi( coder, bank.oracle, @@ -342,6 +343,7 @@ export class Group { bank._price = price; bank._uiPrice = uiPrice; bank._oracleLastUpdatedSlot = lastUpdatedSlot; + bank._oracleProvider = provider; } } } @@ -366,7 +368,7 @@ export class Group { `Undefined ai object in reloadPerpMarketOraclePrices for ${perpMarket.oracle}!`, ); - const { price, uiPrice, lastUpdatedSlot } = + const { price, uiPrice, lastUpdatedSlot, provider } = await this.decodePriceFromOracleAi( coder, perpMarket.oracle, @@ -377,6 +379,7 @@ export class Group { perpMarket._price = price; perpMarket._uiPrice = uiPrice; perpMarket._oracleLastUpdatedSlot = lastUpdatedSlot; + perpMarket._oracleProvider = provider; }), ); } @@ -387,8 +390,13 @@ export class Group { ai: AccountInfo, baseDecimals: number, client: MangoClient, - ): Promise<{ price: I80F48; uiPrice: number; lastUpdatedSlot: number }> { - let price, uiPrice, lastUpdatedSlot; + ): Promise<{ + price: I80F48; + uiPrice: number; + lastUpdatedSlot: number; + provider: OracleProvider; + }> { + let price, uiPrice, lastUpdatedSlot, provider; if ( !BorshAccountsCoder.accountDiscriminator('stubOracle').compare( ai.data.slice(0, 8), @@ -398,11 +406,13 @@ export class Group { price = new I80F48(stubOracle.price.val); uiPrice = this.toUiPrice(price, baseDecimals); lastUpdatedSlot = stubOracle.lastUpdated.val; + provider = OracleProvider.Stub; } else if (isPythOracle(ai)) { const priceData = parsePriceData(ai.data); uiPrice = priceData.previousPrice; price = this.toNativePrice(uiPrice, baseDecimals); lastUpdatedSlot = parseInt(priceData.lastSlot.toString()); + provider = OracleProvider.Pyth; } else if (isSwitchboardOracle(ai)) { const priceData = await parseSwitchboardOracle( ai, @@ -411,12 +421,13 @@ export class Group { uiPrice = priceData.price; price = this.toNativePrice(uiPrice, baseDecimals); lastUpdatedSlot = priceData.lastUpdatedSlot; + provider = OracleProvider.Switchboard; } else { throw new Error( `Unknown oracle provider (parsing not implemented) for oracle ${oracle}, with owner ${ai.owner}!`, ); } - return { price, uiPrice, lastUpdatedSlot }; + return { price, uiPrice, lastUpdatedSlot, provider }; } public async reloadVaults(client: MangoClient): Promise { diff --git a/ts/client/src/accounts/oracle.ts b/ts/client/src/accounts/oracle.ts index 0db7ed24e..431e4fd13 100644 --- a/ts/client/src/accounts/oracle.ts +++ b/ts/client/src/accounts/oracle.ts @@ -13,6 +13,12 @@ const SBV1_MAINNET_PID = new PublicKey( let sbv2DevnetProgram; let sbv2MainnetProgram; +export enum OracleProvider { + Pyth, + Switchboard, + Stub, +} + export class StubOracle { public price: I80F48; public lastUpdated: BN; diff --git a/ts/client/src/accounts/perp.ts b/ts/client/src/accounts/perp.ts index 3ddf74043..a0aec6891 100644 --- a/ts/client/src/accounts/perp.ts +++ b/ts/client/src/accounts/perp.ts @@ -21,6 +21,7 @@ import { } from './bank'; import { Group } from './group'; import { MangoAccount } from './mangoAccount'; +import { OracleProvider } from './oracle'; export type PerpMarketIndex = number & As<'perp-market-index'>; @@ -55,6 +56,8 @@ export class PerpMarket { public _price: I80F48; public _uiPrice: number; public _oracleLastUpdatedSlot: number; + public _oracleProvider: OracleProvider; + public _bids: BookSide; public _asks: BookSide; @@ -265,6 +268,15 @@ export class PerpMarket { return this._oracleLastUpdatedSlot; } + get oracleProvider(): OracleProvider { + if (!this._oracleProvider) { + throw new Error( + `Undefined oracleProvider for perpMarket ${this.publicKey} with marketIndex ${this.perpMarketIndex}!`, + ); + } + return this._oracleProvider; + } + get minOrderSize(): number { return this.baseLotsToUiConverter; } From 1f1f04a40cda1e587c031da85a8ed2eba27ca208 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Wed, 15 Mar 2023 14:57:12 +0100 Subject: [PATCH 06/48] v0.9.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5167bdee5..da39ac5f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.9.5", + "version": "0.9.6", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From a7ee8fb2c001275b15dec0ab978a73e3e9253023 Mon Sep 17 00:00:00 2001 From: riordanp Date: Thu, 16 Mar 2023 09:10:52 +0000 Subject: [PATCH 07/48] Fix null checks on getters for PerpMarket and Bank (#505) * Export OracleProvider * Fix null checks on getters --- ts/client/src/accounts/bank.ts | 8 ++++---- ts/client/src/accounts/perp.ts | 8 ++++---- ts/client/src/index.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index 9dd09ec8b..bd0029996 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -337,7 +337,7 @@ export class Bank implements BankForHealth { } get price(): I80F48 { - if (!this._price) { + if (this._price === undefined) { throw new Error( `Undefined price for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`, ); @@ -346,7 +346,7 @@ export class Bank implements BankForHealth { } get uiPrice(): number { - if (!this._uiPrice) { + if (this._uiPrice === undefined) { throw new Error( `Undefined uiPrice for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`, ); @@ -355,7 +355,7 @@ export class Bank implements BankForHealth { } get oracleLastUpdatedSlot(): number { - if (!this._oracleLastUpdatedSlot) { + if (this._oracleLastUpdatedSlot === undefined) { throw new Error( `Undefined oracleLastUpdatedSlot for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`, ); @@ -364,7 +364,7 @@ export class Bank implements BankForHealth { } get oracleProvider(): OracleProvider { - if (!this._oracleProvider) { + if (this._oracleProvider === undefined) { throw new Error( `Undefined oracleProvider for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`, ); diff --git a/ts/client/src/accounts/perp.ts b/ts/client/src/accounts/perp.ts index a0aec6891..fee65b3c0 100644 --- a/ts/client/src/accounts/perp.ts +++ b/ts/client/src/accounts/perp.ts @@ -242,7 +242,7 @@ export class PerpMarket { } get price(): I80F48 { - if (!this._price) { + if (this._price === undefined) { throw new Error( `Undefined price for perpMarket ${this.publicKey} with marketIndex ${this.perpMarketIndex}!`, ); @@ -251,7 +251,7 @@ export class PerpMarket { } get uiPrice(): number { - if (!this._uiPrice) { + if (this._uiPrice === undefined) { throw new Error( `Undefined price for perpMarket ${this.publicKey} with marketIndex ${this.perpMarketIndex}!`, ); @@ -260,7 +260,7 @@ export class PerpMarket { } get oracleLastUpdatedSlot(): number { - if (!this._oracleLastUpdatedSlot) { + if (this._oracleLastUpdatedSlot === undefined) { throw new Error( `Undefined oracleLastUpdatedSlot for perpMarket ${this.publicKey} with marketIndex ${this.perpMarketIndex}!`, ); @@ -269,7 +269,7 @@ export class PerpMarket { } get oracleProvider(): OracleProvider { - if (!this._oracleProvider) { + if (this._oracleProvider === undefined) { throw new Error( `Undefined oracleProvider for perpMarket ${this.publicKey} with marketIndex ${this.perpMarketIndex}!`, ); diff --git a/ts/client/src/index.ts b/ts/client/src/index.ts index e3576f6f1..5b8994b84 100644 --- a/ts/client/src/index.ts +++ b/ts/client/src/index.ts @@ -1,5 +1,5 @@ import { Group } from './accounts/group'; -import { StubOracle } from './accounts/oracle'; +import { OracleProvider, StubOracle } from './accounts/oracle'; import { MangoClient } from './client'; import { MANGO_V4_ID } from './constants'; @@ -22,4 +22,4 @@ export * from './constants'; export * from './numbers/I80F48'; export * from './utils'; export * from './types'; -export { Group, StubOracle, MangoClient, MANGO_V4_ID }; +export { Group, OracleProvider, StubOracle, MangoClient, MANGO_V4_ID }; From b6bfb018797f5cb4f0c4c614be954bcf8f9f50a8 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Thu, 16 Mar 2023 11:23:45 +0100 Subject: [PATCH 08/48] token_liq_bankruptcy: Use oracle for valuing insurance fund tokens (#503) Previously a token from the insurance fund was valued at 1 USD. Now it uses the oracle associated with it (USDC oracle). --- .../src/instructions/perp_create_market.rs | 2 +- .../src/instructions/token_liq_bankruptcy.rs | 36 ++++++++++--------- .../src/instructions/token_register.rs | 2 +- programs/mango-v4/src/state/group.rs | 17 +++++++++ .../tests/cases/test_bankrupt_tokens.rs | 9 +++-- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/programs/mango-v4/src/instructions/perp_create_market.rs b/programs/mango-v4/src/instructions/perp_create_market.rs index 1165e4e57..a103b8700 100644 --- a/programs/mango-v4/src/instructions/perp_create_market.rs +++ b/programs/mango-v4/src/instructions/perp_create_market.rs @@ -47,7 +47,7 @@ pub fn perp_create_market( // - In perp bankruptcy: fix the assumption that the insurance fund has the same mint as // the settlement token. require_msg!( - settle_token_index == QUOTE_TOKEN_INDEX, + settle_token_index == PERP_SETTLE_TOKEN_INDEX, "settlement tokens != USDC are not fully implemented" ); diff --git a/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs b/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs index 5aafb7924..2c86c11bf 100644 --- a/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs @@ -50,8 +50,11 @@ pub fn token_liq_bankruptcy( liqee_health_cache.require_after_phase2_liquidation()?; liqee.fixed.set_being_liquidated(true); + let liab_is_insurance_token = liab_token_index == INSURANCE_TOKEN_INDEX; let (liab_bank, liab_oracle_price, opt_quote_bank_and_price) = - account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?; + account_retriever.banks_mut_and_oracles(liab_token_index, INSURANCE_TOKEN_INDEX)?; + assert!(liab_is_insurance_token == opt_quote_bank_and_price.is_none()); + let mut liab_deposit_index = liab_bank.deposit_index; let liab_borrow_index = liab_bank.borrow_index; let (liqee_liab, liqee_raw_token_index) = liqee.token_position_mut(liab_token_index)?; @@ -59,13 +62,14 @@ pub fn token_liq_bankruptcy( let mut remaining_liab_loss = -initial_liab_native; require_gt!(remaining_liab_loss, I80F48::ZERO); - // find insurance transfer amount - let liab_fee_factor = if liab_token_index == QUOTE_TOKEN_INDEX { - I80F48::ONE - } else { - I80F48::ONE + liab_bank.liquidation_fee - }; - let liab_price_adjusted = liab_oracle_price * liab_fee_factor; + // We pay for the liab token in quote. Example: SOL is at $20 and USDC is at $2, then for a liab + // of 3 SOL, we'd pay 3 * 20 / 2 * (1+fee) = 30 * (1+fee) USDC. + let liab_to_quote_with_fee = + if let Some((_quote_bank, quote_price)) = opt_quote_bank_and_price.as_ref() { + liab_oracle_price * (I80F48::ONE + liab_bank.liquidation_fee) / quote_price + } else { + I80F48::ONE + }; let liab_transfer_unrounded = remaining_liab_loss.min(max_liab_transfer); @@ -75,7 +79,7 @@ pub fn token_liq_bankruptcy( 0 }; - let insurance_transfer = (liab_transfer_unrounded * liab_price_adjusted) + let insurance_transfer = (liab_transfer_unrounded * liab_to_quote_with_fee) .ceil() .to_num::() .min(insurance_vault_amount); @@ -87,7 +91,7 @@ pub fn token_liq_bankruptcy( // AUDIT: v3 does this, but it seems bad, because it can make liab_transfer // exceed max_liab_transfer due to the ceil() above! Otoh, not doing it would allow // liquidators to exploit the insurance fund for 1 native token each call. - let liab_transfer = insurance_transfer_i80f48 / liab_price_adjusted; + let liab_transfer = insurance_transfer_i80f48 / liab_to_quote_with_fee; let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); @@ -116,7 +120,7 @@ pub fn token_liq_bankruptcy( // credit the liqor let (liqor_quote, liqor_quote_raw_token_index, _) = - liqor.ensure_token_position(QUOTE_TOKEN_INDEX)?; + liqor.ensure_token_position(INSURANCE_TOKEN_INDEX)?; let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48, now_ts)?; @@ -124,7 +128,7 @@ pub fn token_liq_bankruptcy( emit!(TokenBalanceLog { mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.liqor.key(), - token_index: QUOTE_TOKEN_INDEX, + token_index: INSURANCE_TOKEN_INDEX, indexed_position: liqor_quote.indexed_position.to_bits(), deposit_index: quote_deposit_index.to_bits(), borrow_index: quote_borrow_index.to_bits(), @@ -180,12 +184,12 @@ pub fn token_liq_bankruptcy( ); } } else { - // For liab_token_index == QUOTE_TOKEN_INDEX: the insurance fund deposits directly into liqee, + // For liab_token_index == INSURANCE_TOKEN_INDEX: the insurance fund deposits directly into liqee, // without a fee or the liqor being involved // account constraint #2 b) require_keys_eq!(liab_bank.vault, ctx.accounts.quote_vault.key()); - require_eq!(liab_token_index, QUOTE_TOKEN_INDEX); - require_eq!(liab_price_adjusted, I80F48::ONE); + require_eq!(liab_token_index, INSURANCE_TOKEN_INDEX); + require_eq!(liab_to_quote_with_fee, I80F48::ONE); require_eq!(insurance_transfer_i80f48, liab_transfer); } } @@ -265,7 +269,7 @@ pub fn token_liq_bankruptcy( liab_token_index, initial_liab_native: initial_liab_native.to_bits(), liab_price: liab_oracle_price.to_bits(), - insurance_token_index: QUOTE_TOKEN_INDEX, + insurance_token_index: INSURANCE_TOKEN_INDEX, insurance_transfer: insurance_transfer_i80f48.to_bits(), socialized_loss: socialized_loss.to_bits(), starting_liab_deposit_index: starting_deposit_index.to_bits(), diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index 48a168d3e..21dde659e 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -31,7 +31,7 @@ pub fn token_register( net_borrow_limit_per_window_quote: i64, ) -> Result<()> { // Require token 0 to be in the insurance token - if token_index == QUOTE_TOKEN_INDEX { + if token_index == INSURANCE_TOKEN_INDEX { require_keys_eq!( ctx.accounts.group.load()?.insurance_mint, ctx.accounts.mint.key() diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index 55b5ac627..e9359807f 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -4,8 +4,25 @@ use std::mem::size_of; // TODO: Assuming we allow up to 65536 different tokens pub type TokenIndex = u16; + +/// This token index is supposed to be the token that oracles quote in. +/// +/// In practice this is set to the USDC token index, and that is wrong: actually +/// oracles quote in USD. Any use of this constant points to a potentially +/// incorrect assumption. pub const QUOTE_TOKEN_INDEX: TokenIndex = 0; +/// The token index used for the insurance fund. +/// +/// We should eventually generalize insurance funds. +pub const INSURANCE_TOKEN_INDEX: TokenIndex = 0; + +/// The token index used for settling perp markets. +/// +/// We should eventually generalize to make the whole perp quote (and settle) token +/// configurable. +pub const PERP_SETTLE_TOKEN_INDEX: TokenIndex = 0; + #[account(zero_copy(safe_bytemuck_derives))] #[derive(Debug)] pub struct Group { diff --git a/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs b/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs index b5e0ff2d0..215c73113 100644 --- a/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs +++ b/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs @@ -519,6 +519,10 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> { // TEST: use the insurance fund to liquidate borrow1 and borrow2 // + // Change value of token that the insurance fund is in, to check that bankruptcy amounts + // are correct if it depegs + set_bank_stub_oracle_price(solana, group, borrow_token1, admin, 2.0).await; + // bankruptcy of an USDC liability: just transfers funds from insurance vault to liqee, // the liqor is uninvolved let insurance_vault_before = solana.token_account_balance(insurance_vault).await; @@ -553,7 +557,8 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> { let liab_before = account_position_f64(solana, account, borrow_token2.bank).await; let insurance_vault_before = solana.token_account_balance(insurance_vault).await; let liqor_before = account_position(solana, vault_account, borrow_token1.bank).await; - let liab_transfer: f64 = 500.0 / 20.0; + let usdc_to_liab = 2.0 / 20.0; + let liab_transfer: f64 = 500.0 * usdc_to_liab; send_tx( solana, TokenLiqBankruptcyInstruction { @@ -573,7 +578,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> { account_position(solana, account, borrow_token2.bank).await, (liab_before + liab_transfer) as i64 ); - let usdc_amount = (liab_transfer * 20.0 * 1.02).ceil() as u64; + let usdc_amount = (liab_transfer / usdc_to_liab * 1.02).ceil() as u64; assert_eq!( solana.token_account_balance(insurance_vault).await, insurance_vault_before - usdc_amount From 8f86b0998e8d947f06559ab764507d6b4cd9effb Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 16 Mar 2023 16:53:23 +0100 Subject: [PATCH 09/48] v0.9.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index da39ac5f1..18aa66fee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockworks-foundation/mango-v4", - "version": "0.9.6", + "version": "0.9.7", "description": "Typescript Client for mango-v4 program.", "repository": "https://github.com/blockworks-foundation/mango-v4", "author": { From f98dfafe24afe631da2758174b52e43aa5415265 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Sat, 18 Mar 2023 10:56:18 +0100 Subject: [PATCH 10/48] ts: Fix ix gate enum, add code for creating a disable-tx gov ix --- ts/client/scripts/mb-create-gov-ix.ts | 31 +++++++++++++++++++++++++-- ts/client/src/clientIxParamBuilder.ts | 4 +++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/ts/client/scripts/mb-create-gov-ix.ts b/ts/client/scripts/mb-create-gov-ix.ts index e2e6ac8af..304b7e431 100644 --- a/ts/client/scripts/mb-create-gov-ix.ts +++ b/ts/client/scripts/mb-create-gov-ix.ts @@ -13,7 +13,11 @@ import fs from 'fs'; import { TokenIndex } from '../src/accounts/bank'; import { Builder } from '../src/builder'; import { MangoClient } from '../src/client'; -import { NullTokenEditParams } from '../src/clientIxParamBuilder'; +import { + buildIxGate, + NullTokenEditParams, + TrueIxGateParams, +} from '../src/clientIxParamBuilder'; import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../src/constants'; import { bpsToDecimal, percentageToDecimal, toNative } from '../src/utils'; @@ -294,12 +298,35 @@ async function perpCreate(): Promise { console.log(serializeInstructionToBase64(ix)); } +async function ixDisable(): Promise { + const result = await buildAdminClient(); + const client = result[0]; + const admin = result[1]; + + const group = await client.getGroup( + new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), + ); + + const ixGateParams = TrueIxGateParams; + ixGateParams.HealthRegion = false; + const ix = await client.program.methods + .ixGateSet(buildIxGate(ixGateParams)) + .accounts({ + group: group.publicKey, + admin: group.securityAdmin, + }) + .instruction(); + + console.log(await serializeInstructionToBase64(ix)); +} + async function main(): Promise { try { // await tokenRegister(); // await tokenEdit(); // await perpCreate(); - await serum3Register(); + // await serum3Register(); + await ixDisable(); } catch (error) { console.log(error); } diff --git a/ts/client/src/clientIxParamBuilder.ts b/ts/client/src/clientIxParamBuilder.ts index 2fc2dcefb..1148656f5 100644 --- a/ts/client/src/clientIxParamBuilder.ts +++ b/ts/client/src/clientIxParamBuilder.ts @@ -171,6 +171,7 @@ export interface IxGateParams { TokenRegisterTrustless: boolean; TokenUpdateIndexAndRate: boolean; TokenWithdraw: boolean; + AccountBuybackFeesWithMngo: boolean; } // Default with all ixs enabled, use with buildIxGate @@ -226,6 +227,7 @@ export const TrueIxGateParams: IxGateParams = { TokenRegisterTrustless: true, TokenUpdateIndexAndRate: true, TokenWithdraw: true, + AccountBuybackFeesWithMngo: true, }; // build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(), @@ -291,7 +293,7 @@ export function buildIxGate(p: IxGateParams): BN { toggleIx(ixGate, p, 'TokenRegisterTrustless', 45); toggleIx(ixGate, p, 'TokenUpdateIndexAndRate', 46); toggleIx(ixGate, p, 'TokenWithdraw', 47); - toggleIx(ixGate, p, 'AccountSettleFeesWithMngo', 48); + toggleIx(ixGate, p, 'AccountBuybackFeesWithMngo', 48); return ixGate; } From b7dd8e0663db64994dd791bd2414ade9a8c2b0eb Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 20 Mar 2023 11:18:11 +0100 Subject: [PATCH 11/48] Fee buyback: Respect USDC oracle (#504) --- Cargo.lock | 2 +- mango_v4.json | 2 +- .../account_buyback_fees_with_mngo.rs | 68 +++++++++++-------- .../serum3_liq_force_cancel_orders.rs | 1 + .../src/instructions/serum3_place_order.rs | 20 ++++-- .../src/instructions/serum3_settle_funds.rs | 1 + programs/mango-v4/src/lib.rs | 4 +- programs/mango-v4/src/state/group.rs | 3 + .../tests/cases/test_bankrupt_tokens.rs | 1 + .../cases/test_fees_buyback_with_mngo.rs | 23 ++++--- programs/mango-v4/tests/cases/test_serum.rs | 67 +++++++++++++----- .../tests/program_test/mango_client.rs | 2 +- .../tests/program_test/mango_setup.rs | 2 + ts/client/src/client.ts | 6 +- ts/client/src/mango_v4.ts | 4 +- 15 files changed, 136 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 013020289..1ae867ab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3130,7 +3130,7 @@ dependencies = [ ] [[package]] -name = "mango-v4-settle-bot" +name = "mango-v4-settler" version = "0.0.1" dependencies = [ "anchor-client", diff --git a/mango_v4.json b/mango_v4.json index 94ac11f69..2c2ddaab9 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -1176,7 +1176,7 @@ ], "args": [ { - "name": "maxBuyback", + "name": "maxBuybackUsd", "type": "u64" } ] diff --git a/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs b/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs index f0c8765ef..6c388fd78 100644 --- a/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs +++ b/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs @@ -11,7 +11,7 @@ use crate::logs::{AccountBuybackFeesWithMngoLog, TokenBalanceLog}; pub fn account_buyback_fees_with_mngo( ctx: Context, - max_buyback: u64, + max_buyback_usd: u64, ) -> Result<()> { // Cannot buyback from yourself require_keys_neq!( @@ -34,21 +34,36 @@ pub fn account_buyback_fees_with_mngo( let mut mngo_bank = ctx.accounts.mngo_bank.load_mut()?; let mut fees_bank = ctx.accounts.fees_bank.load_mut()?; + let clock = Clock::get()?; + let now_ts = clock.unix_timestamp.try_into().unwrap(); + let slot = clock.slot; + + let mngo_oracle_price = mngo_bank.oracle_price( + &AccountInfoRef::borrow(&ctx.accounts.mngo_oracle.as_ref())?, + Some(slot), + )?; + let mngo_asset_price = mngo_oracle_price.min(mngo_bank.stable_price()); + + let fees_oracle_price = fees_bank.oracle_price( + &AccountInfoRef::borrow(&ctx.accounts.fees_oracle.as_ref())?, + Some(slot), + )?; + let fees_liab_price = fees_oracle_price.max(fees_bank.stable_price()); + let bonus_factor = I80F48::from_num(group.buyback_fees_mngo_bonus_factor); - let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap(); account .fixed .expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval); // quick return if nothing to buyback - let mut max_buyback = { + let mut max_buyback_usd = { let dao_fees_token_position = dao_account.ensure_token_position(fees_bank.token_index)?.0; - let dao_fees_native = dao_fees_token_position.native(&fees_bank); - I80F48::from_num::(max_buyback.min(account.fixed.buyback_fees_accrued())) - .min(dao_fees_native) + let dao_fees = dao_fees_token_position.native(&fees_bank); + I80F48::from_num(max_buyback_usd.min(account.fixed.buyback_fees_accrued())) + .min(dao_fees * fees_liab_price) }; - if max_buyback <= I80F48::ZERO { + if max_buyback_usd <= I80F48::ZERO { msg!( "nothing to buyback, (buyback_fees_accrued {})", account.fixed.buyback_fees_accrued() @@ -57,31 +72,29 @@ pub fn account_buyback_fees_with_mngo( } // if mngo token position has borrows, skip buyback - let account_mngo_native = account + let account_mngo = account .token_position(mngo_bank.token_index) .map(|tp| tp.native(&mngo_bank)) .unwrap_or(I80F48::ZERO); - if account_mngo_native <= I80F48::ZERO { + if account_mngo <= I80F48::ZERO { msg!( "account mngo token position ({} native mngo) is <= 0, nothing will be bought back", - account_mngo_native + account_mngo ); return Ok(()); } let (account_mngo_token_position, account_mngo_raw_token_index, _) = account.ensure_token_position(mngo_bank.token_index)?; + let mngo_buyback_price = mngo_asset_price * bonus_factor; + // compute max mngo to swap for fees - let mngo_oracle_price = mngo_bank.oracle_price( - &AccountInfoRef::borrow(&ctx.accounts.mngo_oracle.as_ref())?, - Some(Clock::get()?.slot), - )?; - let mngo_buyback_price = mngo_oracle_price.min(mngo_bank.stable_price()) * bonus_factor; // mngo is exchanged at a discount - let mut max_buyback_mngo = max_buyback / mngo_buyback_price; + let mut max_buyback_mngo = max_buyback_usd / mngo_buyback_price; // buyback is restricted to account's token position - max_buyback_mngo = max_buyback_mngo.min(account_mngo_native); - max_buyback = max_buyback_mngo * mngo_buyback_price; + max_buyback_mngo = max_buyback_mngo.min(account_mngo); + max_buyback_usd = max_buyback_mngo * mngo_buyback_price; + let max_buyback_fees = max_buyback_usd / fees_liab_price; // move mngo from user to dao let (dao_mngo_token_position, dao_mngo_raw_token_index, _) = @@ -117,11 +130,11 @@ pub fn account_buyback_fees_with_mngo( account.ensure_token_position(fees_bank.token_index)?; let (dao_fees_token_position, dao_fees_raw_token_index, _) = dao_account.ensure_token_position(fees_bank.token_index)?; - let dao_fees_native = dao_fees_token_position.native(&fees_bank); - assert!(dao_fees_native >= max_buyback); + let dao_fees = dao_fees_token_position.native(&fees_bank); + assert!(dao_fees >= max_buyback_fees); let in_use = fees_bank.withdraw_without_fee( dao_fees_token_position, - max_buyback, + max_buyback_fees, now_ts, mngo_oracle_price, )?; @@ -131,7 +144,7 @@ pub fn account_buyback_fees_with_mngo( ctx.accounts.dao_account.key(), ); } - let in_use = fees_bank.deposit(account_fees_token_position, max_buyback, now_ts)?; + let in_use = fees_bank.deposit(account_fees_token_position, max_buyback_fees, now_ts)?; emit!(TokenBalanceLog { mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.account.key(), @@ -149,17 +162,18 @@ pub fn account_buyback_fees_with_mngo( account .fixed - .reduce_buyback_fees_accrued(max_buyback.ceil().to_num::()); + .reduce_buyback_fees_accrued(max_buyback_usd.ceil().to_num::()); msg!( - "bought back {} native fees with {} native mngo", - max_buyback, - max_buyback_mngo + "bought back {} native usd fees by exchanging {} native mngo for {} native fees", + max_buyback_usd, + max_buyback_mngo, + max_buyback_fees, ); emit!(AccountBuybackFeesWithMngoLog { mango_group: ctx.accounts.group.key(), mango_account: ctx.accounts.account.key(), - buyback_fees: max_buyback.to_bits(), + buyback_fees: max_buyback_fees.to_bits(), buyback_mngo: max_buyback_mngo.to_bits(), mngo_buyback_price: mngo_buyback_price.to_bits(), oracle_price: mngo_oracle_price.to_bits(), diff --git a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index c35d36ef2..79308df9b 100644 --- a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -159,6 +159,7 @@ pub fn serum3_liq_force_cancel_orders( &after_oo, Some(&mut health_cache), true, + None, )?; // diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 19776ceee..764735add 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -1,6 +1,7 @@ use crate::accounts_zerocopy::AccountInfoRef; use crate::error::*; use crate::health::*; +use crate::i80f48::ClampToInt; use crate::state::*; use crate::accounts_ix::*; @@ -425,6 +426,7 @@ pub fn apply_settle_changes( after_oo: &OpenOrdersSlim, health_cache: Option<&mut HealthCache>, fees_to_dao: bool, + quote_oracle: Option<&AccountInfo>, ) -> Result<()> { let mut received_fees = 0; if fees_to_dao { @@ -434,15 +436,21 @@ pub fn apply_settle_changes( .saturating_sub(after_oo.native_rebates()); quote_bank.collected_fees_native += I80F48::from(received_fees); - // Ideally we could credit buyback_fees at the current value of the received fees, - // but the settle_funds instruction currently doesn't receive the oracle account - // that would be needed for it. - if quote_bank.token_index == QUOTE_TOKEN_INDEX { - let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap(); + // Credit the buyback_fees at the current value of the quote token. + if let Some(quote_oracle_ai) = quote_oracle { + let clock = Clock::get()?; + let now_ts = clock.unix_timestamp.try_into().unwrap(); + + let quote_oracle_price = quote_bank + .oracle_price(&AccountInfoRef::borrow(quote_oracle_ai)?, Some(clock.slot))?; + let quote_asset_price = quote_oracle_price.min(quote_bank.stable_price()); account .fixed .expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval); - account.fixed.accrue_buyback_fees(received_fees); + let fees_in_usd = I80F48::from(received_fees) * quote_asset_price; + account + .fixed + .accrue_buyback_fees(fees_in_usd.clamp_to_u64()); } } diff --git a/programs/mango-v4/src/instructions/serum3_settle_funds.rs b/programs/mango-v4/src/instructions/serum3_settle_funds.rs index 490d3bd27..6ec68d430 100644 --- a/programs/mango-v4/src/instructions/serum3_settle_funds.rs +++ b/programs/mango-v4/src/instructions/serum3_settle_funds.rs @@ -126,6 +126,7 @@ pub fn serum3_settle_funds<'info>( &after_oo, None, fees_to_dao, + v2.map(|d| d.quote_oracle.as_ref()), )?; emit!(Serum3OpenOrdersBalanceLogV2 { diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index d80fa87d8..bc7bbe534 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -285,10 +285,10 @@ pub mod mango_v4 { pub fn account_buyback_fees_with_mngo( ctx: Context, - max_buyback: u64, + max_buyback_usd: u64, ) -> Result<()> { #[cfg(feature = "enable-gpl")] - instructions::account_buyback_fees_with_mngo(ctx, max_buyback)?; + instructions::account_buyback_fees_with_mngo(ctx, max_buyback_usd)?; Ok(()) } diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index e9359807f..1a5473492 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -23,6 +23,9 @@ pub const INSURANCE_TOKEN_INDEX: TokenIndex = 0; /// configurable. pub const PERP_SETTLE_TOKEN_INDEX: TokenIndex = 0; +/// The token index used in AccountBuybackFeesWithMngo to exchange for MNGO +pub const FEE_BUYBACK_QUOTE_TOKEN_INDEX: TokenIndex = 0; + #[account(zero_copy(safe_bytemuck_derives))] #[derive(Debug)] pub struct Group { diff --git a/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs b/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs index 215c73113..4544dcbb8 100644 --- a/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs +++ b/programs/mango-v4/tests/cases/test_bankrupt_tokens.rs @@ -288,6 +288,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> { group, tokens, insurance_vault, + .. } = mango_setup::GroupWithTokensConfig { admin, payer, diff --git a/programs/mango-v4/tests/cases/test_fees_buyback_with_mngo.rs b/programs/mango-v4/tests/cases/test_fees_buyback_with_mngo.rs index 73a2d530e..07d91219e 100644 --- a/programs/mango-v4/tests/cases/test_fees_buyback_with_mngo.rs +++ b/programs/mango-v4/tests/cases/test_fees_buyback_with_mngo.rs @@ -137,6 +137,10 @@ async fn test_fees_buyback_with_mngo() -> Result<(), TransportError> { .await .unwrap(); + // Change the mngo and quote price to verify that they are taken into account + set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 2.0).await; + set_bank_stub_oracle_price(solana, group, &tokens[1], admin, 3.0).await; + // // Test: Account buyback fees accrued with mngo // @@ -183,15 +187,16 @@ async fn test_fees_buyback_with_mngo() -> Result<(), TransportError> { assert_eq!(before_fees_accrued - after_fees_accrued, 19); // token[1] swapped at discount for token[0] - // TODO: https://github.com/blockworks-foundation/mango-v4/pull/464#discussion_r1111779730 - assert!( - (fees_token_position_after - fees_token_position_before) - I80F48::from_num(20) - < I80F48::from_num(0.000001) - ); - assert!( - (mngo_token_position_before - mngo_token_position_after) - I80F48::from_num(16.666666) - < I80F48::from_num(0.000001) - ); + assert!(assert_equal( + fees_token_position_after - fees_token_position_before, + 19.0 / 2.0, + 0.1 + )); + assert!(assert_equal( + mngo_token_position_after - mngo_token_position_before, + -19.0 / 3.0 / 1.2, + 0.1 + )); Ok(()) } diff --git a/programs/mango-v4/tests/cases/test_serum.rs b/programs/mango-v4/tests/cases/test_serum.rs index 401f0fe83..7daef6bf7 100644 --- a/programs/mango-v4/tests/cases/test_serum.rs +++ b/programs/mango-v4/tests/cases/test_serum.rs @@ -347,12 +347,14 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> { let deposit_amount = 180000; let CommonSetup { serum_market_cookie, - quote_bank, - base_bank, + quote_token, + base_token, mut order_placer, mut order_placer2, .. } = common_setup(&context, deposit_amount).await; + let quote_bank = quote_token.bank; + let base_bank = base_token.bank; let account = order_placer.account; let account2 = order_placer2.account; @@ -508,7 +510,7 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> { let account_data = solana.get_account::(account).await; assert_eq!( account_data.buyback_fees_accrued_current, - serum_fee(fill_amount) as u64 + 0 // the v1 function doesn't accumulate buyback fees ); assert_eq!( @@ -533,12 +535,14 @@ async fn test_serum_settle_v1() -> Result<(), TransportError> { let deposit_amount = 160000; let CommonSetup { serum_market_cookie, - quote_bank, - base_bank, + quote_token, + base_token, mut order_placer, mut order_placer2, .. } = common_setup(&context, deposit_amount).await; + let quote_bank = quote_token.bank; + let base_bank = base_token.bank; let account = order_placer.account; let account2 = order_placer2.account; @@ -615,23 +619,37 @@ async fn test_serum_settle_v2_to_dao() -> Result<(), TransportError> { // let deposit_amount = 160000; let CommonSetup { + group_with_tokens, serum_market_cookie, - quote_bank, - base_bank, + quote_token, + base_token, mut order_placer, mut order_placer2, .. } = common_setup(&context, deposit_amount).await; + let quote_bank = quote_token.bank; + let base_bank = base_token.bank; let account = order_placer.account; let account2 = order_placer2.account; + // Change the quote price to verify that the current value of the serum quote token + // is added to the buyback fees amount + set_bank_stub_oracle_price( + solana, + group_with_tokens.group, + "e_token, + group_with_tokens.admin, + 2.0, + ) + .await; + let serum_taker_fee = |amount: i64| (amount as f64 * 0.0004).trunc() as i64; let serum_maker_rebate = |amount: i64| (amount as f64 * 0.0002).floor() as i64; let serum_referrer_fee = |amount: i64| (amount as f64 * 0.0002).trunc() as i64; let loan_origination_fee = |amount: i64| (amount as f64 * 0.0005).trunc() as i64; // - // TEST: Use v1 serum3_settle_funds + // TEST: Use v2 serum3_settle_funds // let deposit_amount = deposit_amount as i64; let amount = 200000; @@ -683,6 +701,14 @@ async fn test_serum_settle_v2_to_dao() -> Result<(), TransportError> { 0.1 )); + let account_data = solana.get_account::(account).await; + assert_eq!( + account_data.buyback_fees_accrued_current, + (serum_maker_rebate(amount) * 2) as u64 // *2 because that's the quote price and this number is in $ + ); + let account2_data = solana.get_account::(account2).await; + assert_eq!(account2_data.buyback_fees_accrued_current, 0); + Ok(()) } @@ -699,12 +725,14 @@ async fn test_serum_settle_v2_to_account() -> Result<(), TransportError> { let deposit_amount = 160000; let CommonSetup { serum_market_cookie, - quote_bank, - base_bank, + quote_token, + base_token, mut order_placer, mut order_placer2, .. } = common_setup(&context, deposit_amount).await; + let quote_bank = quote_token.bank; + let base_bank = base_token.bank; let account = order_placer.account; let account2 = order_placer2.account; @@ -769,14 +797,19 @@ async fn test_serum_settle_v2_to_account() -> Result<(), TransportError> { 0.1 )); + let account_data = solana.get_account::(account).await; + assert_eq!(account_data.buyback_fees_accrued_current, 0); + let account2_data = solana.get_account::(account2).await; + assert_eq!(account2_data.buyback_fees_accrued_current, 0); + Ok(()) } struct CommonSetup { - _group_with_tokens: GroupWithTokens, + group_with_tokens: GroupWithTokens, serum_market_cookie: SpotMarketCookie, - quote_bank: Pubkey, - base_bank: Pubkey, + quote_token: crate::program_test::mango_setup::Token, + base_token: crate::program_test::mango_setup::Token, order_placer: SerumOrderPlacer, order_placer2: SerumOrderPlacer, } @@ -800,9 +833,7 @@ async fn common_setup(context: &TestContext, deposit_amount: u64) -> CommonSetup let group = group_with_tokens.group; let tokens = group_with_tokens.tokens.clone(); let base_token = &tokens[1]; - let base_bank = base_token.bank; let quote_token = &tokens[0]; - let quote_bank = quote_token.bank; // // SETUP: Create serum market @@ -916,10 +947,10 @@ async fn common_setup(context: &TestContext, deposit_amount: u64) -> CommonSetup }; CommonSetup { - _group_with_tokens: group_with_tokens, + group_with_tokens, serum_market_cookie, - quote_bank, - base_bank, + quote_token: quote_token.clone(), + base_token: base_token.clone(), order_placer, order_placer2, } diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 00bfead7b..66aec4510 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -1836,7 +1836,7 @@ impl ClientInstruction for AccountBuybackFeesWithMngo { ) -> (Self::Accounts, instruction::Instruction) { let program_id = mango_v4::id(); let instruction = Self::Instruction { - max_buyback: u64::MAX, + max_buyback_usd: u64::MAX, }; let account = account_loader diff --git a/programs/mango-v4/tests/program_test/mango_setup.rs b/programs/mango-v4/tests/program_test/mango_setup.rs index 837d3e247..684390fd6 100644 --- a/programs/mango-v4/tests/program_test/mango_setup.rs +++ b/programs/mango-v4/tests/program_test/mango_setup.rs @@ -27,6 +27,7 @@ pub struct Token { pub struct GroupWithTokens { pub group: Pubkey, + pub admin: TestKeypair, pub insurance_vault: Pubkey, pub tokens: Vec, } @@ -141,6 +142,7 @@ impl<'a> GroupWithTokensConfig { GroupWithTokens { group, + admin, insurance_vault, tokens, } diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 0afab2124..64097a364 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -999,11 +999,11 @@ export class MangoClient { public async accountBuybackFeesWithMngoIx( group: Group, mangoAccount: MangoAccount, - maxBuyback?: number, + maxBuybackUsd?: number, ): Promise { - maxBuyback = maxBuyback ?? mangoAccount.getMaxFeesBuybackUi(group); + maxBuybackUsd = maxBuybackUsd ?? mangoAccount.getMaxFeesBuybackUi(group); return await this.program.methods - .accountBuybackFeesWithMngo(new BN(maxBuyback)) + .accountBuybackFeesWithMngo(toNative(maxBuybackUsd, 6)) .accounts({ group: group.publicKey, account: mangoAccount.publicKey, diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 32311e266..16fcf1c55 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1176,7 +1176,7 @@ export type MangoV4 = { ], "args": [ { - "name": "maxBuyback", + "name": "maxBuybackUsd", "type": "u64" } ] @@ -9856,7 +9856,7 @@ export const IDL: MangoV4 = { ], "args": [ { - "name": "maxBuyback", + "name": "maxBuybackUsd", "type": "u64" } ] From fd0bd1d6d5e5ab8821cf8f0793c6bc53647d2f1b Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Mon, 20 Mar 2023 13:57:32 +0100 Subject: [PATCH 12/48] refactor script (#509) Signed-off-by: microwavedcola1 --- ts/client/scripts/mb-create-gov-ix.ts | 69 +++++++++------------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/ts/client/scripts/mb-create-gov-ix.ts b/ts/client/scripts/mb-create-gov-ix.ts index 304b7e431..991cc87eb 100644 --- a/ts/client/scripts/mb-create-gov-ix.ts +++ b/ts/client/scripts/mb-create-gov-ix.ts @@ -14,14 +14,17 @@ import { TokenIndex } from '../src/accounts/bank'; import { Builder } from '../src/builder'; import { MangoClient } from '../src/client'; import { - buildIxGate, NullTokenEditParams, TrueIxGateParams, + buildIxGate, } from '../src/clientIxParamBuilder'; import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../src/constants'; import { bpsToDecimal, percentageToDecimal, toNative } from '../src/utils'; -const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MB_PAYER3_KEYPAIR } = process.env; +const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR } = process.env; + +const CLIENT_USER = MB_PAYER_KEYPAIR; +const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'; const defaultOracleConfig = { confFilter: 0.1, @@ -37,41 +40,31 @@ const defaultInterestRate = { maxRate: 2.0, }; -async function buildAdminClient(): Promise<[MangoClient, Keypair, Keypair]> { - const admin = Keypair.fromSecretKey( - Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER3_KEYPAIR!, 'utf-8'))), +async function buildClient(): Promise { + const clientKeypair = Keypair.fromSecretKey( + Buffer.from(JSON.parse(fs.readFileSync(CLIENT_USER!, 'utf-8'))), ); const options = AnchorProvider.defaultOptions(); const connection = new Connection(MB_CLUSTER_URL!, options); - const adminWallet = new Wallet(admin); - const adminProvider = new AnchorProvider(connection, adminWallet, options); + const clientWallet = new Wallet(clientKeypair); + const clientProvider = new AnchorProvider(connection, clientWallet, options); - const client = await MangoClient.connect( - adminProvider, + return await MangoClient.connect( + clientProvider, 'mainnet-beta', MANGO_V4_ID['mainnet-beta'], { idsSource: 'get-program-accounts', }, ); - - const creator = Keypair.fromSecretKey( - Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))), - ); - - return [client, admin, creator]; } async function tokenRegister(): Promise { - const result = await buildAdminClient(); - const client = result[0]; - const admin = result[1]; + const client = await buildClient(); - const group = await client.getGroup( - new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - ); + const group = await client.getGroup(new PublicKey(GROUP_PK)); const ix = await client.program.methods .tokenRegister( @@ -107,16 +100,11 @@ async function tokenRegister(): Promise { } async function tokenEdit(): Promise { - const result = await buildAdminClient(); - const client = result[0]; - const admin = result[1]; + const client = await buildClient(); - const group = await client.getGroup( - new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - ); + const group = await client.getGroup(new PublicKey(GROUP_PK)); const params = Builder(NullTokenEditParams) - .oracle(new PublicKey('GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU')) .borrowWeightScaleStartQuote(new BN(toNative(100000, 6)).toNumber()) .depositWeightScaleStartQuote(new BN(toNative(100000, 6)).toNumber()) .build(); @@ -168,12 +156,9 @@ async function tokenEdit(): Promise { } async function serum3Register(): Promise { - const result = await buildAdminClient(); - const client = result[0]; + const client = await buildClient(); - const group = await client.getGroup( - new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - ); + const group = await client.getGroup(new PublicKey(GROUP_PK)); const ix = await client.program.methods .serum3RegisterMarket(3, 'ETH (Portal)/USDC') @@ -198,9 +183,7 @@ async function serum3Register(): Promise { } async function perpCreate(): Promise { - const result = await buildAdminClient(); - const client = result[0]; - const admin = result[1]; + const client = await buildClient(); const bids = new Keypair(); const asks = new Keypair(); @@ -213,9 +196,7 @@ async function perpCreate(): Promise { (client.program.account.eventQueue as any)._idlAccount, ); - const group = await client.getGroup( - new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - ); + const group = await client.getGroup(new PublicKey(GROUP_PK)); const ix = await client.program.methods .perpCreateMarket( @@ -299,13 +280,9 @@ async function perpCreate(): Promise { } async function ixDisable(): Promise { - const result = await buildAdminClient(); - const client = result[0]; - const admin = result[1]; + const client = await buildClient(); - const group = await client.getGroup( - new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'), - ); + const group = await client.getGroup(new PublicKey(GROUP_PK)); const ixGateParams = TrueIxGateParams; ixGateParams.HealthRegion = false; @@ -326,7 +303,7 @@ async function main(): Promise { // await tokenEdit(); // await perpCreate(); // await serum3Register(); - await ixDisable(); + // await ixDisable(); } catch (error) { console.log(error); } From d1e3da2b7529c90aa54cec26075e76dd2293a0a3 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 20 Mar 2023 13:31:56 +0100 Subject: [PATCH 13/48] IxGateSet: Log AccountBuybackFeesWithMngo state --- programs/mango-v4/src/instructions/ix_gate_set.rs | 1 + programs/mango-v4/src/state/group.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/programs/mango-v4/src/instructions/ix_gate_set.rs b/programs/mango-v4/src/instructions/ix_gate_set.rs index 489e48f4d..e5805a717 100644 --- a/programs/mango-v4/src/instructions/ix_gate_set.rs +++ b/programs/mango-v4/src/instructions/ix_gate_set.rs @@ -64,6 +64,7 @@ pub fn ix_gate_set(ctx: Context, ix_gate: u128) -> Result<()> { log_if_changed(&group, ix_gate, IxGate::TokenRegisterTrustless); log_if_changed(&group, ix_gate, IxGate::TokenUpdateIndexAndRate); log_if_changed(&group, ix_gate, IxGate::TokenWithdraw); + log_if_changed(&group, ix_gate, IxGate::AccountBuybackFeesWithMngo); group.ix_gate = ix_gate; diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index 1a5473492..c687656ce 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -187,6 +187,7 @@ pub enum IxGate { TokenUpdateIndexAndRate = 46, TokenWithdraw = 47, AccountBuybackFeesWithMngo = 48, + // NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction. } // note: using creator instead of admin, since admin can be changed From 99360e69a3c03323ec2d5eadfb7727d5ef65c056 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 20 Mar 2023 13:42:30 +0100 Subject: [PATCH 14/48] TokenRegister: Sanity checks on token_index --- programs/mango-v4/src/instructions/token_register.rs | 1 + programs/mango-v4/src/instructions/token_register_trustless.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index 21dde659e..1afe7be64 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -37,6 +37,7 @@ pub fn token_register( ctx.accounts.mint.key() ); } + require_neq!(token_index, TokenIndex::MAX); let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index 3e4b8222f..710b37e32 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -17,7 +17,8 @@ pub fn token_register_trustless( token_index: TokenIndex, name: String, ) -> Result<()> { - require_neq!(token_index, 0); + require_neq!(token_index, QUOTE_TOKEN_INDEX); + require_neq!(token_index, TokenIndex::MAX); let net_borrow_limit_window_size_ts = 24 * 60 * 60u64; let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); From 658a2200955a08b694cc7002ee01960cb8cedd73 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 20 Mar 2023 13:48:44 +0100 Subject: [PATCH 15/48] Allow token/market names to fill storage bytes completely Previously the last byte was always zero. --- programs/mango-v4/src/util.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/programs/mango-v4/src/util.rs b/programs/mango-v4/src/util.rs index 7b1380631..58c34dc72 100644 --- a/programs/mango-v4/src/util.rs +++ b/programs/mango-v4/src/util.rs @@ -14,7 +14,7 @@ pub(crate) use zip; pub fn fill_from_str(name: &str) -> Result<[u8; N]> { let name_bytes = name.as_bytes(); - require!(name_bytes.len() < N, MangoError::SomeError); + require!(name_bytes.len() <= N, MangoError::SomeError); let mut name_ = [0u8; N]; name_[..name_bytes.len()].copy_from_slice(name_bytes); Ok(name_) @@ -30,3 +30,22 @@ pub fn format_zero_terminated_utf8_bytes( .trim_matches(char::from(0)), ) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fill_from_str() { + assert_eq!(fill_from_str::<4>(""), Ok([0, 0, 0, 0])); + assert_eq!( + fill_from_str::<4>("abc"), + Ok(['a' as u8, 'b' as u8, 'c' as u8, 0]) + ); + assert_eq!( + fill_from_str::<4>("abcd"), + Ok(['a' as u8, 'b' as u8, 'c' as u8, 'd' as u8]) + ); + assert!(fill_from_str::<4>("abcde").is_err()); + } +} From b22a1e7f57a76b0edf627bccfb35f3eb088d2af0 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 20 Mar 2023 14:02:35 +0100 Subject: [PATCH 16/48] HealthRegion: Whitelist allowed instruction types (#508) This fixes a security issue where bankruptcy related instructions could be called inside a health region. Now health regions are limited to compute optimization like when placing multiple orders in one transaction. This limitation also makes it impossible to abuse health regions for flash loans. Use the FlashLoan instructions for that purpose. --- programs/mango-v4/src/error.rs | 2 + .../src/instructions/health_region.rs | 52 +++-- .../tests/cases/test_health_region.rs | 201 ++++++++++++------ 3 files changed, 179 insertions(+), 76 deletions(-) diff --git a/programs/mango-v4/src/error.rs b/programs/mango-v4/src/error.rs index d8e136a54..c865819da 100644 --- a/programs/mango-v4/src/error.rs +++ b/programs/mango-v4/src/error.rs @@ -95,6 +95,8 @@ pub enum MangoError { NoLiquidatablePerpBasePosition, #[msg("perp order id not found on the orderbook")] PerpOrderIdNotFound, + #[msg("HealthRegions allow only specific instructions between Begin and End")] + HealthRegionBadInnerInstruction, } impl MangoError { diff --git a/programs/mango-v4/src/instructions/health_region.rs b/programs/mango-v4/src/instructions/health_region.rs index 19abebcc2..230dd13d9 100644 --- a/programs/mango-v4/src/instructions/health_region.rs +++ b/programs/mango-v4/src/instructions/health_region.rs @@ -10,11 +10,33 @@ use fixed::types::I80F48; pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, HealthRegionBegin<'info>>, ) -> Result<()> { - // Check if the other instructions in the transactions are compatible + // The instructions that may be called inside a HealthRegion + let allowed_inner_ix = [ + crate::instruction::PerpCancelAllOrders::discriminator(), + crate::instruction::PerpCancelAllOrdersBySide::discriminator(), + crate::instruction::PerpCancelOrder::discriminator(), + crate::instruction::PerpCancelOrderByClientOrderId::discriminator(), + crate::instruction::PerpPlaceOrder::discriminator(), + crate::instruction::PerpPlaceOrderPegged::discriminator(), + crate::instruction::Serum3CancelAllOrders::discriminator(), + crate::instruction::Serum3CancelOrder::discriminator(), + crate::instruction::Serum3PlaceOrder::discriminator(), + crate::instruction::Serum3SettleFunds::discriminator(), + crate::instruction::Serum3SettleFundsV2::discriminator(), + ]; + + // Check if the other instructions in the transaction are compatible { let ixs = ctx.accounts.instructions.as_ref(); let current_index = tx_instructions::load_current_index_checked(ixs)? as usize; + // Forbid HealthRegionBegin to be called from CPI (it does not have to be the first instruction) + let current_ix = tx_instructions::load_instruction_at_checked(current_index, ixs)?; + require_msg!( + current_ix.program_id == *ctx.program_id, + "HealthRegionBegin must be a top-level instruction" + ); + // There must be a matching HealthRegionEnd instruction let mut index = current_index + 1; let mut found_end = false; @@ -26,18 +48,24 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>( }; index += 1; - if ix.program_id != crate::id() { - continue; - } - if ix.data[0..8] != crate::instruction::HealthRegionEnd::discriminator() { - continue; - } + require_keys_eq!( + ix.program_id, + crate::id(), + MangoError::HealthRegionBadInnerInstruction + ); - // check that it's for the same account - require_keys_eq!(ix.accounts[0].pubkey, ctx.accounts.account.key()); - - found_end = true; - index += 1; + let discriminator: [u8; 8] = ix.data[0..8].try_into().unwrap(); + if discriminator == crate::instruction::HealthRegionEnd::discriminator() { + // check that it's for the same account + require_keys_eq!(ix.accounts[0].pubkey, ctx.accounts.account.key()); + found_end = true; + break; + } else { + require!( + allowed_inner_ix.contains(&discriminator), + MangoError::HealthRegionBadInnerInstruction + ); + } } require_msg!( found_end, diff --git a/programs/mango-v4/tests/cases/test_health_region.rs b/programs/mango-v4/tests/cases/test_health_region.rs index b10c3e133..f3d7d827b 100644 --- a/programs/mango-v4/tests/cases/test_health_region.rs +++ b/programs/mango-v4/tests/cases/test_health_region.rs @@ -1,9 +1,10 @@ use super::*; +use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side}; #[tokio::test] async fn test_health_wrap() -> Result<(), TransportError> { let mut test_builder = TestContextBuilder::new(); - test_builder.test().set_compute_max_units(100000); + test_builder.test().set_compute_max_units(135000); let context = test_builder.start_default().await; let solana = &context.solana.clone(); @@ -11,7 +12,6 @@ async fn test_health_wrap() -> Result<(), TransportError> { let owner = context.users[0].key; let payer = context.users[1].key; let mints = &context.mints[0..2]; - let payer_mint_accounts = &context.users[1].token_accounts; // // SETUP: Create a group, account, register a token (mint0) @@ -25,73 +25,102 @@ async fn test_health_wrap() -> Result<(), TransportError> { } .create(solana) .await; + let quote_token = &tokens[0]; + let base_token = &tokens[1]; - // SETUP: Create an account with deposits, so the second account can borrow more than it has - create_funded_account(&solana, group, owner, 0, &context.users[1], mints, 2000, 0).await; - - // SETUP: Make a second account - let account = send_tx( + // + // SETUP: Create serum market + // + let serum_market_cookie = context + .serum + .list_spot_market(&base_token.mint, "e_token.mint) + .await; + let serum_market = send_tx( solana, - AccountCreateInstruction { - account_num: 1, - token_count: 8, - serum3_count: 0, - perp_count: 0, - perp_oo_count: 0, + Serum3RegisterMarketInstruction { group, - owner, + admin, + serum_program: context.serum.program_id, + serum_market_external: serum_market_cookie.market, + market_index: 0, + base_bank: base_token.bank, + quote_bank: quote_token.bank, payer, }, ) .await .unwrap() - .account; + .serum_market; + + // SETUP: Create an account with deposits, so the second account can borrow more than it has + create_funded_account( + &solana, + group, + owner, + 0, + &context.users[1], + mints, + 200000000, + 0, + ) + .await; + + // SETUP: Make a second account + let account = create_funded_account( + &solana, + group, + owner, + 1, + &context.users[1], + &mints[0..=1], + 100, + 0, + ) + .await; - // SETUP: deposit something, so only one new token position needs to be created - // simply because the test code can't deal with two affected banks right now send_tx( solana, - TokenDepositInstruction { - amount: 1, - reduce_only: false, + Serum3CreateOpenOrdersInstruction { account, + serum_market, owner, - token_account: payer_mint_accounts[0], - token_authority: payer.clone(), - bank_index: 0, + payer, }, ) .await .unwrap(); - let send_test_tx = |repay_amount| { - let tokens = tokens.clone(); + let send_test_tx = |limit_price, order_size, cancel| { async move { let mut tx = ClientTransaction::new(solana); tx.add_instruction(HealthRegionBeginInstruction { account }) .await; - tx.add_instruction(TokenWithdrawInstruction { - amount: 1000, // more than the 1 token that's on the account - allow_borrow: true, + tx.add_instruction(Serum3PlaceOrderInstruction { + side: Serum3Side::Ask, + limit_price: (limit_price * 100.0 / 10.0) as u64, // in quote_lot (10) per base lot (100) + max_base_qty: (order_size as u64) / 100, // in base lot (100) + max_native_quote_qty_including_fees: (limit_price * (order_size as f64)) as u64, + self_trade_behavior: Serum3SelfTradeBehavior::AbortTransaction, + order_type: Serum3OrderType::Limit, + client_order_id: 42, + limit: 10, account, owner, - token_account: payer_mint_accounts[0], - bank_index: 0, - }) - .await; - tx.add_instruction(TokenDepositInstruction { - amount: repay_amount, - reduce_only: false, - account, - owner, - token_account: payer_mint_accounts[1], - token_authority: payer.clone(), - bank_index: 0, + serum_market, }) .await; + if cancel { + tx.add_instruction(Serum3CancelAllOrdersInstruction { + limit: 10, + account, + owner, + serum_market, + }) + .await; + } tx.add_instruction(HealthRegionEndInstruction { account, - affected_bank: Some(tokens[1].bank), + affected_bank: None, }) .await; tx.send().await @@ -99,10 +128,10 @@ async fn test_health_wrap() -> Result<(), TransportError> { }; // - // TEST: Borrow a lot of token0 without collateral, but repay too little + // TEST: Placing a giant order fails // { - send_test_tx(1000).await.unwrap_err(); + send_test_tx(1.0, 100000, false).await.unwrap_err(); let logs = solana.program_log(); // reaches the End instruction assert!(logs @@ -112,35 +141,79 @@ async fn test_health_wrap() -> Result<(), TransportError> { assert!(logs .iter() .any(|line| line.contains("Error Code: HealthMustBePositiveOrIncrease"))); + // health computed only once + assert_eq!( + logs.iter() + .filter(|line| line.contains("post_init_health")) + .count(), + 1 + ); } // - // TEST: Borrow a lot of token0 without collateral, and repay in token1 in the same tx + // TEST: If we cancel the order again before the HealthRegionEnd, it can go through // { - let start_payer_mint0 = solana.token_account_balance(payer_mint_accounts[0]).await; - let start_payer_mint1 = solana.token_account_balance(payer_mint_accounts[1]).await; + send_test_tx(1.0, 100000, true).await.unwrap(); + let logs = solana.program_log(); + // health computed only once + assert_eq!( + logs.iter() + .filter(|line| line.contains("post_init_health")) + .count(), + 1 + ); + } - send_test_tx(3000).await.unwrap(); + // + // TEST: Try using withdraw in a health region + // + { + let mut tx = ClientTransaction::new(solana); + tx.add_instruction(HealthRegionBeginInstruction { account }) + .await; + tx.add_instruction(TokenWithdrawInstruction { + amount: 1, + allow_borrow: true, + account, + owner, + token_account: context.users[1].token_accounts[0], + bank_index: 0, + }) + .await; + tx.add_instruction(HealthRegionEndInstruction { + account, + affected_bank: None, + }) + .await; + tx.send().await.unwrap_err(); + } - assert_eq!( - solana.token_account_balance(payer_mint_accounts[0]).await - start_payer_mint0, - 1000 + // + // TEST: Try using a different program in a health region + // + { + let mut tx = ClientTransaction::new(solana); + tx.add_instruction(HealthRegionBeginInstruction { account }) + .await; + tx.add_instruction_direct( + spl_token::instruction::transfer( + &spl_token::ID, + &context.users[1].token_accounts[0], + &context.users[0].token_accounts[0], + &owner.pubkey(), + &[&owner.pubkey()], + 1, + ) + .unwrap(), ); - assert_eq!( - start_payer_mint1 - solana.token_account_balance(payer_mint_accounts[1]).await, - 3000 - ); - assert_eq!( - account_position(solana, account, tokens[0].bank).await, - -999 - ); - assert_eq!( - account_position(solana, account, tokens[1].bank).await, - 3000 - ); - let account_data: MangoAccount = solana.get_account(account).await; - assert_eq!(account_data.in_health_region, 0); + tx.add_instruction(HealthRegionEndInstruction { + account, + affected_bank: None, + }) + .await; + tx.add_signer(owner); + tx.send().await.unwrap_err(); } Ok(()) From fd4c69b1c2345058f8d7732e26bb0aa702f92b13 Mon Sep 17 00:00:00 2001 From: riordanp Date: Mon, 20 Mar 2023 13:59:43 +0000 Subject: [PATCH 17/48] Add fly deploy scripts (#490) --- .dockerignore | 5 +++- .github/workflows/ci-docker-fly-deploy.yml | 35 ++++++++++++++++++++++ cd/keeper.toml | 6 ++++ cd/liquidator.toml | 6 ++++ cd/mm.toml | 6 ++++ ts/client/scripts/mm/Dockerfile.mm | 35 ++++++++++++++++++++++ 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci-docker-fly-deploy.yml create mode 100644 cd/keeper.toml create mode 100644 cd/liquidator.toml create mode 100644 cd/mm.toml create mode 100644 ts/client/scripts/mm/Dockerfile.mm diff --git a/.dockerignore b/.dockerignore index fd22191a7..1f7a1bb34 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,4 +16,7 @@ yarn-error.log programs/margin-trade/src/lib-expanded.rs programs/mango-v4/src/lib-expanded.rs -keeper/.env \ No newline at end of file +keeper/.env +.env + +ts/client/src/scripts/archive/ts.ts \ No newline at end of file diff --git a/.github/workflows/ci-docker-fly-deploy.yml b/.github/workflows/ci-docker-fly-deploy.yml new file mode 100644 index 000000000..d7a80e3ae --- /dev/null +++ b/.github/workflows/ci-docker-fly-deploy.yml @@ -0,0 +1,35 @@ +name: Deploy to Fly + +on: + workflow_dispatch: + inputs: + appName: + description: 'Fly App Name' + required: true + type: string + imageName: + description: 'Image Name' + description: 'liquidator, keeper, mm, settler' + required: true + type: string + imageTag: + description: 'Docker Image Tag' + required: true + type: string + default: 'latest' + +env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Fly + uses: superfly/flyctl-actions/setup-flyctl@master + + - name: Deploy + run: flyctl deploy -c cd/${{ inputs.imageName }}.toml -a ${{ inputs.appName }} \ No newline at end of file diff --git a/cd/keeper.toml b/cd/keeper.toml new file mode 100644 index 000000000..8d126e8b9 --- /dev/null +++ b/cd/keeper.toml @@ -0,0 +1,6 @@ +app = "mango-keeper" +kill_signal = "SIGTERM" +kill_timeout = 30 + +[build] + dockerfile = "../bin/keeper/Dockerfile.keeper" diff --git a/cd/liquidator.toml b/cd/liquidator.toml new file mode 100644 index 000000000..b6d4f30c5 --- /dev/null +++ b/cd/liquidator.toml @@ -0,0 +1,6 @@ +app = "mango-liquidator" +kill_signal = "SIGTERM" +kill_timeout = 30 + +[build] + dockerfile = "../bin/liquidator/Dockerfile.liquidator" diff --git a/cd/mm.toml b/cd/mm.toml new file mode 100644 index 000000000..e72d808e0 --- /dev/null +++ b/cd/mm.toml @@ -0,0 +1,6 @@ +app = "mango-mm" +kill_signal = "SIGTERM" +kill_timeout = 30 + +[build] + dockerfile = "../ts/client/scripts/mm/Dockerfile.mm" diff --git a/ts/client/scripts/mm/Dockerfile.mm b/ts/client/scripts/mm/Dockerfile.mm new file mode 100644 index 000000000..79345fb45 --- /dev/null +++ b/ts/client/scripts/mm/Dockerfile.mm @@ -0,0 +1,35 @@ +FROM debian:bullseye as builder + +ARG NODE_VERSION=16.17.0 +ARG YARN_VERSION=1.22.19 + +RUN apt-get update; apt install -y curl +RUN curl https://get.volta.sh | bash +ENV VOLTA_HOME /root/.volta +ENV PATH /root/.volta/bin:$PATH +RUN volta install node@${NODE_VERSION} yarn@${YARN_VERSION} + +####################################################################### + +RUN mkdir /app +WORKDIR /app + +COPY . . + +RUN yarn install && yarn run build + +FROM debian:bullseye as run + +LABEL fly_launch_runtime="nodejs" + +COPY --from=builder /root/.volta /root/.volta +COPY --from=builder /app /app + +WORKDIR /app +ENV NODE_ENV production +ENV PATH /root/.volta/bin:$PATH + +RUN adduser --system --group --no-create-home mangouser +USER mangouser + +CMD [ "node", "dist/cjs/src/scripts/mm/market-maker.js" ] \ No newline at end of file From 7c02075c80f7391edd3652fc0354a68f2bd2377b Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 21 Mar 2023 12:51:21 +0100 Subject: [PATCH 18/48] Bump program version to v0.10.0 --- Cargo.lock | 2 +- programs/mango-v4/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ae867ab0..5f7ad81b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2964,7 +2964,7 @@ dependencies = [ [[package]] name = "mango-v4" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/programs/mango-v4/Cargo.toml b/programs/mango-v4/Cargo.toml index 6cb69e88a..e67941974 100644 --- a/programs/mango-v4/Cargo.toml +++ b/programs/mango-v4/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mango-v4" -version = "0.9.0" +version = "0.10.0" description = "Created with Anchor" edition = "2021" From cce8bb4b144d6a34b76c131c25095e27aaa9eaa7 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:37:01 +0100 Subject: [PATCH 19/48] liquidator docs (#512) * liquidator docs Signed-off-by: microwavedcola1 * update Signed-off-by: microwavedcola1 * update Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- bin/liquidator/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 bin/liquidator/README.md diff --git a/bin/liquidator/README.md b/bin/liquidator/README.md new file mode 100644 index 000000000..78bf10cd3 --- /dev/null +++ b/bin/liquidator/README.md @@ -0,0 +1,28 @@ +Two branches are relevant here: + +- `devnet`: bleeding edge, may be unstable, could be incompatible with deployed program +- `main`: stable, currently running on the `mainnet-beta` cluster + +## Setup Environment + +### .env Config file: + +A `.env` file can be used to configure the liquidator setup. See `.env.example` for a example. + +The environment variables required are + +- `LIQOR_MANGO_ACCOUNT` - public key of the mango account +- `LIQOR_OWNER` - private key of the owner of the mango account +- `RPC_URL` - RPC cluster url +- `SERUM_PROGRAM` - the Openbook program Id the mango group is configured with e.g. primary mango group `78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX` is configured to work with `srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX` + +more advanced parameters + +- `MIN_HEALTH_RATIO` - minimum health ratio the liquidator should retain (default 50%) +- `REBALANCE_SLIPPAGE_BPS` - slippage liquidator should tolerate when offloading tokens (default 100) + +```shell +cargo run --bin liquidator +``` + +There is also a dockerfile `Dockerfile.liquidator` available in case one wants to run this in a containerized environment. From d0a546381f7945ebf76c51312af8db9f136b813f Mon Sep 17 00:00:00 2001 From: Maximilian Schneider Date: Wed, 22 Mar 2023 02:01:15 +0400 Subject: [PATCH 20/48] Quality of life fixes (#511) * breaking: make pegLimit an optional arg * pass externally cached blockhashes to sendTransaction * convenience accessors for connection & walletPk on client --- ts/client/src/accounts/perp.ts | 2 +- ts/client/src/client.ts | 18 ++++++++++++++---- ts/client/src/utils/rpc.ts | 12 +++++++----- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/ts/client/src/accounts/perp.ts b/ts/client/src/accounts/perp.ts index fee65b3c0..ee24933a2 100644 --- a/ts/client/src/accounts/perp.ts +++ b/ts/client/src/accounts/perp.ts @@ -877,7 +877,7 @@ export class PerpOrder { priceLots = perpMarket.uiPriceToLots(perpMarket.uiPrice).add(priceOffset); const isInvalid = type === BookSideType.bids - ? priceLots.gt(leafNode.pegLimit) + ? priceLots.gt(leafNode.pegLimit) && !leafNode.pegLimit.eqn(-1) : leafNode.pegLimit.gt(priceLots); oraclePeggedProperties = { isInvalid, diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 64097a364..f76dad6aa 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -17,6 +17,7 @@ import { SystemProgram, TransactionInstruction, TransactionSignature, + Connection, } from '@solana/web3.js'; import bs58 from 'bs58'; import { Bank, MintInfo, TokenIndex } from './accounts/bank'; @@ -105,6 +106,15 @@ export class MangoClient { Error.stackTraceLimit = 1000; } + /// Convenience accessors + public get connection(): Connection { + return this.program.provider.connection; + } + + public get walletPk(): PublicKey { + return (this.program.provider as AnchorProvider).wallet.publicKey; + } + /// Transactions public async sendAndConfirmTransaction( ixs: TransactionInstruction[], @@ -2181,8 +2191,8 @@ export class MangoClient { perpMarketIndex: PerpMarketIndex, side: PerpOrderSide, priceOffset: number, - pegLimit: number, quantity: number, + pegLimit?: number, maxQuoteQuantity?: number, clientOrderId?: number, orderType?: PerpOrderType, @@ -2196,8 +2206,8 @@ export class MangoClient { perpMarketIndex, side, priceOffset, - pegLimit, quantity, + pegLimit, maxQuoteQuantity, clientOrderId, orderType, @@ -2215,8 +2225,8 @@ export class MangoClient { perpMarketIndex: PerpMarketIndex, side: PerpOrderSide, priceOffset: number, - pegLimit: number, quantity: number, + pegLimit?: number, maxQuoteQuantity?: number, clientOrderId?: number, orderType?: PerpOrderType, @@ -2238,7 +2248,7 @@ export class MangoClient { .perpPlaceOrderPegged( side, perpMarket.uiPriceToLots(priceOffset), - perpMarket.uiPriceToLots(pegLimit), + pegLimit ? perpMarket.uiPriceToLots(pegLimit) : new BN(-1), perpMarket.uiBaseToLots(quantity), maxQuoteQuantity ? perpMarket.uiQuoteToLots(maxQuoteQuantity) diff --git a/ts/client/src/utils/rpc.ts b/ts/client/src/utils/rpc.ts index 4613e1b6a..c8d45fcf0 100644 --- a/ts/client/src/utils/rpc.ts +++ b/ts/client/src/utils/rpc.ts @@ -16,11 +16,13 @@ export async function sendTransaction( opts: any = {}, ): Promise { const connection = provider.connection; - const latestBlockhash = await connection.getLatestBlockhash( - opts.preflightCommitment ?? - provider.opts.preflightCommitment ?? - 'finalized', - ); + const latestBlockhash = + opts.latestBlockhash ?? + (await connection.getLatestBlockhash( + opts.preflightCommitment ?? + provider.opts.preflightCommitment ?? + 'finalized', + )); const payer = (provider as AnchorProvider).wallet; From 89f936c14ae60fe3f3591514a0de67c2a4c61510 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:58:20 +0100 Subject: [PATCH 21/48] add script to sim accounts with leverage change (#514) Signed-off-by: microwavedcola1 --- .../mb-sim-accounts-with-param-change.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 ts/client/scripts/mb-sim-accounts-with-param-change.ts diff --git a/ts/client/scripts/mb-sim-accounts-with-param-change.ts b/ts/client/scripts/mb-sim-accounts-with-param-change.ts new file mode 100644 index 000000000..afecf1229 --- /dev/null +++ b/ts/client/scripts/mb-sim-accounts-with-param-change.ts @@ -0,0 +1,73 @@ +import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; +import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; +import fs from 'fs'; +import { HealthType } from '../src/accounts/mangoAccount'; +import { PerpMarketIndex } from '../src/accounts/perp'; +import { MangoClient } from '../src/client'; +import { MANGO_V4_ID } from '../src/constants'; +import { I80F48 } from '../src/numbers/I80F48'; +import { toUiDecimalsForQuote } from '../src/utils'; + +const CLUSTER_URL = + process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL; +const SOME_KEYPAIR = + process.env.PAYER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR; +const CLUSTER: Cluster = + (process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta'; + +const GROUP_PK = '78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'; + +async function main(): Promise { + const options = AnchorProvider.defaultOptions(); + const connection = new Connection(CLUSTER_URL!, options); + + const someKeypair = Keypair.fromSecretKey( + Buffer.from(JSON.parse(fs.readFileSync(SOME_KEYPAIR!, 'utf-8'))), + ); + + const someWallet = new Wallet(someKeypair); + const someProvider = new AnchorProvider(connection, someWallet, options); + const client = MangoClient.connect( + someProvider, + CLUSTER, + MANGO_V4_ID[CLUSTER], + { + idsSource: 'api', + }, + ); + + const group = await client.getGroup(new PublicKey(GROUP_PK)); + const btcPerpPerpMarket = group.perpMarketsMapByMarketIndex.get( + 0 as PerpMarketIndex, + )!; + + // e.g. change btc-perp leverage to 5/10x + btcPerpPerpMarket.maintBaseAssetWeight = I80F48.fromNumber(0.9); + btcPerpPerpMarket.initBaseAssetWeight = I80F48.fromNumber(0.8); + btcPerpPerpMarket.maintBaseLiabWeight = I80F48.fromNumber(1.1); + btcPerpPerpMarket.initBaseLiabWeight = I80F48.fromNumber(1.2); + + const mangoAccountsWithHealth = ( + await client.getAllMangoAccounts(group, true) + ) + .map((a) => { + return { account: a, health: a.getHealth(group, HealthType.maint) }; + }) + .sort((a, b) => a.health.toNumber() - b.health.toNumber()); + + for (const obj of mangoAccountsWithHealth) { + console.log( + `${obj.account.publicKey}: ${toUiDecimalsForQuote(obj.health).toFixed( + 2, + )} `, + ); + } + + process.exit(); +} + +try { + main(); +} catch (error) { + console.log(error); +} From 258d95d035ca84e2cd47c0ced22685888da30239 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:58:31 +0100 Subject: [PATCH 22/48] Fix bug: only account for borrows we are offsetting (#513) * Fix bug: only account for borrows we are offsetting Signed-off-by: microwavedcola1 * fix Signed-off-by: microwavedcola1 * Bank: Unittest for net borrow limits --------- Signed-off-by: microwavedcola1 Co-authored-by: Christian Kamm --- programs/mango-v4/src/state/bank.rs | 65 +++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index 66d51d375..0e11ddf68 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -1,6 +1,7 @@ use super::{OracleConfig, TokenIndex, TokenPosition}; use crate::accounts_zerocopy::KeyedAccountReader; use crate::error::*; +use crate::i80f48::ClampToInt; use crate::state::{oracle, StablePriceModel}; use crate::util; @@ -302,9 +303,8 @@ impl Bank { allow_dusting: bool, now_ts: u64, ) -> Result { - self.update_net_borrows(-native_amount, now_ts); let opening_indexed_position = position.indexed_position; - let result = self.deposit_internal(position, native_amount, allow_dusting)?; + let result = self.deposit_internal(position, native_amount, allow_dusting, now_ts)?; self.update_cumulative_interest(position, opening_indexed_position); Ok(result) } @@ -315,6 +315,7 @@ impl Bank { position: &mut TokenPosition, mut native_amount: I80F48, allow_dusting: bool, + now_ts: u64, ) -> Result { require_gte!(native_amount, 0); @@ -336,6 +337,9 @@ impl Bank { }; if native_position.is_negative() { + // Only account for the borrows we are repaying + self.update_net_borrows(native_position.max(-native_amount), now_ts); + let new_native_position = native_position + native_amount; let indexed_change = div_rounding_up(native_amount, self.borrow_index); // this is only correct if it's not positive, because it scales the whole amount by borrow_index @@ -585,13 +589,15 @@ impl Bank { let in_new_window = now_ts >= self.last_net_borrows_window_start_ts + self.net_borrow_limit_window_size_ts; + let amount = native_amount.ceil().clamp_to_i64(); + self.net_borrows_in_window = if in_new_window { // reset to latest window self.last_net_borrows_window_start_ts = now_ts / self.net_borrow_limit_window_size_ts * self.net_borrow_limit_window_size_ts; - native_amount.to_num::() + amount } else { - self.net_borrows_in_window + native_amount.to_num::() + self.net_borrows_in_window + amount }; } @@ -1015,4 +1021,55 @@ mod tests { compute_new_avg_utilization_runner(&mut bank, I80F48::ONE, 1040); assert_eq!(bank.avg_utilization, I80F48::ONE); } + + #[test] + pub fn test_net_borrows() -> Result<()> { + let mut bank = Bank::zeroed(); + bank.net_borrow_limit_window_size_ts = 100; + bank.net_borrow_limit_per_window_quote = 1000; + bank.deposit_index = I80F48::from_num(100.0); + bank.borrow_index = I80F48::from_num(100.0); + + let price = I80F48::from(2); + + let mut account = TokenPosition::default(); + + bank.change_without_fee(&mut account, I80F48::from(100), 0, price) + .unwrap(); + assert_eq!(bank.net_borrows_in_window, 0); + bank.change_without_fee(&mut account, I80F48::from(-100), 0, price) + .unwrap(); + assert_eq!(bank.net_borrows_in_window, 0); + + account = TokenPosition::default(); + bank.change_without_fee(&mut account, I80F48::from(10), 0, price) + .unwrap(); + bank.change_without_fee(&mut account, I80F48::from(-110), 0, price) + .unwrap(); + assert_eq!(bank.net_borrows_in_window, 100); + bank.change_without_fee(&mut account, I80F48::from(50), 0, price) + .unwrap(); + assert_eq!(bank.net_borrows_in_window, 50); + bank.change_without_fee(&mut account, I80F48::from(100), 0, price) + .unwrap(); + assert_eq!(bank.net_borrows_in_window, 1); // rounding + + account = TokenPosition::default(); + bank.net_borrows_in_window = 0; + bank.change_without_fee(&mut account, I80F48::from(-450), 0, price) + .unwrap(); + bank.change_without_fee(&mut account, I80F48::from(-51), 0, price) + .unwrap_err(); + + account = TokenPosition::default(); + bank.net_borrows_in_window = 0; + bank.change_without_fee(&mut account, I80F48::from(-450), 0, price) + .unwrap(); + bank.change_without_fee(&mut account, I80F48::from(-50), 0, price) + .unwrap(); + bank.change_without_fee(&mut account, I80F48::from(-50), 101, price) + .unwrap(); + + Ok(()) + } } From 797e0bf91b11078e9cfe525fe82ae98b449faece Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 22 Mar 2023 14:30:25 +0100 Subject: [PATCH 23/48] extend script, fix util function Signed-off-by: microwavedcola1 --- ts/client/scripts/mb-create-gov-ix.ts | 72 +++++++++++++++++++++++++++ ts/client/src/utils.ts | 4 +- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/ts/client/scripts/mb-create-gov-ix.ts b/ts/client/scripts/mb-create-gov-ix.ts index 991cc87eb..c10f21a97 100644 --- a/ts/client/scripts/mb-create-gov-ix.ts +++ b/ts/client/scripts/mb-create-gov-ix.ts @@ -11,9 +11,11 @@ import { } from '@solana/web3.js'; import fs from 'fs'; import { TokenIndex } from '../src/accounts/bank'; +import { PerpMarketIndex } from '../src/accounts/perp'; import { Builder } from '../src/builder'; import { MangoClient } from '../src/client'; import { + NullPerpEditParams, NullTokenEditParams, TrueIxGateParams, buildIxGate, @@ -279,6 +281,57 @@ async function perpCreate(): Promise { console.log(serializeInstructionToBase64(ix)); } +async function perpEdit(): Promise { + const client = await buildClient(); + const group = await client.getGroup(new PublicKey(GROUP_PK)); + const perpMarket = group.getPerpMarketByMarketIndex(0 as PerpMarketIndex); + const params = Builder(NullPerpEditParams) + .positivePnlLiquidationFee(bpsToDecimal(250)) + .build(); + const ix = await client.program.methods + .perpEditMarket( + params.oracle, + params.oracleConfig, + params.baseDecimals, + params.maintBaseAssetWeight, + params.initBaseAssetWeight, + params.maintBaseLiabWeight, + params.initBaseLiabWeight, + params.maintOverallAssetWeight, + params.initOverallAssetWeight, + params.baseLiquidationFee, + params.makerFee, + params.takerFee, + params.minFunding, + params.maxFunding, + params.impactQuantity !== null ? new BN(params.impactQuantity) : null, + params.groupInsuranceFund, + params.feePenalty, + params.settleFeeFlat, + params.settleFeeAmountThreshold, + params.settleFeeFractionLowHealth, + params.stablePriceDelayIntervalSeconds, + params.stablePriceDelayGrowthLimit, + params.stablePriceGrowthLimit, + params.settlePnlLimitFactor, + params.settlePnlLimitWindowSize !== null + ? new BN(params.settlePnlLimitWindowSize) + : null, + params.reduceOnly, + params.resetStablePrice ?? false, + params.positivePnlLiquidationFee, + params.name, + ) + .accounts({ + group: group.publicKey, + oracle: params.oracle ?? perpMarket.oracle, + admin: group.admin, + perpMarket: perpMarket.publicKey, + }) + .instruction(); + console.log(serializeInstructionToBase64(ix)); +} + async function ixDisable(): Promise { const client = await buildClient(); @@ -297,13 +350,32 @@ async function ixDisable(): Promise { console.log(await serializeInstructionToBase64(ix)); } +async function createMangoAccount(): Promise { + const client = await buildClient(); + + const group = await client.getGroup(new PublicKey(GROUP_PK)); + + const ix = await client.program.methods + .accountCreate(0, 8, 8, 8, 32, 'Mango DAO 0') + .accounts({ + group: group.publicKey, + owner: new PublicKey('5tgfd6XgwiXB9otEnzFpXK11m7Q7yZUaAJzWK4oT5UGF'), + payer: new PublicKey('5tgfd6XgwiXB9otEnzFpXK11m7Q7yZUaAJzWK4oT5UGF'), + }) + .instruction(); + + console.log(await serializeInstructionToBase64(ix)); +} + async function main(): Promise { try { // await tokenRegister(); // await tokenEdit(); // await perpCreate(); + await perpEdit(); // await serum3Register(); // await ixDisable(); + // await createMangoAccount(); } catch (error) { console.log(error); } diff --git a/ts/client/src/utils.ts b/ts/client/src/utils.ts index 7ed7ae5f2..f59fb728a 100644 --- a/ts/client/src/utils.ts +++ b/ts/client/src/utils.ts @@ -1,5 +1,4 @@ import { AnchorProvider } from '@coral-xyz/anchor'; -import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from './utils/spl'; import { AddressLookupTableAccount, MessageV0, @@ -11,6 +10,7 @@ import { } from '@solana/web3.js'; import BN from 'bn.js'; import { I80F48 } from './numbers/I80F48'; +import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from './utils/spl'; /// /// numeric helpers @@ -19,7 +19,7 @@ export const U64_MAX_BN = new BN('18446744073709551615'); export const I64_MAX_BN = new BN('9223372036854775807').toTwos(64); export function bpsToDecimal(bps: number): number { - return bps / 1000; + return bps / 10000; } export function percentageToDecimal(percentage: number): number { From f866a49aa93128a237a59e70eff32ec17c859359 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Mon, 27 Mar 2023 10:39:01 +0200 Subject: [PATCH 24/48] Fix interest rate computation in client (#520) Signed-off-by: microwavedcola1 --- ts/client/src/accounts/bank.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index bd0029996..c59d1b348 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -410,21 +410,19 @@ export class Bank implements BankForHealth { } const utilization = totalBorrows.div(totalDeposits); - if (utilization.gt(this.util1)) { + if (utilization.lte(this.util0)) { + const slope = this.rate0.div(this.util0); + return slope.mul(utilization); + } else if (utilization.lt(this.util1)) { + const extraUtil = utilization.sub(this.util0); + const slope = this.rate1.sub(this.rate0).div(this.util1.sub(this.util0)); + return this.rate0.add(slope.mul(extraUtil)); + } else { const extraUtil = utilization.sub(this.util1); const slope = this.maxRate .sub(this.rate1) .div(I80F48.fromNumber(1).sub(this.util1)); return this.rate1.add(slope.mul(extraUtil)); - } else if (utilization.gt(this.util0)) { - const extraUtil = utilization.sub(this.util0); - const slope = this.maxRate - .sub(this.rate0) - .div(I80F48.fromNumber(1).sub(this.util0)); - return this.rate0.add(slope.mul(extraUtil)); - } else { - const slope = this.rate0.div(this.util0); - return slope.mul(utilization); } } From ef23d1406e651119de023ed34b998382abf546e6 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Mon, 27 Mar 2023 13:34:12 +0200 Subject: [PATCH 25/48] minor ts fixes Signed-off-by: microwavedcola1 --- ts/client/scripts/mb-admin.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/ts/client/scripts/mb-admin.ts b/ts/client/scripts/mb-admin.ts index 4c13b2163..b3cd173fb 100644 --- a/ts/client/scripts/mb-admin.ts +++ b/ts/client/scripts/mb-admin.ts @@ -684,7 +684,7 @@ async function createAndPopulateAlt() { // Extend using mango v4 relevant pub keys try { - let bankAddresses = Array.from(group.banksMapByMint.values()) + const bankAddresses = Array.from(group.banksMapByMint.values()) .flat() .map((bank) => [bank.publicKey, bank.oracle, bank.vault]) .flat() @@ -694,13 +694,13 @@ async function createAndPopulateAlt() { .map((mintInfo) => mintInfo.publicKey), ); - let serum3MarketAddresses = Array.from( + const serum3MarketAddresses = Array.from( group.serum3MarketsMapByExternal.values(), ) .flat() .map((serum3Market) => serum3Market.publicKey); - let serum3ExternalMarketAddresses = Array.from( + const serum3ExternalMarketAddresses = Array.from( group.serum3ExternalMarketsMap.values(), ) .flat() @@ -711,7 +711,7 @@ async function createAndPopulateAlt() { ]) .flat(); - let perpMarketAddresses = Array.from( + const perpMarketAddresses = Array.from( group.perpMarketsMapByMarketIndex.values(), ) .flat() @@ -724,7 +724,8 @@ async function createAndPopulateAlt() { ]) .flat(); - async function extendTable(addresses: PublicKey[]) { + // eslint-disable-next-line no-inner-declarations + async function extendTable(addresses: PublicKey[]): Promise { await group.reloadAll(client); const alt = await client.program.provider.connection.getAddressLookupTable( @@ -751,7 +752,7 @@ async function createAndPopulateAlt() { client.program.provider as AnchorProvider, [extendIx], ); - let sig = await client.program.provider.connection.sendTransaction( + const sig = await client.program.provider.connection.sendTransaction( extendTx, ); console.log(`https://explorer.solana.com/tx/${sig}`); @@ -765,7 +766,7 @@ async function createAndPopulateAlt() { await extendTable(serum3ExternalMarketAddresses); // TODO: dont extend for perps atm - // await extendTable(perpMarketAddresses); + await extendTable(perpMarketAddresses); // Well known addressess await extendTable([ @@ -819,8 +820,10 @@ async function main() { } try { - // createAndPopulateAlt(); - } catch (error) {} + createAndPopulateAlt(); + } catch (error) { + console.log(error); + } } try { @@ -920,8 +923,8 @@ async function deregisterSerum3Markets() { const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); // change xxx/xxx to market of choice - let serum3Market = group.getSerum3MarketByName('XXX/XXX'); - let sig = await client.serum3deregisterMarket( + const serum3Market = group.getSerum3MarketByName('XXX/XXX'); + const sig = await client.serum3deregisterMarket( group, serum3Market.serumMarketExternal, ); @@ -938,8 +941,8 @@ async function deregisterTokens() { const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); // change -1 to tokenIndex of choice - let bank = group.getFirstBankByTokenIndex(-1 as TokenIndex); - let sig = await client.tokenDeregister(group, bank.mint); + const bank = group.getFirstBankByTokenIndex(-1 as TokenIndex); + const sig = await client.tokenDeregister(group, bank.mint); console.log( `...removed token ${bank.name}, sig https://explorer.solana.com/tx/${sig}`, ); From 30984bbef12d0e1f27982d246c5956f533491f44 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Wed, 29 Mar 2023 09:46:06 +0200 Subject: [PATCH 27/48] Use new shared mango-feeds-connector crate for chain_data (#515) --- Cargo.lock | 141 +++++++++++++- lib/client/Cargo.toml | 1 + lib/client/src/account_update_stream.rs | 6 +- lib/client/src/chain_data.rs | 248 +----------------------- lib/client/src/chain_data_fetcher.rs | 26 ++- 5 files changed, 159 insertions(+), 263 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f7ad81b1..89f175160 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,6 +625,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.5.3" @@ -821,6 +827,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -2962,6 +2978,31 @@ dependencies = [ "libc", ] +[[package]] +name = "mango-feeds-connector" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f19edbc11d424f24901120ec7eb09ac5c49b1da805fb93c47950febbf088433" +dependencies = [ + "anyhow", + "async-channel", + "async-trait", + "futures 0.3.25", + "jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core-client", + "log 0.4.17", + "rustls", + "serde", + "serde_derive", + "solana-account-decoder", + "solana-client", + "solana-rpc", + "solana-sdk", + "tokio", + "warp", + "yellowstone-grpc-proto", +] + [[package]] name = "mango-v4" version = "0.10.0" @@ -3042,6 +3083,7 @@ dependencies = [ "jsonrpc-core 18.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core-client", "log 0.4.17", + "mango-feeds-connector", "mango-v4", "pyth-sdk-solana", "reqwest", @@ -3401,6 +3443,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "quick-error", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -4215,6 +4275,12 @@ dependencies = [ "percent-encoding 2.2.0", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-protobuf" version = "0.8.0" @@ -4628,7 +4694,7 @@ dependencies = [ "percent-encoding 2.2.0", "pin-project-lite", "rustls", - "rustls-pemfile 1.0.1", + "rustls-pemfile 1.0.2", "serde", "serde_json", "serde_urlencoded", @@ -4785,9 +4851,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log 0.4.17", "ring", @@ -4802,7 +4868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.1", + "rustls-pemfile 1.0.2", "schannel", "security-framework", ] @@ -4818,11 +4884,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.13.1", + "base64 0.21.0", ] [[package]] @@ -4892,6 +4958,12 @@ dependencies = [ "syn 1.0.105", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -7241,7 +7313,7 @@ dependencies = [ "pin-project", "prost", "prost-derive", - "rustls-pemfile 1.0.1", + "rustls-pemfile 1.0.2", "tokio", "tokio-rustls", "tokio-stream", @@ -7459,6 +7531,15 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typeable" version = "0.1.2" @@ -7688,6 +7769,37 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" +dependencies = [ + "bytes 1.3.0", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.23", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "multipart", + "percent-encoding 2.2.0", + "pin-project", + "rustls-pemfile 0.2.1", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.17.2", + "tokio-util 0.7.2", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -8076,6 +8188,19 @@ dependencies = [ "time 0.3.17", ] +[[package]] +name = "yellowstone-grpc-proto" +version = "1.0.1+solana.1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f80b9fe53333bb46c7dec611089faef2553ad6ced1c6dde52d78d2eaaf1db5c" +dependencies = [ + "anyhow", + "prost", + "protobuf-src", + "tonic", + "tonic-build", +] + [[package]] name = "zeroize" version = "1.3.0" diff --git a/lib/client/Cargo.toml b/lib/client/Cargo.toml index ae6bae500..a44d94df7 100644 --- a/lib/client/Cargo.toml +++ b/lib/client/Cargo.toml @@ -28,6 +28,7 @@ solana-client = "~1.14.9" solana-rpc = "~1.14.9" solana-sdk = "~1.14.9" solana-address-lookup-table-program = "~1.14.9" +mango-feeds-connector = "0.1.0" spl-associated-token-account = "1.0.3" thiserror = "1.0.31" log = "0.4" diff --git a/lib/client/src/account_update_stream.rs b/lib/client/src/account_update_stream.rs index 671a9c2c6..59487d629 100644 --- a/lib/client/src/account_update_stream.rs +++ b/lib/client/src/account_update_stream.rs @@ -44,9 +44,10 @@ impl Message { trace!("websocket account message"); chain.update_account( account_write.pubkey, - AccountAndSlot { + AccountData { slot: account_write.slot, account: account_write.account.clone(), + write_version: 1, }, ); } @@ -54,9 +55,10 @@ impl Message { for account_update in snapshot { chain.update_account( account_update.pubkey, - chain_data::AccountAndSlot { + chain_data::AccountData { slot: account_update.slot, account: account_update.account.clone(), + write_version: 0, }, ); } diff --git a/lib/client/src/chain_data.rs b/lib/client/src/chain_data.rs index e875ca659..c07f5dd6e 100644 --- a/lib/client/src/chain_data.rs +++ b/lib/client/src/chain_data.rs @@ -1,248 +1,2 @@ -use { - solana_sdk::account::AccountSharedData, solana_sdk::pubkey::Pubkey, std::collections::HashMap, -}; - pub use crate::chain_data_fetcher::AccountFetcher; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SlotStatus { - Rooted, - Confirmed, - Processed, -} - -#[derive(Clone, Debug)] -pub struct SlotData { - pub slot: u64, - pub parent: Option, - pub status: SlotStatus, - pub chain: u64, // the top slot that this is in a chain with. uncles will have values < tip -} - -#[derive(Clone, Debug)] -pub struct AccountAndSlot { - pub slot: u64, - pub account: AccountSharedData, -} - -/// Track slots and account writes -/// -/// - use account() to retrieve the current best data for an account. -/// - update_from_snapshot() and update_from_websocket() update the state for new messages -pub struct ChainData { - /// only slots >= newest_rooted_slot are retained - slots: HashMap, - /// writes to accounts, only the latest rooted write an newer are retained - accounts: HashMap>, - newest_rooted_slot: u64, - newest_processed_slot: u64, - best_chain_slot: u64, -} - -impl ChainData { - pub fn new() -> Self { - Self { - slots: HashMap::new(), - accounts: HashMap::new(), - newest_rooted_slot: 0, - newest_processed_slot: 0, - best_chain_slot: 0, - } - } - - pub fn update_slot(&mut self, new_slot: SlotData) { - let new_processed_head = new_slot.slot > self.newest_processed_slot; - if new_processed_head { - self.newest_processed_slot = new_slot.slot; - } - - let new_rooted_head = - new_slot.slot > self.newest_rooted_slot && new_slot.status == SlotStatus::Rooted; - if new_rooted_head { - self.newest_rooted_slot = new_slot.slot; - } - - // Use the highest slot that has a known parent as best chain - // (sometimes slots OptimisticallyConfirm before we even know the parent!) - let new_best_chain = new_slot.parent.is_some() && new_slot.slot > self.best_chain_slot; - if new_best_chain { - self.best_chain_slot = new_slot.slot; - } - - let mut parent_update = false; - - use std::collections::hash_map::Entry; - match self.slots.entry(new_slot.slot) { - Entry::Vacant(v) => { - v.insert(new_slot); - } - Entry::Occupied(o) => { - let v = o.into_mut(); - parent_update = v.parent != new_slot.parent && new_slot.parent.is_some(); - v.parent = v.parent.or(new_slot.parent); - if v.status == SlotStatus::Processed || new_slot.status == SlotStatus::Rooted { - v.status = new_slot.status; - } - } - }; - - if new_best_chain || parent_update { - // update the "chain" field down to the first rooted slot - let mut slot = self.best_chain_slot; - loop { - if let Some(data) = self.slots.get_mut(&slot) { - data.chain = self.best_chain_slot; - if data.status == SlotStatus::Rooted { - break; - } - if let Some(parent) = data.parent { - slot = parent; - continue; - } - } - break; - } - } - - if new_rooted_head { - // for each account, preserve only writes > newest_rooted_slot, or the newest - // rooted write - for (_, writes) in self.accounts.iter_mut() { - let newest_rooted_write = writes - .iter() - .rev() - .find(|w| { - w.slot <= self.newest_rooted_slot - && self - .slots - .get(&w.slot) - .map(|s| { - // sometimes we seem not to get notifications about slots - // getting rooted, hence assume non-uncle slots < newest_rooted_slot - // are rooted too - s.status == SlotStatus::Rooted - || s.chain == self.best_chain_slot - }) - // preserved account writes for deleted slots <= newest_rooted_slot - // are expected to be rooted - .unwrap_or(true) - }) - .map(|w| w.slot) - // no rooted write found: produce no effect, since writes > newest_rooted_slot are retained anyway - .unwrap_or(self.newest_rooted_slot + 1); - writes - .retain(|w| w.slot == newest_rooted_write || w.slot > self.newest_rooted_slot); - } - - // now it's fine to drop any slots before the new rooted head - // as account writes for non-rooted slots before it have been dropped - self.slots.retain(|s, _| *s >= self.newest_rooted_slot); - } - } - - pub fn update_account(&mut self, pubkey: Pubkey, account: AccountAndSlot) { - use std::collections::hash_map::Entry; - match self.accounts.entry(pubkey) { - Entry::Vacant(v) => { - v.insert(vec![account]); - } - Entry::Occupied(o) => { - let v = o.into_mut(); - // v is ordered by slot ascending. find the right position - // overwrite if an entry for the slot already exists, otherwise insert - let rev_pos = v - .iter() - .rev() - .position(|d| d.slot <= account.slot) - .unwrap_or(v.len()); - let pos = v.len() - rev_pos; - if pos < v.len() && v[pos].slot == account.slot { - v[pos] = account; - } else { - v.insert(pos, account); - } - } - }; - } - - pub fn update_from_rpc(&mut self, pubkey: &Pubkey, account: AccountAndSlot) { - // Add a stub slot if the rpc has information about the future. - // If it's in the past, either the slot already exists (and maybe we have - // the data already), or it was a skipped slot and adding it now makes no difference. - if account.slot > self.best_chain_slot { - self.update_slot(SlotData { - slot: account.slot, - parent: Some(self.best_chain_slot), - status: SlotStatus::Processed, - chain: 0, - }); - } - - self.update_account(*pubkey, account) - } - - fn is_account_write_live(&self, write: &AccountAndSlot) -> bool { - self.slots - .get(&write.slot) - // either the slot is rooted, in the current chain or newer than the chain head - .map(|s| { - s.status == SlotStatus::Rooted - || s.chain == self.best_chain_slot - || write.slot > self.best_chain_slot - }) - // if the slot can't be found but preceeds newest rooted, use it too (old rooted slots are removed) - .unwrap_or(write.slot <= self.newest_rooted_slot) - } - - /// Cloned snapshot of all the most recent live writes per pubkey - pub fn accounts_snapshot(&self) -> HashMap { - self.accounts - .iter() - .filter_map(|(pubkey, writes)| { - let latest_good_write = writes - .iter() - .rev() - .find(|w| self.is_account_write_live(w))?; - Some((*pubkey, latest_good_write.clone())) - }) - .collect() - } - - /// Ref to the most recent live write of the pubkey - pub fn account<'a>(&'a self, pubkey: &Pubkey) -> anyhow::Result<&'a AccountSharedData> { - self.account_and_slot(pubkey).map(|w| &w.account) - } - - /// Ref to the most recent live write of the pubkey, along with the slot that it was for - pub fn account_and_slot<'a>(&'a self, pubkey: &Pubkey) -> anyhow::Result<&'a AccountAndSlot> { - self.accounts - .get(pubkey) - .ok_or_else(|| anyhow::anyhow!("account {} not found", pubkey))? - .iter() - .rev() - .find(|w| self.is_account_write_live(w)) - .ok_or_else(|| anyhow::anyhow!("account {} has no live data", pubkey)) - } - - pub fn iter_accounts<'a>(&'a self) -> impl Iterator { - self.accounts.iter().filter_map(|(pk, writes)| { - writes - .iter() - .rev() - .find(|w| self.is_account_write_live(w)) - .map(|latest_write| (pk, latest_write)) - }) - } - - pub fn slots_count(&self) -> usize { - self.slots.len() - } - - pub fn accounts_count(&self) -> usize { - self.accounts.len() - } - - pub fn account_writes_count(&self) -> usize { - self.accounts.values().map(|v| v.len()).sum() - } -} +pub use mango_feeds_connector::chain_data::*; diff --git a/lib/client/src/chain_data_fetcher.rs b/lib/client/src/chain_data_fetcher.rs index c6058f3a7..a0cc210c1 100644 --- a/lib/client/src/chain_data_fetcher.rs +++ b/lib/client/src/chain_data_fetcher.rs @@ -75,8 +75,8 @@ impl AccountFetcher { let chain_data = self.chain_data.read().unwrap(); Ok(chain_data .account(address) - .with_context(|| format!("fetch account {} via chain_data", address))? - .clone()) + .map(|d| d.account.clone()) + .with_context(|| format!("fetch account {} via chain_data", address))?) } pub async fn refresh_account_via_rpc(&self, address: &Pubkey) -> anyhow::Result { @@ -92,11 +92,25 @@ impl AccountFetcher { .with_context(|| format!("refresh account {} via rpc", address))?; let mut chain_data = self.chain_data.write().unwrap(); - chain_data.update_from_rpc( - address, - AccountAndSlot { - slot: response.context.slot, + let best_chain_slot = chain_data.best_chain_slot(); + + // The RPC can get information for slots that haven't been seen yet on chaindata. That means + // that the rpc thinks that slot is valid. Make it so by telling chain data about it. + if best_chain_slot < slot { + chain_data.update_slot(SlotData { + slot, + parent: Some(best_chain_slot), + status: SlotStatus::Processed, + chain: 0, + }); + } + + chain_data.update_account( + *address, + AccountData { + slot, account: account.into(), + write_version: 1, }, ); From 6be7810c5612a6e3a0b0bf93a102f05c76ddd629 Mon Sep 17 00:00:00 2001 From: riordanp Date: Wed, 29 Mar 2023 15:51:32 +0100 Subject: [PATCH 28/48] Add prometheus metrics to crank (#517) * Add prometheus metrics to keeper --- Cargo.lock | 24 +++++++++++ bin/keeper/Cargo.toml | 3 ++ bin/keeper/src/crank.rs | 88 +++++++++++++++++++++++++++++++++++++---- cd/keeper.toml | 4 ++ 4 files changed, 112 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89f175160..6782c6272 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3116,14 +3116,17 @@ dependencies = [ "fixed", "futures 0.3.25", "itertools 0.10.5", + "lazy_static", "log 0.4.17", "mango-v4", "mango-v4-client", + "prometheus", "pyth-sdk-solana", "serum_dex 0.5.10", "solana-client", "solana-sdk", "tokio", + "warp", ] [[package]] @@ -4153,6 +4156,21 @@ dependencies = [ "yansi", ] +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.1", + "protobuf", + "thiserror", +] + [[package]] name = "prost" version = "0.11.3" @@ -4208,6 +4226,12 @@ dependencies = [ "prost", ] +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + [[package]] name = "protobuf-src" version = "1.1.0+21.5" diff --git a/bin/keeper/Cargo.toml b/bin/keeper/Cargo.toml index 6c1a23294..7a260a365 100644 --- a/bin/keeper/Cargo.toml +++ b/bin/keeper/Cargo.toml @@ -26,3 +26,6 @@ serum_dex = { git = "https://github.com/openbook-dex/program.git", default-featu solana-client = "~1.14.9" solana-sdk = "~1.14.9" tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] } +prometheus = "0.13.3" +warp = "0.3.3" +lazy_static = "1.4.0" diff --git a/bin/keeper/src/crank.rs b/bin/keeper/src/crank.rs index f4c7f5ca5..572a03721 100644 --- a/bin/keeper/src/crank.rs +++ b/bin/keeper/src/crank.rs @@ -6,14 +6,72 @@ use itertools::Itertools; use anchor_lang::{__private::bytemuck::cast_ref, solana_program}; use futures::Future; use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex}; +use prometheus::{register_histogram, Encoder, Histogram, IntCounter, Registry}; use solana_sdk::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; use tokio::time; +use warp::Filter; + +lazy_static::lazy_static! { + pub static ref METRICS_REGISTRY: Registry = Registry::new_custom(Some("keeper".to_string()), None).unwrap(); + pub static ref METRIC_UPDATE_TOKENS_SUCCESS: IntCounter = + IntCounter::new("update_tokens_success", "Successful update token transactions").unwrap(); + pub static ref METRIC_UPDATE_TOKENS_FAILURE: IntCounter = + IntCounter::new("update_tokens_failure", "Failed update token transactions").unwrap(); + pub static ref METRIC_CONSUME_EVENTS_SUCCESS: IntCounter = + IntCounter::new("consume_events_success", "Successful consume events transactions").unwrap(); + pub static ref METRIC_CONSUME_EVENTS_FAILURE: IntCounter = + IntCounter::new("consume_events_failure", "Failed consume events transactions").unwrap(); + pub static ref METRIC_UPDATE_FUNDING_SUCCESS: IntCounter = + IntCounter::new("update_funding_success", "Successful update funding transactions").unwrap(); + pub static ref METRIC_UPDATE_FUNDING_FAILURE: IntCounter = + IntCounter::new("update_funding_failure", "Failed update funding transactions").unwrap(); + pub static ref METRIC_CONFIRMATION_TIMES: Histogram = register_histogram!( + "confirmation_times", "Transaction confirmation times", + vec![1000.0, 3000.0, 5000.0, 7000.0, 10000.0, 15000.0, 20000.0, 30000.0, 40000.0, 50000.0, 60000.0] + ).unwrap(); +} // TODO: move instructions into the client proper +async fn serve_metrics() { + METRICS_REGISTRY + .register(Box::new(METRIC_UPDATE_TOKENS_SUCCESS.clone())) + .unwrap(); + METRICS_REGISTRY + .register(Box::new(METRIC_UPDATE_TOKENS_FAILURE.clone())) + .unwrap(); + METRICS_REGISTRY + .register(Box::new(METRIC_CONSUME_EVENTS_SUCCESS.clone())) + .unwrap(); + METRICS_REGISTRY + .register(Box::new(METRIC_CONSUME_EVENTS_FAILURE.clone())) + .unwrap(); + METRICS_REGISTRY + .register(Box::new(METRIC_UPDATE_FUNDING_SUCCESS.clone())) + .unwrap(); + METRICS_REGISTRY + .register(Box::new(METRIC_UPDATE_FUNDING_FAILURE.clone())) + .unwrap(); + METRICS_REGISTRY + .register(Box::new(METRIC_CONFIRMATION_TIMES.clone())) + .unwrap(); + + let metrics_route = warp::path!("metrics").map(|| { + let mut buffer = Vec::::new(); + let encoder = prometheus::TextEncoder::new(); + encoder + .encode(&METRICS_REGISTRY.gather(), &mut buffer) + .unwrap(); + + String::from_utf8(buffer.clone()).unwrap() + }); + println!("Metrics server starting on port 9091"); + warp::serve(metrics_route).run(([0, 0, 0, 0], 9091)).await; +} + pub async fn runner( mango_client: Arc, debugging_handle: impl Future, @@ -83,7 +141,8 @@ pub async fn runner( mango_client.clone(), interval_check_new_listings_and_abort ), - debugging_handle + serve_metrics(), + debugging_handle, ); Ok(()) @@ -165,19 +224,24 @@ pub async fn loop_update_index_and_rate( .send_and_confirm_permissionless_tx(instructions) .await; + let confirmation_time = pre.elapsed().as_millis(); + METRIC_CONFIRMATION_TIMES.observe(confirmation_time as f64); + if let Err(e) = sig_result { + METRIC_UPDATE_TOKENS_FAILURE.inc(); log::info!( "metricName=UpdateTokensV4Failure tokens={} durationMs={} error={}", token_names, - pre.elapsed().as_millis(), + confirmation_time, e ); log::error!("{:?}", e) } else { + METRIC_UPDATE_TOKENS_SUCCESS.inc(); log::info!( "metricName=UpdateTokensV4Success tokens={} durationMs={}", token_names, - pre.elapsed().as_millis(), + confirmation_time, ); log::info!("{:?}", sig_result); } @@ -278,20 +342,25 @@ pub async fn loop_consume_events( let sig_result = client.send_and_confirm_permissionless_tx(vec![ix]).await; + let confirmation_time = pre.elapsed().as_millis(); + METRIC_CONFIRMATION_TIMES.observe(confirmation_time as f64); + if let Err(e) = sig_result { + METRIC_CONSUME_EVENTS_FAILURE.inc(); log::info!( "metricName=ConsumeEventsV4Failure market={} durationMs={} consumed={} error={}", perp_market.name(), - pre.elapsed().as_millis(), + confirmation_time, num_of_events, e.to_string() ); log::error!("{:?}", e) } else { + METRIC_CONSUME_EVENTS_SUCCESS.inc(); log::info!( "metricName=ConsumeEventsV4Success market={} durationMs={} consumed={}", perp_market.name(), - pre.elapsed().as_millis(), + confirmation_time, num_of_events, ); log::info!("{:?}", sig_result); @@ -328,19 +397,24 @@ pub async fn loop_update_funding( }; let sig_result = client.send_and_confirm_permissionless_tx(vec![ix]).await; + let confirmation_time = pre.elapsed().as_millis(); + METRIC_CONFIRMATION_TIMES.observe(confirmation_time as f64); + if let Err(e) = sig_result { + METRIC_UPDATE_FUNDING_FAILURE.inc(); log::error!( "metricName=UpdateFundingV4Error market={} durationMs={} error={}", perp_market.name(), - pre.elapsed().as_millis(), + confirmation_time, e.to_string() ); log::error!("{:?}", e) } else { + METRIC_UPDATE_FUNDING_SUCCESS.inc(); log::info!( "metricName=UpdateFundingV4Success market={} durationMs={}", perp_market.name(), - pre.elapsed().as_millis(), + confirmation_time, ); log::info!("{:?}", sig_result); } diff --git a/cd/keeper.toml b/cd/keeper.toml index 8d126e8b9..2ea15a03b 100644 --- a/cd/keeper.toml +++ b/cd/keeper.toml @@ -4,3 +4,7 @@ kill_timeout = 30 [build] dockerfile = "../bin/keeper/Dockerfile.keeper" + +[metrics] + port = 9091 + path = "/metrics" \ No newline at end of file From d0cf3e6b42e9df0675eb446f5003d63762e6e641 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 30 Mar 2023 10:10:00 +0200 Subject: [PATCH 29/48] update script Signed-off-by: microwavedcola1 --- ts/client/scripts/debug-scripts/debug-perp.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ts/client/scripts/debug-scripts/debug-perp.ts b/ts/client/scripts/debug-scripts/debug-perp.ts index 4dd57a9c5..c242eebc8 100644 --- a/ts/client/scripts/debug-scripts/debug-perp.ts +++ b/ts/client/scripts/debug-scripts/debug-perp.ts @@ -3,6 +3,7 @@ import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; import * as dotenv from 'dotenv'; import { MangoClient } from '../../src/client'; import { MANGO_V4_ID } from '../../src/constants'; +import { toUiDecimalsForQuote } from '../../src/utils'; dotenv.config(); const CLUSTER_URL = @@ -114,6 +115,12 @@ async function main(): Promise { .toFixed(4) .padStart(10)}`, ); + console.log( + `- perp.feesAccrued ${toUiDecimalsForQuote(perpMarket.feesAccrued)}`, + ); + console.log( + `- perp.feesSettled ${toUiDecimalsForQuote(perpMarket.feesSettled)}`, + ); console.log( `- unsettled pnl aggr - base position aggr * price ${( getUnsettledPnlUiAgg - @@ -122,7 +129,13 @@ async function main(): Promise { .toFixed(4) .padStart(10)}`, ); - console.log(); + console.log( + `- perp.feesAccrued - perp.feesSettled - unsettled pnl aggr ${ + toUiDecimalsForQuote( + perpMarket.feesAccrued.sub(perpMarket.feesSettled), + ) - getUnsettledPnlUiAgg + }`, + ); }); process.exit(); From 613b062e8fb672a5fa5ce735c236d008edf8b1fb Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 30 Mar 2023 09:39:49 +0200 Subject: [PATCH 30/48] fix bug where unrealised profit was not abs'ed Signed-off-by: microwavedcola1 --- ts/client/src/accounts/mangoAccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index fbc6ef76d..b63b4bf90 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -1478,7 +1478,7 @@ export class PerpPosition { const baseNative = I80F48.fromI64( this.basePositionLots.mul(perpMarket.baseLotSize), - ); + ).abs(); const positionValue = I80F48.fromNumber( perpMarket.stablePriceModel.stablePrice, ) From 9b9ad707c207e1fb52b3ce5054a8c5f737a2af2e Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 30 Mar 2023 10:40:33 +0200 Subject: [PATCH 31/48] Fix script Signed-off-by: microwavedcola1 --- ts/client/scripts/debug-scripts/debug-perp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/client/scripts/debug-scripts/debug-perp.ts b/ts/client/scripts/debug-scripts/debug-perp.ts index c242eebc8..7faff9f19 100644 --- a/ts/client/scripts/debug-scripts/debug-perp.ts +++ b/ts/client/scripts/debug-scripts/debug-perp.ts @@ -130,10 +130,10 @@ async function main(): Promise { .padStart(10)}`, ); console.log( - `- perp.feesAccrued - perp.feesSettled - unsettled pnl aggr ${ + `- perp.feesAccrued - perp.feesSettled + unsettled pnl aggr ${ toUiDecimalsForQuote( perpMarket.feesAccrued.sub(perpMarket.feesSettled), - ) - getUnsettledPnlUiAgg + ) + getUnsettledPnlUiAgg }`, ); }); From 7d389275a83b62ca1e3a8e3ac5a7d14cc58310ab Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 30 Mar 2023 17:00:28 +0200 Subject: [PATCH 32/48] update script Signed-off-by: microwavedcola1 --- ts/client/scripts/debug-scripts/debug-perp.ts | 81 +++++-------------- 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/ts/client/scripts/debug-scripts/debug-perp.ts b/ts/client/scripts/debug-scripts/debug-perp.ts index 7faff9f19..a64ee8fc6 100644 --- a/ts/client/scripts/debug-scripts/debug-perp.ts +++ b/ts/client/scripts/debug-scripts/debug-perp.ts @@ -26,7 +26,7 @@ async function main(): Promise { }); const group = await client.getGroup(new PublicKey(GROUP_PK)); - const mangoAccounts = await client.getAllMangoAccounts(group); + const mangoAccounts = await client.getAllMangoAccounts(group, true); Array.from(group.perpMarketsMapByMarketIndex.values()) .filter((perpMarket) => perpMarket.name != 'SOMETHING-PERP') @@ -34,6 +34,7 @@ async function main(): Promise { console.log(`name ${perpMarket.name}`); let getUnsettledPnlUiAgg = 0; let getBasePositionUiAgg = 0; + let getQuotePositionUiAgg = 0; let longSettledFundingAgg = 0; let shortSettledFundingAgg = 0; mangoAccounts.map((mangoAccount) => { @@ -43,71 +44,30 @@ async function main(): Promise { if (pp) { getUnsettledPnlUiAgg += pp.getUnsettledPnlUi(perpMarket); getBasePositionUiAgg += pp.getBasePositionUi(perpMarket); + getQuotePositionUiAgg += pp.getQuotePositionUi(perpMarket); longSettledFundingAgg += pp.longSettledFunding.toNumber(); shortSettledFundingAgg += pp.shortSettledFunding.toNumber(); - // console.log(` - ${mangoAccount.publicKey.toBase58().padStart(45)}`); - // console.log( - // ` - unsettled pnl ${pp - // .getUnsettledPnlUi(group, perpMarket) - // .toFixed(4) - // .padStart(10)}`, - // ); - // console.log( - // ` - base position ${pp - // .getBasePositionUi(perpMarket) - // .toFixed(4) - // .padStart(10)}`, - // ); - // console.log( - // ` - avgEntryPricePerBaseLot ${pp.avgEntryPricePerBaseLot}`, - // ); - // console.log( - // ` - realizedTradePnl ${toUiDecimalsForQuote( - // pp.realizedTradePnlNative, - // )}`, - // ); - // console.log( - // ` - realizedOtherPnl ${toUiDecimalsForQuote( - // pp.realizedOtherPnlNative, - // )}`, - // ); - // console.log( - // ` - settlePnlLimitRealizedTrade ${pp.settlePnlLimitRealizedTrade.toNumber()}`, - // ); - // console.log( - // ` - realizedPnlForPosition ${toUiDecimalsForQuote( - // pp.realizedPnlForPositionNative, - // )}`, - // ); - // console.log( - // ` - settlePnlLimitSettledInCurrentWindow ${toUiDecimalsForQuote( - // pp.settlePnlLimitSettledInCurrentWindowNative, - // )}`, - // ); } }); - // console.log( - // `- feesAccrued ${toUiDecimalsForQuote(perpMarket.feesAccrued)}`, - // ); - // console.log( - // `- feesSettled ${toUiDecimalsForQuote(perpMarket.feesSettled)}`, - // ); - // console.log( - // `- longSettledFundingAgg ${longSettledFundingAgg - // .toFixed(4) - // .padStart(10)}`, - // ); - // console.log( - // `- shortSettledFunding ${shortSettledFundingAgg - // .toFixed(4) - // .padStart(10)}`, - // ); + + console.log( + `- longSettledFundingAgg - shortSettledFunding ${( + longSettledFundingAgg - shortSettledFundingAgg + ) + .toFixed(4) + .padStart(10)}`, + ); console.log( `- unsettled pnl aggr ${getUnsettledPnlUiAgg.toFixed(4).padStart(10)}`, ); console.log( `- base position aggr ${getBasePositionUiAgg.toFixed(4).padStart(10)}`, ); + console.log( + `- quote position aggr ${getQuotePositionUiAgg + .toFixed(4) + .padStart(10)}`, + ); console.log( `- base position aggr * price ${( getBasePositionUiAgg * perpMarket.uiPrice @@ -118,9 +78,6 @@ async function main(): Promise { console.log( `- perp.feesAccrued ${toUiDecimalsForQuote(perpMarket.feesAccrued)}`, ); - console.log( - `- perp.feesSettled ${toUiDecimalsForQuote(perpMarket.feesSettled)}`, - ); console.log( `- unsettled pnl aggr - base position aggr * price ${( getUnsettledPnlUiAgg - @@ -130,10 +87,8 @@ async function main(): Promise { .padStart(10)}`, ); console.log( - `- perp.feesAccrued - perp.feesSettled + unsettled pnl aggr ${ - toUiDecimalsForQuote( - perpMarket.feesAccrued.sub(perpMarket.feesSettled), - ) + getUnsettledPnlUiAgg + `- perp.feesAccrued + unsettled pnl aggr ${ + toUiDecimalsForQuote(perpMarket.feesAccrued) + getUnsettledPnlUiAgg }`, ); }); From 2957796de866b3b6e6ac4909cad8001c6d0a65c2 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:00:41 +0200 Subject: [PATCH 33/48] in perp settle fees, dont error, rather return early, this enables blindly concatenating perp settle fees to perp settle pnl (#526) * in perp settle fees, dont error, rather return early Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- .../src/instructions/perp_settle_fees.rs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/programs/mango-v4/src/instructions/perp_settle_fees.rs b/programs/mango-v4/src/instructions/perp_settle_fees.rs index d466d6930..9a0b7a50a 100644 --- a/programs/mango-v4/src/instructions/perp_settle_fees.rs +++ b/programs/mango-v4/src/instructions/perp_settle_fees.rs @@ -43,18 +43,17 @@ pub fn perp_settle_fees(ctx: Context, max_settle_amount: u64) -> // Calculate PnL let pnl = perp_position.unsettled_pnl(&perp_market, oracle_price)?; - // Account perp position must have a loss to be able to settle against the fee account - require!(pnl.is_negative(), MangoError::ProfitabilityMismatch); - require!( - perp_market.fees_accrued.is_positive(), - MangoError::ProfitabilityMismatch - ); - let settleable_pnl = perp_position.apply_pnl_settle_limit(&perp_market, pnl); - require!( - settleable_pnl.is_negative(), - MangoError::ProfitabilityMismatch - ); + + if !settleable_pnl.is_negative() || !perp_market.fees_accrued.is_positive() { + msg!( + "Not settling: pnl {}, perp_market.fees_accrued {}, settleable_pnl {}", + pnl, + perp_market.fees_accrued, + settleable_pnl + ); + return Ok(()); + } // Settle for the maximum possible capped to max_settle_amount let settlement = settleable_pnl From b7189696d82d5cdb4f0b8dbb4561be5a579f3d0b Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Thu, 30 Mar 2023 17:17:38 +0200 Subject: [PATCH 34/48] update script Signed-off-by: microwavedcola1 --- ts/client/scripts/debug-scripts/debug-perp.ts | 4 +++- ts/client/src/accounts/mangoAccount.ts | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ts/client/scripts/debug-scripts/debug-perp.ts b/ts/client/scripts/debug-scripts/debug-perp.ts index a64ee8fc6..330b6b3d0 100644 --- a/ts/client/scripts/debug-scripts/debug-perp.ts +++ b/ts/client/scripts/debug-scripts/debug-perp.ts @@ -42,7 +42,9 @@ async function main(): Promise { .perpActive() .find((pp) => pp.marketIndex === perpMarket.perpMarketIndex); if (pp) { - getUnsettledPnlUiAgg += pp.getUnsettledPnlUi(perpMarket); + getUnsettledPnlUiAgg += + pp.getUnsettledPnlUi(perpMarket) - + pp.getUnsettledFundingUi(perpMarket); getBasePositionUiAgg += pp.getBasePositionUi(perpMarket); getQuotePositionUiAgg += pp.getQuotePositionUi(perpMarket); longSettledFundingAgg += pp.longSettledFunding.toNumber(); diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index b63b4bf90..c863a5788 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -1352,6 +1352,9 @@ export class PerpPosition { } return ZERO_I80F48(); } + public getUnsettledFundingUi(perpMarket: PerpMarket): number { + return toUiDecimalsForQuote(this.getUnsettledFunding(perpMarket)); + } public getEquityUi(perpMarket: PerpMarket): number { if (perpMarket.perpMarketIndex !== this.marketIndex) { From 312a60fe4f4e88b60d5d22c511c9e5e44c787835 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Thu, 30 Mar 2023 17:25:29 +0200 Subject: [PATCH 35/48] Net borrow limits: Use correct price for check (#527) --- .../account_buyback_fees_with_mngo.rs | 2 +- .../perp_liq_base_or_positive_pnl.rs | 6 ++- .../perp_liq_negative_pnl_or_bankruptcy.rs | 15 +++--- .../src/instructions/perp_settle_fees.rs | 8 +++- .../src/instructions/perp_settle_pnl.rs | 13 ++++- programs/mango-v4/src/state/bank.rs | 4 +- .../tests/cases/test_borrow_limits.rs | 48 +++++++++++++++++-- 7 files changed, 78 insertions(+), 18 deletions(-) diff --git a/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs b/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs index 6c388fd78..66a1f5c3e 100644 --- a/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs +++ b/programs/mango-v4/src/instructions/account_buyback_fees_with_mngo.rs @@ -136,7 +136,7 @@ pub fn account_buyback_fees_with_mngo( dao_fees_token_position, max_buyback_fees, now_ts, - mngo_oracle_price, + fees_oracle_price, )?; if !in_use { dao_account.deactivate_token_position_and_log( diff --git a/programs/mango-v4/src/instructions/perp_liq_base_or_positive_pnl.rs b/programs/mango-v4/src/instructions/perp_liq_base_or_positive_pnl.rs index 5cf89e1a8..8ef041b2b 100644 --- a/programs/mango-v4/src/instructions/perp_liq_base_or_positive_pnl.rs +++ b/programs/mango-v4/src/instructions/perp_liq_base_or_positive_pnl.rs @@ -215,6 +215,10 @@ pub(crate) fn liquidation_action( let liqor_perp_position = liqor.perp_position_mut(perp_market_index)?; let perp_info = liqee_health_cache.perp_info(perp_market_index)?; + let settle_token_oracle_price = liqee_health_cache + .token_info(settle_token_index)? + .prices + .oracle; let oracle_price = perp_info.prices.oracle; let base_lot_size = I80F48::from(perp_market.base_lot_size); let oracle_price_per_lot = base_lot_size * oracle_price; @@ -451,7 +455,7 @@ pub(crate) fn liquidation_action( liqor_token_position, token_transfer, now_ts, - oracle_price, + settle_token_oracle_price, )?; liqee_health_cache.adjust_token_balance(&settle_bank, token_transfer)?; } diff --git a/programs/mango-v4/src/instructions/perp_liq_negative_pnl_or_bankruptcy.rs b/programs/mango-v4/src/instructions/perp_liq_negative_pnl_or_bankruptcy.rs index 783cb3a78..9f691a26b 100644 --- a/programs/mango-v4/src/instructions/perp_liq_negative_pnl_or_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/perp_liq_negative_pnl_or_bankruptcy.rs @@ -4,7 +4,6 @@ use anchor_spl::token; use fixed::types::I80F48; use crate::accounts_ix::*; -use crate::accounts_zerocopy::*; use crate::error::*; use crate::health::{compute_health, new_health_cache, HealthType, ScanningAccountRetriever}; use crate::logs::{ @@ -72,10 +71,14 @@ pub fn perp_liq_negative_pnl_or_bankruptcy( // Get oracle price for market. Price is validated inside let mut perp_market = ctx.accounts.perp_market.load_mut()?; - let oracle_price = perp_market.oracle_price( - &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, - None, // staleness checked in health - )?; + let oracle_price = liqee_health_cache + .perp_info(perp_market_index)? + .prices + .oracle; + let settle_token_oracle_price = liqee_health_cache + .token_info(settle_token_index)? + .prices + .oracle; let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); @@ -126,7 +129,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy( liqee_token_position, settlement, now_ts, - oracle_price, + settle_token_oracle_price, )?; liqee_health_cache.adjust_token_balance(&settle_bank, -settlement)?; diff --git a/programs/mango-v4/src/instructions/perp_settle_fees.rs b/programs/mango-v4/src/instructions/perp_settle_fees.rs index 9a0b7a50a..c3ad8de5c 100644 --- a/programs/mango-v4/src/instructions/perp_settle_fees.rs +++ b/programs/mango-v4/src/instructions/perp_settle_fees.rs @@ -28,11 +28,15 @@ pub fn perp_settle_fees(ctx: Context, max_settle_amount: u64) -> MangoError::InvalidBank ); - // Get oracle price for market. Price is validated inside + // Get oracle prices let oracle_price = perp_market.oracle_price( &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None, // staleness checked in health )?; + let settle_token_oracle_price = settle_bank.oracle_price( + &AccountInfoRef::borrow(ctx.accounts.settle_oracle.as_ref())?, + None, // staleness checked in health + )?; // Fetch perp positions for accounts let perp_position = account.perp_position_mut(perp_market.perp_market_index)?; @@ -92,7 +96,7 @@ pub fn perp_settle_fees(ctx: Context, max_settle_amount: u64) -> token_position, settlement, Clock::get()?.unix_timestamp.try_into().unwrap(), - oracle_price, + settle_token_oracle_price, )?; // Update the settled balance on the market itself perp_market.fees_settled += settlement; diff --git a/programs/mango-v4/src/instructions/perp_settle_pnl.rs b/programs/mango-v4/src/instructions/perp_settle_pnl.rs index 050b5f15a..0e401fffe 100644 --- a/programs/mango-v4/src/instructions/perp_settle_pnl.rs +++ b/programs/mango-v4/src/instructions/perp_settle_pnl.rs @@ -58,11 +58,15 @@ pub fn perp_settle_pnl(ctx: Context) -> Result<()> { MangoError::InvalidBank ); - // Get oracle price for market. Price is validated inside + // Get oracle prices let oracle_price = perp_market.oracle_price( &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None, // staleness checked in health )?; + let settle_token_oracle_price = settle_bank.oracle_price( + &AccountInfoRef::borrow(ctx.accounts.settle_oracle.as_ref())?, + None, // staleness checked in health + )?; // Fetch perp position and pnl let a_perp_position = account_a.perp_position_mut(perp_market_index)?; @@ -172,7 +176,12 @@ pub fn perp_settle_pnl(ctx: Context) -> Result<()> { // Don't charge loan origination fees on borrows created via settling: // Even small loan origination fees could accumulate if a perp position is // settled back and forth repeatedly. - settle_bank.withdraw_without_fee(b_token_position, settlement, now_ts, oracle_price)?; + settle_bank.withdraw_without_fee( + b_token_position, + settlement, + now_ts, + settle_token_oracle_price, + )?; emit!(TokenBalanceLog { mango_group: ctx.accounts.group.key(), diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index 0e11ddf68..10bbaf17a 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -612,8 +612,8 @@ impl Bank { .unwrap(); if net_borrows_quote > self.net_borrow_limit_per_window_quote { return Err(error_msg_typed!(MangoError::BankNetBorrowsLimitReached, - "net_borrows_in_window ({:?}) exceeds net_borrow_limit_per_window_quote ({:?}) for last_net_borrows_window_start_ts ({:?}) ", - self.net_borrows_in_window, self.net_borrow_limit_per_window_quote, self.last_net_borrows_window_start_ts + "net_borrows_in_window ({:?}) valued at ({:?} exceed net_borrow_limit_per_window_quote ({:?}) for last_net_borrows_window_start_ts ({:?}) ", + self.net_borrows_in_window, net_borrows_quote, self.net_borrow_limit_per_window_quote, self.last_net_borrows_window_start_ts )); } diff --git a/programs/mango-v4/tests/cases/test_borrow_limits.rs b/programs/mango-v4/tests/cases/test_borrow_limits.rs index 45086863d..f115e750a 100644 --- a/programs/mango-v4/tests/cases/test_borrow_limits.rs +++ b/programs/mango-v4/tests/cases/test_borrow_limits.rs @@ -153,6 +153,11 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError } }; + let bank_borrow_used = || { + let bank = tokens[0].bank; + async move { solana.get_account::(bank).await.net_borrows_in_window } + }; + reset_net_borrows().await; // @@ -198,6 +203,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError ) .await .unwrap(); + assert_eq!(bank_borrow_used().await, 5003); // loan fees & ceil // fails because borrow is greater than remaining margin in net borrow limit // (requires the test to be quick enough to avoid accidentally going to the next borrow limit window!) @@ -229,6 +235,38 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError ) .await .unwrap(); + + // depositing reduces usage, but only the repayment part + send_tx( + solana, + TokenDepositInstruction { + amount: 7000, + account: account_1, + owner, + token_authority: payer, + token_account: payer_mint_accounts[0], + bank_index: 0, + reduce_only: false, + }, + ) + .await + .unwrap(); + assert_eq!(bank_borrow_used().await, 1); // due to rounding + + // give account1 a negative token0 balance again + send_tx( + solana, + TokenWithdrawInstruction { + amount: 5000, + allow_borrow: true, + account: account_1, + owner, + token_account: payer_mint_accounts[0], + bank_index: 0, + }, + ) + .await + .unwrap(); } reset_net_borrows().await; @@ -242,7 +280,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError send_tx( solana, TokenWithdrawInstruction { - amount: 1000, + amount: 999, // borrow limit increases more due to loan fees + ceil allow_borrow: true, account: account_1, owner, @@ -252,11 +290,12 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError ) .await .unwrap(); + assert_eq!(bank_borrow_used().await, 1000); } set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 10.0).await; - // cannot borrow anything: net borrowed 1000 * price 10.0 > limit 6000 + // cannot borrow anything: net borrowed 1002 * price 10.0 > limit 6000 let res = send_tx( solana, TokenWithdrawInstruction { @@ -277,7 +316,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError let res = send_tx( solana, TokenWithdrawInstruction { - amount: 201, + amount: 200, allow_borrow: true, account: account_1, owner, @@ -292,7 +331,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError send_tx( solana, TokenWithdrawInstruction { - amount: 199, + amount: 198, allow_borrow: true, account: account_1, owner, @@ -302,6 +341,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError ) .await .unwrap(); + assert_eq!(bank_borrow_used().await, 1199); } Ok(()) From ec48db01bb3b4178a4cca17c591e46007eb9285a Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Thu, 30 Mar 2023 17:28:14 +0200 Subject: [PATCH 36/48] Changelog for program-v0.10.0 and idl update --- CHANGELOG.md | 30 +++++++++++++++++++++++++++--- mango_v4.json | 7 ++++++- ts/client/src/mango_v4.ts | 14 ++++++++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eace5c72..ec670cb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,36 @@ Update this for each program release and mainnet deployment. ## not on mainnet -### v0.9.0, 2023-3- +### v0.10.0, 2023-4- Deployment: +- HealthRegion: Explicitly whitelist allowed instructions (#508) + + The security council had disabled the HealthRegion instructions after the audit + found a vulnerability. The issue has been resolved by restricting which other + instructions may be called in a health region. That way it's still usable to + save compute units, but its attack surface is significantly reduced. + +- Use insurance fund token oracle for bankruptcies (#503) + + This is in preparation for using an oracle for the USDC price instead of fixing + its value to $1. The insurance fund is in USDC, so the oracle price needs to + be taken into account once a real oracle is provided. + +- Fee buyback: Use the USDC oracle (#504) +- Perp settle fees: Return early instead of error on failure (#526) +- Net borrow limits: Fixed accounting of deposits (#513) +- Better logging in IxGateSet instruction +- Sanity check token_index in TokenRegister instruction +- Allow using all available bytes for bank and market names + +## mainnet + +### v0.9.0, 2023-3-16 + +Deployment: Mar 16, 2023 at 11:07:30 Central European Standard Time, https://explorer.solana.com/tx/2hVqFQhxC9BGzDvH7y9bWChrMRvzsBGMPcMepHLBamK4vKJMJG48Fv8ZB54b46qErH1aGRy9YVhFnVnpaKgnoP3c + - Downgrade the "fixed" dependency to v1.11.0 (#500) The dependency had a regression. This downgrades to the previous version that @@ -16,8 +42,6 @@ Deployment: - Improvements to perp position docstrings (#497) -## mainnet - ### v0.8.0, 2023-3-11 Deployment: Mar 11, 2023 at 08:06:22 Central European Standard Time, https://explorer.solana.com/tx/61CbcyDaCV1DKHEGxkfNfx9nCUfsH3RgUU7mivTjtqbHJ3YVPX6vNAzn91CZYRsjohVc5LdcZCZtteDKrCiKjYEi diff --git a/mango_v4.json b/mango_v4.json index 2c2ddaab9..3286d1318 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -1,5 +1,5 @@ { - "version": "0.9.0", + "version": "0.10.0", "name": "mango_v4", "instructions": [ { @@ -8674,6 +8674,11 @@ "code": 6044, "name": "PerpOrderIdNotFound", "msg": "perp order id not found on the orderbook" + }, + { + "code": 6045, + "name": "HealthRegionBadInnerInstruction", + "msg": "HealthRegions allow only specific instructions between Begin and End" } ] } \ No newline at end of file diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 16fcf1c55..998688a48 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1,5 +1,5 @@ export type MangoV4 = { - "version": "0.9.0", + "version": "0.10.0", "name": "mango_v4", "instructions": [ { @@ -8674,12 +8674,17 @@ export type MangoV4 = { "code": 6044, "name": "PerpOrderIdNotFound", "msg": "perp order id not found on the orderbook" + }, + { + "code": 6045, + "name": "HealthRegionBadInnerInstruction", + "msg": "HealthRegions allow only specific instructions between Begin and End" } ] }; export const IDL: MangoV4 = { - "version": "0.9.0", + "version": "0.10.0", "name": "mango_v4", "instructions": [ { @@ -17354,6 +17359,11 @@ export const IDL: MangoV4 = { "code": 6044, "name": "PerpOrderIdNotFound", "msg": "perp order id not found on the orderbook" + }, + { + "code": 6045, + "name": "HealthRegionBadInnerInstruction", + "msg": "HealthRegions allow only specific instructions between Begin and End" } ] }; From 08f6bf10ffe095c3b8ab2045d42d81973e34543a Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 31 Mar 2023 08:55:00 +0200 Subject: [PATCH 37/48] script for grabbing logs Signed-off-by: microwavedcola1 --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1f7a1bb34..49fd9077a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,7 @@ programs/mango-v4/src/lib-expanded.rs keeper/.env .env -ts/client/src/scripts/archive/ts.ts \ No newline at end of file +ts/client/src/scripts/archive/ts.ts + +logs +grab-logs.sh \ No newline at end of file From 8559d3ef07abe9e2ab635808da14c296d1d06b3a Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 31 Mar 2023 10:58:04 +0200 Subject: [PATCH 38/48] Perp funding: Fix logging in update funding + deactivate pos (#528) --- programs/mango-v4/src/state/mango_account.rs | 2 +- programs/mango-v4/src/state/perp_market.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index de97617e4..0537635ac 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -867,7 +867,7 @@ impl< mango_account: mango_account_pubkey, market_index: perp_market_index, cumulative_long_funding: perp_position.cumulative_long_funding, - cumulative_short_funding: perp_position.cumulative_long_funding, + cumulative_short_funding: perp_position.cumulative_short_funding, maker_volume: perp_position.maker_volume, taker_volume: perp_position.taker_volume, perp_spot_transfers: perp_position.perp_spot_transfers, diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index 5ba2f4d21..00f0751b3 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -306,7 +306,7 @@ impl PerpMarket { mango_group: self.group, market_index: self.perp_market_index, long_funding: self.long_funding.to_bits(), - short_funding: self.long_funding.to_bits(), + short_funding: self.short_funding.to_bits(), price: oracle_price.to_bits(), stable_price: self.stable_price().to_bits(), fees_accrued: self.fees_accrued.to_bits(), From c7e2b96802bc7c6c4386d65ea1b1b7f82673e683 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 31 Mar 2023 12:37:16 +0200 Subject: [PATCH 39/48] update Signed-off-by: microwavedcola1 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 49fd9077a..29432e759 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ keeper/.env ts/client/src/scripts/archive/ts.ts logs -grab-logs.sh \ No newline at end of file +check.sh \ No newline at end of file From cd39a9f432c2e9bb6e880d89357771b80fc270e3 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 31 Mar 2023 12:45:05 +0200 Subject: [PATCH 40/48] Fix test Signed-off-by: microwavedcola1 --- .../tests/cases/test_perp_settle_fees.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/programs/mango-v4/tests/cases/test_perp_settle_fees.rs b/programs/mango-v4/tests/cases/test_perp_settle_fees.rs index 0f04c36f2..6b0a2099f 100644 --- a/programs/mango-v4/tests/cases/test_perp_settle_fees.rs +++ b/programs/mango-v4/tests/cases/test_perp_settle_fees.rs @@ -327,8 +327,8 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> { // Try and settle with high price set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 1200.0).await; - // Account must have a loss - let result = send_tx( + // Account must have a loss, should not settle anything and instead return early + send_tx( solana, PerpSettleFeesInstruction { account: account_0, @@ -338,12 +338,18 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> { }, ) .await; - - assert_mango_error( - &result, - MangoError::ProfitabilityMismatch.into(), - "Account must be unprofitable".to_string(), - ); + // No change + { + let perp_market = solana.get_account::(perp_market).await; + assert_eq!( + get_pnl_native(&mango_account_0.perps[0], &perp_market, I80F48::from(1200)).round(), + 19980 + ); + assert_eq!( + get_pnl_native(&mango_account_1.perps[0], &perp_market, I80F48::from(1200)), + -20000 + ); + } // TODO: Difficult to test health due to fees being so small. Need alternative // let result = send_tx( From 7080c9bf3cf35770cbbc30eaab4f8a17ddbd789f Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Fri, 31 Mar 2023 12:45:26 +0200 Subject: [PATCH 41/48] sync rate params to latest proposal (#523) * sync rate params to latest proposal Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 --------- Signed-off-by: microwavedcola1 --- .../src/instructions/token_register_trustless.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index 710b37e32..53a7358aa 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -45,9 +45,9 @@ pub fn token_register_trustless( avg_utilization: I80F48::ZERO, // 10% daily adjustment at 0% or 100% utilization adjustment_factor: I80F48::from_num(0.004), - util0: I80F48::from_num(0.7), - rate0: I80F48::from_num(0.1), - util1: I80F48::from_num(0.85), + util0: I80F48::from_num(0.5), + rate0: I80F48::from_num(0.072), + util1: I80F48::from_num(0.8), rate1: I80F48::from_num(0.2), max_rate: I80F48::from_num(2.0), collected_fees_native: I80F48::ZERO, @@ -69,10 +69,10 @@ pub fn token_register_trustless( net_borrow_limit_window_size_ts, last_net_borrows_window_start_ts: now_ts / net_borrow_limit_window_size_ts * net_borrow_limit_window_size_ts, - net_borrow_limit_per_window_quote: 1_000_000_000_000, // 1M USD + net_borrow_limit_per_window_quote: 250_000_000_000, // $250k net_borrows_in_window: 0, - borrow_weight_scale_start_quote: f64::MAX, - deposit_weight_scale_start_quote: f64::MAX, + borrow_weight_scale_start_quote: 100_000_000_000.0, // $100k + deposit_weight_scale_start_quote: 100_000_000_000.0, // $100k reduce_only: 0, reserved: [0; 2119], }; From 762608ecce596b8cd110b1fd3a7e4b0de1b32164 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 31 Mar 2023 12:48:34 +0200 Subject: [PATCH 42/48] Limit funding and interest accrual during downtimes (#529) Previously, if the funding or interest updating instruction wasn't called for a long time (like for a solana downtime or the security council halting the program), the next update would apply funding or interest for the whole time interval since the last update. This could lead to a bad downtime situation becoming worse. Instead, limit the maximum funding and interest time interval to one hour. --- .../src/instructions/token_update_index_and_rate.rs | 7 ++++++- programs/mango-v4/src/state/perp_market.rs | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/programs/mango-v4/src/instructions/token_update_index_and_rate.rs b/programs/mango-v4/src/instructions/token_update_index_and_rate.rs index 9c7d68a70..15cb1ec57 100644 --- a/programs/mango-v4/src/instructions/token_update_index_and_rate.rs +++ b/programs/mango-v4/src/instructions/token_update_index_and_rate.rs @@ -71,7 +71,12 @@ pub fn token_update_index_and_rate(ctx: Context) -> Res { let mut some_bank = ctx.remaining_accounts[0].load_mut::()?; - let diff_ts = I80F48::from_num(now_ts - some_bank.index_last_updated); + // Limit the maximal time interval that interest is applied for. This means we won't use + // a fixed interest rate for a very long time period in exceptional circumstances, like + // when there is a solana downtime or the security council disables this instruction. + let max_interest_timestep = 3600; // hour + let diff_ts = + I80F48::from_num((now_ts - some_bank.index_last_updated).min(max_interest_timestep)); let (deposit_index, borrow_index, borrow_fees, borrow_rate, deposit_rate) = some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?; diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index 00f0751b3..67b640e1c 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -288,7 +288,14 @@ impl PerpMarket { (None, None) => I80F48::ZERO, }; - let diff_ts = I80F48::from_num(now_ts - self.funding_last_updated as u64); + // Limit the maximal time interval that funding is applied for. This means we won't use + // the funding rate computed from a single orderbook snapshot for a very long time period + // in exceptional circumstances, like a solana downtime or the security council disabling + // funding updates. + let max_funding_timestep = 3600; // one hour + let diff_ts = + I80F48::from_num((now_ts - self.funding_last_updated as u64).min(max_funding_timestep)); + let time_factor = diff_ts / DAY_I80F48; let base_lot_size = I80F48::from_num(self.base_lot_size); From 535b0b2d0c49d2ff7c26f4ec3692ba7683faa107 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 31 Mar 2023 18:47:34 +0200 Subject: [PATCH 43/48] Changelog for program-v0.11.0, bump version, update idl --- CHANGELOG.md | 21 +++++++++++++++++++++ Cargo.lock | 2 +- mango_v4.json | 2 +- programs/mango-v4/Cargo.toml | 2 +- ts/client/src/mango_v4.ts | 4 ++-- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec670cb60..7eafb1215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ Update this for each program release and mainnet deployment. ## not on mainnet +### v0.11.0, 2023-4- + +Deployment: + +- Limit funding and interest accrual during downtimes (#529) + + Previously, if the funding or interest updating instruction wassn't + called for a long time (like for a solana downtime or the security + council halting the program), the next update would apply funding or + interest for the whole time interval since the last update. + + This could lead to a bad downtime situation becoming worse. Instead, + limit the maximum funding and interest time interval to one hour. + +- Update default interest parameters in token_register_trustless (#523) + + This brings them in line with the recent interest rate changes for >50% + utilization. + +- Perp: Fix logging of funding rate in update funding and deactivate pos (#528) + ### v0.10.0, 2023-4- Deployment: diff --git a/Cargo.lock b/Cargo.lock index 6782c6272..f7cf9c743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3005,7 +3005,7 @@ dependencies = [ [[package]] name = "mango-v4" -version = "0.10.0" +version = "0.11.0" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/mango_v4.json b/mango_v4.json index 3286d1318..c44c6fd27 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -1,5 +1,5 @@ { - "version": "0.10.0", + "version": "0.11.0", "name": "mango_v4", "instructions": [ { diff --git a/programs/mango-v4/Cargo.toml b/programs/mango-v4/Cargo.toml index e67941974..d8b2d5d1a 100644 --- a/programs/mango-v4/Cargo.toml +++ b/programs/mango-v4/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mango-v4" -version = "0.10.0" +version = "0.11.0" description = "Created with Anchor" edition = "2021" diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 998688a48..47e161657 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1,5 +1,5 @@ export type MangoV4 = { - "version": "0.10.0", + "version": "0.11.0", "name": "mango_v4", "instructions": [ { @@ -8684,7 +8684,7 @@ export type MangoV4 = { }; export const IDL: MangoV4 = { - "version": "0.10.0", + "version": "0.11.0", "name": "mango_v4", "instructions": [ { From 789a5135aceb541099fcdec9185bb40cdece500f Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Mon, 3 Apr 2023 20:07:00 +0100 Subject: [PATCH 44/48] Don't reload openorders if there's no active markets --- ts/client/src/accounts/mangoAccount.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index c863a5788..8d076d56c 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -115,6 +115,7 @@ export class MangoAccount { async reloadSerum3OpenOrders(client: MangoClient): Promise { const serum3Active = this.serum3Active(); + if (!serum3Active.length) return this; const ais = await client.program.provider.connection.getMultipleAccountsInfo( serum3Active.map((serum3) => serum3.openOrders), From c8bd104e875f54ba8f7d178b7be4acaddeee2977 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 4 Apr 2023 08:28:45 +0200 Subject: [PATCH 45/48] update Signed-off-by: microwavedcola1 --- ts/client/scripts/debug-scripts/debug-perp.ts | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/ts/client/scripts/debug-scripts/debug-perp.ts b/ts/client/scripts/debug-scripts/debug-perp.ts index 330b6b3d0..f43606222 100644 --- a/ts/client/scripts/debug-scripts/debug-perp.ts +++ b/ts/client/scripts/debug-scripts/debug-perp.ts @@ -52,42 +52,42 @@ async function main(): Promise { } }); - console.log( - `- longSettledFundingAgg - shortSettledFunding ${( - longSettledFundingAgg - shortSettledFundingAgg - ) - .toFixed(4) - .padStart(10)}`, - ); - console.log( - `- unsettled pnl aggr ${getUnsettledPnlUiAgg.toFixed(4).padStart(10)}`, - ); - console.log( - `- base position aggr ${getBasePositionUiAgg.toFixed(4).padStart(10)}`, - ); - console.log( - `- quote position aggr ${getQuotePositionUiAgg - .toFixed(4) - .padStart(10)}`, - ); - console.log( - `- base position aggr * price ${( - getBasePositionUiAgg * perpMarket.uiPrice - ) - .toFixed(4) - .padStart(10)}`, - ); - console.log( - `- perp.feesAccrued ${toUiDecimalsForQuote(perpMarket.feesAccrued)}`, - ); - console.log( - `- unsettled pnl aggr - base position aggr * price ${( - getUnsettledPnlUiAgg - - getBasePositionUiAgg * perpMarket.uiPrice - ) - .toFixed(4) - .padStart(10)}`, - ); + // console.log( + // `- longSettledFundingAgg - shortSettledFunding ${( + // longSettledFundingAgg - shortSettledFundingAgg + // ) + // .toFixed(4) + // .padStart(10)}`, + // ); + // console.log( + // `- unsettled pnl aggr ${getUnsettledPnlUiAgg.toFixed(4).padStart(10)}`, + // ); + // console.log( + // `- base position aggr ${getBasePositionUiAgg.toFixed(4).padStart(10)}`, + // ); + // console.log( + // `- quote position aggr ${getQuotePositionUiAgg + // .toFixed(4) + // .padStart(10)}`, + // ); + // console.log( + // `- base position aggr * price ${( + // getBasePositionUiAgg * perpMarket.uiPrice + // ) + // .toFixed(4) + // .padStart(10)}`, + // ); + // console.log( + // `- perp.feesAccrued ${toUiDecimalsForQuote(perpMarket.feesAccrued)}`, + // ); + // console.log( + // `- unsettled pnl aggr - base position aggr * price ${( + // getUnsettledPnlUiAgg - + // getBasePositionUiAgg * perpMarket.uiPrice + // ) + // .toFixed(4) + // .padStart(10)}`, + // ); console.log( `- perp.feesAccrued + unsettled pnl aggr ${ toUiDecimalsForQuote(perpMarket.feesAccrued) + getUnsettledPnlUiAgg From 9a77f8d64d86da2f16891924b26d13bc63ce92d8 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Tue, 4 Apr 2023 08:36:54 +0200 Subject: [PATCH 46/48] reorg Signed-off-by: microwavedcola1 --- ts/client/scripts/{debug-scripts => archive}/debug-banks.ts | 0 ts/client/scripts/{debug-scripts => archive}/debug-user.ts | 0 ts/client/scripts/{ => archive}/devnet-admin-close.ts | 4 ++-- ts/client/scripts/{ => archive}/devnet-admin.ts | 0 ts/client/scripts/{ => archive}/devnet-user-2.ts | 0 .../scripts/{ => archive}/devnet-user-close-account.ts | 0 ts/client/scripts/{ => archive}/devnet-user.ts | 0 ts/client/scripts/{ => archive}/mb-add-spot-market.ts | 4 ++-- ts/client/scripts/{ => archive}/mb-admin-close.ts | 0 ts/client/scripts/{ => archive}/mb-admin.ts | 0 ts/client/scripts/{ => archive}/mb-close-account.ts | 0 ts/client/scripts/{ => archive}/mb-force-close-account.ts | 0 ts/client/scripts/{ => archive}/mb-oracle-inspect.ts | 0 .../{ => archive}/mb-sim-accounts-with-param-change.ts | 0 ts/client/scripts/{ => archive}/mb-user.ts | 0 ts/client/scripts/{ => archive}/token-spec.ts | 0 ts/client/scripts/{mb-create-gov-ix.ts => create-gov-ix.ts} | 0 .../liqtest-create-group.ts} | 4 ++-- .../liqtest-make-candidates.ts} | 0 .../liqtest-settle-and-close-all.ts} | 0 .../{debug-scripts/debug-perp.ts => perp-sanity-check.ts} | 6 +++--- 21 files changed, 9 insertions(+), 9 deletions(-) rename ts/client/scripts/{debug-scripts => archive}/debug-banks.ts (100%) rename ts/client/scripts/{debug-scripts => archive}/debug-user.ts (100%) rename ts/client/scripts/{ => archive}/devnet-admin-close.ts (96%) rename ts/client/scripts/{ => archive}/devnet-admin.ts (100%) rename ts/client/scripts/{ => archive}/devnet-user-2.ts (100%) rename ts/client/scripts/{ => archive}/devnet-user-close-account.ts (100%) rename ts/client/scripts/{ => archive}/devnet-user.ts (100%) rename ts/client/scripts/{ => archive}/mb-add-spot-market.ts (94%) rename ts/client/scripts/{ => archive}/mb-admin-close.ts (100%) rename ts/client/scripts/{ => archive}/mb-admin.ts (100%) rename ts/client/scripts/{ => archive}/mb-close-account.ts (100%) rename ts/client/scripts/{ => archive}/mb-force-close-account.ts (100%) rename ts/client/scripts/{ => archive}/mb-oracle-inspect.ts (100%) rename ts/client/scripts/{ => archive}/mb-sim-accounts-with-param-change.ts (100%) rename ts/client/scripts/{ => archive}/mb-user.ts (100%) rename ts/client/scripts/{ => archive}/token-spec.ts (100%) rename ts/client/scripts/{mb-create-gov-ix.ts => create-gov-ix.ts} (100%) rename ts/client/scripts/{mb-liqtest-create-group.ts => liqtest/liqtest-create-group.ts} (99%) rename ts/client/scripts/{mb-liqtest-make-candidates.ts => liqtest/liqtest-make-candidates.ts} (100%) rename ts/client/scripts/{mb-liqtest-settle-and-close-all.ts => liqtest/liqtest-settle-and-close-all.ts} (100%) rename ts/client/scripts/{debug-scripts/debug-perp.ts => perp-sanity-check.ts} (95%) diff --git a/ts/client/scripts/debug-scripts/debug-banks.ts b/ts/client/scripts/archive/debug-banks.ts similarity index 100% rename from ts/client/scripts/debug-scripts/debug-banks.ts rename to ts/client/scripts/archive/debug-banks.ts diff --git a/ts/client/scripts/debug-scripts/debug-user.ts b/ts/client/scripts/archive/debug-user.ts similarity index 100% rename from ts/client/scripts/debug-scripts/debug-user.ts rename to ts/client/scripts/archive/debug-user.ts diff --git a/ts/client/scripts/devnet-admin-close.ts b/ts/client/scripts/archive/devnet-admin-close.ts similarity index 96% rename from ts/client/scripts/devnet-admin-close.ts rename to ts/client/scripts/archive/devnet-admin-close.ts index 7204f1ca6..deb99eefa 100644 --- a/ts/client/scripts/devnet-admin-close.ts +++ b/ts/client/scripts/archive/devnet-admin-close.ts @@ -1,8 +1,8 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // example script to close accounts - banks, markets, group etc. which require admin to be the signer diff --git a/ts/client/scripts/devnet-admin.ts b/ts/client/scripts/archive/devnet-admin.ts similarity index 100% rename from ts/client/scripts/devnet-admin.ts rename to ts/client/scripts/archive/devnet-admin.ts diff --git a/ts/client/scripts/devnet-user-2.ts b/ts/client/scripts/archive/devnet-user-2.ts similarity index 100% rename from ts/client/scripts/devnet-user-2.ts rename to ts/client/scripts/archive/devnet-user-2.ts diff --git a/ts/client/scripts/devnet-user-close-account.ts b/ts/client/scripts/archive/devnet-user-close-account.ts similarity index 100% rename from ts/client/scripts/devnet-user-close-account.ts rename to ts/client/scripts/archive/devnet-user-close-account.ts diff --git a/ts/client/scripts/devnet-user.ts b/ts/client/scripts/archive/devnet-user.ts similarity index 100% rename from ts/client/scripts/devnet-user.ts rename to ts/client/scripts/archive/devnet-user.ts diff --git a/ts/client/scripts/mb-add-spot-market.ts b/ts/client/scripts/archive/mb-add-spot-market.ts similarity index 94% rename from ts/client/scripts/mb-add-spot-market.ts rename to ts/client/scripts/archive/mb-add-spot-market.ts index e1dc122c2..5fb541391 100644 --- a/ts/client/scripts/mb-add-spot-market.ts +++ b/ts/client/scripts/archive/mb-add-spot-market.ts @@ -2,8 +2,8 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import * as dotenv from 'dotenv'; import fs from 'fs'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; dotenv.config(); // diff --git a/ts/client/scripts/mb-admin-close.ts b/ts/client/scripts/archive/mb-admin-close.ts similarity index 100% rename from ts/client/scripts/mb-admin-close.ts rename to ts/client/scripts/archive/mb-admin-close.ts diff --git a/ts/client/scripts/mb-admin.ts b/ts/client/scripts/archive/mb-admin.ts similarity index 100% rename from ts/client/scripts/mb-admin.ts rename to ts/client/scripts/archive/mb-admin.ts diff --git a/ts/client/scripts/mb-close-account.ts b/ts/client/scripts/archive/mb-close-account.ts similarity index 100% rename from ts/client/scripts/mb-close-account.ts rename to ts/client/scripts/archive/mb-close-account.ts diff --git a/ts/client/scripts/mb-force-close-account.ts b/ts/client/scripts/archive/mb-force-close-account.ts similarity index 100% rename from ts/client/scripts/mb-force-close-account.ts rename to ts/client/scripts/archive/mb-force-close-account.ts diff --git a/ts/client/scripts/mb-oracle-inspect.ts b/ts/client/scripts/archive/mb-oracle-inspect.ts similarity index 100% rename from ts/client/scripts/mb-oracle-inspect.ts rename to ts/client/scripts/archive/mb-oracle-inspect.ts diff --git a/ts/client/scripts/mb-sim-accounts-with-param-change.ts b/ts/client/scripts/archive/mb-sim-accounts-with-param-change.ts similarity index 100% rename from ts/client/scripts/mb-sim-accounts-with-param-change.ts rename to ts/client/scripts/archive/mb-sim-accounts-with-param-change.ts diff --git a/ts/client/scripts/mb-user.ts b/ts/client/scripts/archive/mb-user.ts similarity index 100% rename from ts/client/scripts/mb-user.ts rename to ts/client/scripts/archive/mb-user.ts diff --git a/ts/client/scripts/token-spec.ts b/ts/client/scripts/archive/token-spec.ts similarity index 100% rename from ts/client/scripts/token-spec.ts rename to ts/client/scripts/archive/token-spec.ts diff --git a/ts/client/scripts/mb-create-gov-ix.ts b/ts/client/scripts/create-gov-ix.ts similarity index 100% rename from ts/client/scripts/mb-create-gov-ix.ts rename to ts/client/scripts/create-gov-ix.ts diff --git a/ts/client/scripts/mb-liqtest-create-group.ts b/ts/client/scripts/liqtest/liqtest-create-group.ts similarity index 99% rename from ts/client/scripts/mb-liqtest-create-group.ts rename to ts/client/scripts/liqtest/liqtest-create-group.ts index 813d7cdad..b542347a5 100644 --- a/ts/client/scripts/mb-liqtest-create-group.ts +++ b/ts/client/scripts/liqtest/liqtest-create-group.ts @@ -6,8 +6,8 @@ import { PublicKey, } from '@solana/web3.js'; import fs from 'fs'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // Script which depoys a new mango group, and registers 3 tokens diff --git a/ts/client/scripts/mb-liqtest-make-candidates.ts b/ts/client/scripts/liqtest/liqtest-make-candidates.ts similarity index 100% rename from ts/client/scripts/mb-liqtest-make-candidates.ts rename to ts/client/scripts/liqtest/liqtest-make-candidates.ts diff --git a/ts/client/scripts/mb-liqtest-settle-and-close-all.ts b/ts/client/scripts/liqtest/liqtest-settle-and-close-all.ts similarity index 100% rename from ts/client/scripts/mb-liqtest-settle-and-close-all.ts rename to ts/client/scripts/liqtest/liqtest-settle-and-close-all.ts diff --git a/ts/client/scripts/debug-scripts/debug-perp.ts b/ts/client/scripts/perp-sanity-check.ts similarity index 95% rename from ts/client/scripts/debug-scripts/debug-perp.ts rename to ts/client/scripts/perp-sanity-check.ts index f43606222..971de229e 100644 --- a/ts/client/scripts/debug-scripts/debug-perp.ts +++ b/ts/client/scripts/perp-sanity-check.ts @@ -1,9 +1,9 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; import * as dotenv from 'dotenv'; -import { MangoClient } from '../../src/client'; -import { MANGO_V4_ID } from '../../src/constants'; -import { toUiDecimalsForQuote } from '../../src/utils'; +import { MangoClient } from '../src/client'; +import { MANGO_V4_ID } from '../src/constants'; +import { toUiDecimalsForQuote } from '../src/utils'; dotenv.config(); const CLUSTER_URL = From 0ba41e34718e0cf9ad5ed17242a40c124b52f52d Mon Sep 17 00:00:00 2001 From: Nicholas Clarke Date: Thu, 6 Apr 2023 22:09:50 -0700 Subject: [PATCH 47/48] Emit perp fees settled on update_funding. Required to have a full picture of total perp market fees. (#530) --- mango_v4.json | 5 +++++ programs/mango-v4/src/logs.rs | 1 + programs/mango-v4/src/state/perp_market.rs | 1 + ts/client/src/mango_v4.ts | 10 ++++++++++ 4 files changed, 17 insertions(+) diff --git a/mango_v4.json b/mango_v4.json index c44c6fd27..60bd374ed 100644 --- a/mango_v4.json +++ b/mango_v4.json @@ -7659,6 +7659,11 @@ "type": "i128", "index": false }, + { + "name": "feesSettled", + "type": "i128", + "index": false + }, { "name": "openInterest", "type": "i64", diff --git a/programs/mango-v4/src/logs.rs b/programs/mango-v4/src/logs.rs index 4c42f01d2..d80105cdc 100644 --- a/programs/mango-v4/src/logs.rs +++ b/programs/mango-v4/src/logs.rs @@ -146,6 +146,7 @@ pub struct PerpUpdateFundingLog { pub price: i128, pub stable_price: i128, pub fees_accrued: i128, + pub fees_settled: i128, pub open_interest: i64, pub instantaneous_funding_rate: i128, } diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index 67b640e1c..3794f639f 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -317,6 +317,7 @@ impl PerpMarket { price: oracle_price.to_bits(), stable_price: self.stable_price().to_bits(), fees_accrued: self.fees_accrued.to_bits(), + fees_settled: self.fees_settled.to_bits(), open_interest: self.open_interest, instantaneous_funding_rate: funding_rate.to_bits(), }); diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 47e161657..81401922a 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -7659,6 +7659,11 @@ export type MangoV4 = { "type": "i128", "index": false }, + { + "name": "feesSettled", + "type": "i128", + "index": false + }, { "name": "openInterest", "type": "i64", @@ -16344,6 +16349,11 @@ export const IDL: MangoV4 = { "type": "i128", "index": false }, + { + "name": "feesSettled", + "type": "i128", + "index": false + }, { "name": "openInterest", "type": "i64", From b49e41a2eb7662aac75d117f7907c26236049ab6 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Fri, 7 Apr 2023 14:04:32 +0100 Subject: [PATCH 48/48] Fix paths in archived scripts --- ts/client/scripts/archive/devnet-admin.ts | 8 ++++---- ts/client/scripts/archive/devnet-user-2.ts | 4 ++-- .../archive/devnet-user-close-account.ts | 6 +++--- ts/client/scripts/archive/devnet-user.ts | 12 ++++++------ ts/client/scripts/archive/mb-admin-close.ts | 4 ++-- ts/client/scripts/archive/mb-admin.ts | 18 +++++++++--------- ts/client/scripts/archive/mb-close-account.ts | 6 +++--- .../scripts/archive/mb-force-close-account.ts | 6 +++--- ts/client/scripts/archive/mb-oracle-inspect.ts | 4 ++-- .../mb-sim-accounts-with-param-change.ts | 12 ++++++------ ts/client/scripts/archive/mb-user.ts | 4 ++-- .../scripts/liqtest/liqtest-make-candidates.ts | 16 ++++++++-------- .../liqtest/liqtest-settle-and-close-all.ts | 4 ++-- 13 files changed, 52 insertions(+), 52 deletions(-) diff --git a/ts/client/scripts/archive/devnet-admin.ts b/ts/client/scripts/archive/devnet-admin.ts index 71b7f8bd1..0210b2163 100644 --- a/ts/client/scripts/archive/devnet-admin.ts +++ b/ts/client/scripts/archive/devnet-admin.ts @@ -6,10 +6,10 @@ import { PublicKey, } from '@solana/web3.js'; import fs from 'fs'; -import { PerpMarketIndex } from '../src/accounts/perp'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; -import { buildVersionedTx } from '../src/utils'; +import { PerpMarketIndex } from '../../src/accounts/perp'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; +import { buildVersionedTx } from '../../src/utils'; // // An example for admins based on high level api i.e. the client diff --git a/ts/client/scripts/archive/devnet-user-2.ts b/ts/client/scripts/archive/devnet-user-2.ts index 3a469cb78..3d2fe29f8 100644 --- a/ts/client/scripts/archive/devnet-user-2.ts +++ b/ts/client/scripts/archive/devnet-user-2.ts @@ -1,8 +1,8 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // An example for users based on high level api i.e. the client diff --git a/ts/client/scripts/archive/devnet-user-close-account.ts b/ts/client/scripts/archive/devnet-user-close-account.ts index 1f3c43422..af4ee78d7 100644 --- a/ts/client/scripts/archive/devnet-user-close-account.ts +++ b/ts/client/scripts/archive/devnet-user-close-account.ts @@ -1,9 +1,9 @@ import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; -import { Serum3Side } from '../src/accounts/serum3'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { Serum3Side } from '../../src/accounts/serum3'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // script which shows how to close a mango account cleanly i.e. close all active positions, withdraw all tokens, etc. diff --git a/ts/client/scripts/archive/devnet-user.ts b/ts/client/scripts/archive/devnet-user.ts index 87189f628..669c71cd5 100644 --- a/ts/client/scripts/archive/devnet-user.ts +++ b/ts/client/scripts/archive/devnet-user.ts @@ -2,12 +2,12 @@ import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { expect } from 'chai'; import fs from 'fs'; -import { Group } from '../src/accounts/group'; -import { HealthType } from '../src/accounts/mangoAccount'; -import { PerpOrderSide, PerpOrderType } from '../src/accounts/perp'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; -import { toUiDecimalsForQuote } from '../src/utils'; +import { Group } from '../../src/accounts/group'; +import { HealthType } from '../../src/accounts/mangoAccount'; +import { PerpOrderSide, PerpOrderType } from '../../src/accounts/perp'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; +import { toUiDecimalsForQuote } from '../../src/utils'; // // An example for users based on high level api i.e. the client diff --git a/ts/client/scripts/archive/mb-admin-close.ts b/ts/client/scripts/archive/mb-admin-close.ts index d2449f52f..fabc5f579 100644 --- a/ts/client/scripts/archive/mb-admin-close.ts +++ b/ts/client/scripts/archive/mb-admin-close.ts @@ -1,8 +1,8 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // example script to close accounts - banks, markets, group etc. which require admin to be the signer diff --git a/ts/client/scripts/archive/mb-admin.ts b/ts/client/scripts/archive/mb-admin.ts index b3cd173fb..3658618e5 100644 --- a/ts/client/scripts/archive/mb-admin.ts +++ b/ts/client/scripts/archive/mb-admin.ts @@ -3,7 +3,7 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, NATIVE_MINT, TOKEN_PROGRAM_ID, -} from '../src/utils/spl'; +} from '../../src/utils/spl'; import { AddressLookupTableProgram, ComputeBudgetProgram, @@ -15,21 +15,21 @@ import { SystemProgram, } from '@solana/web3.js'; import fs from 'fs'; -import { TokenIndex } from '../src/accounts/bank'; -import { Group } from '../src/accounts/group'; +import { TokenIndex } from '../../src/accounts/bank'; +import { Group } from '../../src/accounts/group'; import { Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side, -} from '../src/accounts/serum3'; -import { Builder } from '../src/builder'; -import { MangoClient } from '../src/client'; +} from '../../src/accounts/serum3'; +import { Builder } from '../../src/builder'; +import { MangoClient } from '../../src/client'; import { NullPerpEditParams, NullTokenEditParams, -} from '../src/clientIxParamBuilder'; -import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../src/constants'; -import { buildVersionedTx, toNative } from '../src/utils'; +} from '../../src/clientIxParamBuilder'; +import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../../src/constants'; +import { buildVersionedTx, toNative } from '../../src/utils'; const GROUP_NUM = Number(process.env.GROUP_NUM || 0); diff --git a/ts/client/scripts/archive/mb-close-account.ts b/ts/client/scripts/archive/mb-close-account.ts index 13d6ad8ca..4dd3907e0 100644 --- a/ts/client/scripts/archive/mb-close-account.ts +++ b/ts/client/scripts/archive/mb-close-account.ts @@ -1,9 +1,9 @@ import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; -import { Serum3Side } from '../src/accounts/serum3'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { Serum3Side } from '../../src/accounts/serum3'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // (untested?) script which closes a mango account cleanly, first closes all positions, withdraws all tokens and then closes it diff --git a/ts/client/scripts/archive/mb-force-close-account.ts b/ts/client/scripts/archive/mb-force-close-account.ts index 44eea6b76..e06cda03b 100644 --- a/ts/client/scripts/archive/mb-force-close-account.ts +++ b/ts/client/scripts/archive/mb-force-close-account.ts @@ -1,9 +1,9 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { Group } from '../src/accounts/group'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { Group } from '../../src/accounts/group'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; const GROUP_NUM = Number(process.env.GROUP_NUM || 0); const MANGO_ACCOUNT_PK = process.env.MANGO_ACCOUNT_PK; diff --git a/ts/client/scripts/archive/mb-oracle-inspect.ts b/ts/client/scripts/archive/mb-oracle-inspect.ts index 146934a0f..f8ebbe13f 100644 --- a/ts/client/scripts/archive/mb-oracle-inspect.ts +++ b/ts/client/scripts/archive/mb-oracle-inspect.ts @@ -4,8 +4,8 @@ import { isPythOracle, isSwitchboardOracle, parseSwitchboardOracle, -} from '../src/accounts/oracle'; -import { toNativeI80F48 } from '../src/utils'; +} from '../../src/accounts/oracle'; +import { toNativeI80F48 } from '../../src/utils'; const { MB_CLUSTER_URL } = process.env; async function decodePrice(conn, ai): Promise { diff --git a/ts/client/scripts/archive/mb-sim-accounts-with-param-change.ts b/ts/client/scripts/archive/mb-sim-accounts-with-param-change.ts index afecf1229..19dc1aef9 100644 --- a/ts/client/scripts/archive/mb-sim-accounts-with-param-change.ts +++ b/ts/client/scripts/archive/mb-sim-accounts-with-param-change.ts @@ -1,12 +1,12 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { HealthType } from '../src/accounts/mangoAccount'; -import { PerpMarketIndex } from '../src/accounts/perp'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; -import { I80F48 } from '../src/numbers/I80F48'; -import { toUiDecimalsForQuote } from '../src/utils'; +import { HealthType } from '../../src/accounts/mangoAccount'; +import { PerpMarketIndex } from '../../src/accounts/perp'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; +import { I80F48 } from '../../src/numbers/I80F48'; +import { toUiDecimalsForQuote } from '../../src/utils'; const CLUSTER_URL = process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL; diff --git a/ts/client/scripts/archive/mb-user.ts b/ts/client/scripts/archive/mb-user.ts index c2d7cd0db..ff6b3d232 100644 --- a/ts/client/scripts/archive/mb-user.ts +++ b/ts/client/scripts/archive/mb-user.ts @@ -1,8 +1,8 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; -import { HealthType } from '../src/accounts/mangoAccount'; -import { MangoClient, MANGO_V4_ID, toUiDecimalsForQuote } from '../src/index'; +import { HealthType } from '../../src/accounts/mangoAccount'; +import { MangoClient, MANGO_V4_ID, toUiDecimalsForQuote } from '../../src/index'; async function main() { const options = AnchorProvider.defaultOptions(); diff --git a/ts/client/scripts/liqtest/liqtest-make-candidates.ts b/ts/client/scripts/liqtest/liqtest-make-candidates.ts index 3dac48399..baff01af3 100644 --- a/ts/client/scripts/liqtest/liqtest-make-candidates.ts +++ b/ts/client/scripts/liqtest/liqtest-make-candidates.ts @@ -1,21 +1,21 @@ import { AnchorProvider, BN, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import fs from 'fs'; -import { Bank } from '../src/accounts/bank'; -import { MangoAccount } from '../src/accounts/mangoAccount'; -import { PerpMarket, PerpOrderSide, PerpOrderType } from '../src/accounts/perp'; +import { Bank } from '../../src/accounts/bank'; +import { MangoAccount } from '../../src/accounts/mangoAccount'; +import { PerpMarket, PerpOrderSide, PerpOrderType } from '../../src/accounts/perp'; import { Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side, -} from '../src/accounts/serum3'; -import { Builder } from '../src/builder'; -import { MangoClient } from '../src/client'; +} from '../../src/accounts/serum3'; +import { Builder } from '../../src/builder'; +import { MangoClient } from '../../src/client'; import { NullPerpEditParams, NullTokenEditParams, -} from '../src/clientIxParamBuilder'; -import { MANGO_V4_ID } from '../src/constants'; +} from '../../src/clientIxParamBuilder'; +import { MANGO_V4_ID } from '../../src/constants'; // // This script creates liquidation candidates diff --git a/ts/client/scripts/liqtest/liqtest-settle-and-close-all.ts b/ts/client/scripts/liqtest/liqtest-settle-and-close-all.ts index b61046826..a79efcfd6 100644 --- a/ts/client/scripts/liqtest/liqtest-settle-and-close-all.ts +++ b/ts/client/scripts/liqtest/liqtest-settle-and-close-all.ts @@ -1,8 +1,8 @@ import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { Connection, Keypair } from '@solana/web3.js'; import fs from 'fs'; -import { MangoClient } from '../src/client'; -import { MANGO_V4_ID } from '../src/constants'; +import { MangoClient } from '../../src/client'; +import { MANGO_V4_ID } from '../../src/constants'; // // This script tries to withdraw all positive balances for all accounts