Merge branch 'dev' into feature/close-mango-account

This commit is contained in:
Adrian Brzeziński 2023-01-13 17:33:50 +01:00
commit 3077f57c2f
21 changed files with 178 additions and 153 deletions

View File

@ -11,7 +11,6 @@ jobs:
format:
name: Format
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout code
uses: actions/checkout@v3
@ -31,7 +30,6 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout code
uses: actions/checkout@v3
@ -51,7 +49,6 @@ jobs:
unit-test:
name: Unit Test
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout code
uses: actions/checkout@v3
@ -71,7 +68,6 @@ jobs:
semgrep:
name: Security Scan
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
container:
image: returntocorp/semgrep
@ -80,6 +76,12 @@ jobs:
uses: actions/checkout@v3
- name: Run semgrep
run: semgrep ci
run: semgrep ci --sarif --output=semgrep-results.sarif
env:
SEMGREP_RULES: p/typescript
- name: Upload output
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: semgrep-results.sarif

View File

@ -25,12 +25,11 @@ jobs:
trivy:
name: Dependency Scan
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout code
uses: actions/checkout@v3
# Report all vulnerabilities in CI output
# Report all vulnerabilities in security tab
- name: Report on all vulnerabilities
uses: aquasecurity/trivy-action@master
with:
@ -38,7 +37,8 @@ jobs:
scan-ref: 'Cargo.lock'
ignore-unfixed: true
hide-progress: true
format: 'table'
format: 'sarif'
output: 'trivy-results.sarif'
# Fail the job on critical vulnerabiliies with fix available
- name: Fail on critical vulnerabilities
@ -51,3 +51,9 @@ jobs:
format: 'table'
severity: 'CRITICAL'
exit-code: '1'
- name: Upload output
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'

View File

@ -11,12 +11,11 @@ jobs:
trivy:
name: Dependency Scan
runs-on: ubuntu-latest
if: (github.actor != 'dependabot[bot]')
steps:
- name: Checkout code
uses: actions/checkout@v3
# Report all vulnerabilities in CI output
# Report all vulnerabilities in security tab
- name: Report on all vulnerabilities
uses: aquasecurity/trivy-action@master
with:
@ -24,7 +23,8 @@ jobs:
scan-ref: 'yarn.lock'
ignore-unfixed: true
hide-progress: true
format: 'table'
format: 'sarif'
output: 'trivy-results.sarif'
# Fail the job on critical vulnerabiliies with fix available
- name: Fail on critical vulnerabilities
@ -37,3 +37,9 @@ jobs:
format: 'table'
severity: 'CRITICAL'
exit-code: '1'
- name: Upload output
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'

View File

@ -1,42 +1,55 @@
# Mango v4 Program Change Log
Update this for each mainnet deployment.
Update this for each program release and mainnet deployment.
## not on mainnet
## mainnet
Jan 5, 2023 Central European Standard Time
### v0.2.0, 2023-1-13
Deployment: Jan 13, 2023 at 11:31:05 Central European Standard Time, https://explorer.solana.com/tx/4yGRUk6QwntvC4umECDPDZJNcbevSJ1fdZi75Mz9rGa9SHKzUtjMF3V5FCTkzBZqAETQTccqv63BYw6yX8JNxiur
- Add an optional security authority with the ability to halt a group or
temporarily freeze user accounts.
- Extend perp pnl settle limits to apply to realized pnl
- Rename perp_liq_bankruptcy to perp_liq_quote_and_bankruptcy and extend it to
cover taking over the liqee's negative pnl while the settle limits and perp
settle health allow it.
- Perp bankruptcy is now allowed when settling is impossible, even when there are
spot assets remaining.
### Jan 5, 2023 Central European Standard Time
- Change max staleness slots from -1 to 600 for trustless token registering
Jan 4, 2023 Central European Standard Time
### Jan 4, 2023 Central European Standard Time
- Reduce only mode for tokens, and perp markets
- Perp settlement applies no loan origination fee
Dec 16, 2022 at 16:40 Central European Standard Time
### Dec 16, 2022 at 16:40 Central European Standard Time
Oct 8, 2022 at 14:38:31 Central European Summer Time
### Oct 8, 2022 at 14:38:31 Central European Summer Time
https://explorer.solana.com/tx/3m8EDohkgwJZyiwpGXztBWARWQVxyhnSNDVuH467D7FPS2wxJerr79HhdhDEed5hpConHgGsKHvxtW1HJP6GixX9
Oct 8, 2022 at 14:38:31 Central European Summer Time
### Oct 8, 2022 at 14:38:31 Central European Summer Time
https://explorer.solana.com/tx/3m8EDohkgwJZyiwpGXztBWARWQVxyhnSNDVuH467D7FPS2wxJerr79HhdhDEed5hpConHgGsKHvxtW1HJP6GixX9
- New ix `TokenDepositIntoExisting`
Sep 1, 2022 at 10:24:35 Central European Summer Time
### Sep 1, 2022 at 10:24:35 Central European Summer Time
https://explorer.solana.com/tx/3NnX13A3QwsREKKKo3iYR4jqgoongpCjdhhXuJ3y5iP6FwfPcNieVop623tpgPbyreC7m7KtphwdWdoHYE5YC394
- Add HealthRegionBegin, -End instructions
- Add explicit "oracle" account argument for TokenDeposit and TokenWithdraw instructions
Aug 20, 2022 at 19:58:29 Central European Summer Time
### Aug 20, 2022 at 19:58:29 Central European Summer Time
https://explorer.solana.com/tx/3R4frko1AekQKJmmQ5T6k3mdXF9uZVHTR7oocdspTPsc82xX7qrbgnG61r28UdhCxsjMxtQHgBqMc37FSvoHQfCN
- loan fee logging for off-chain services
Aug 18, 2022 at 17:17:40 Central European Summer Time
### Aug 18, 2022 at 17:17:40 Central European Summer Time
https://explorer.solana.com/tx/4Xnyswcwx98y6khw8ptNVmdhQZwJjuNy2BvmQg2pJayoThFiw8kmS2ecRAg5cg2DncvW3NQgn2vtP8mCUtv6Q1yB
- liq_token_bankruptcy: removed liab_token_index argument
@ -82,13 +95,13 @@ https://explorer.solana.com/tx/4Xnyswcwx98y6khw8ptNVmdhQZwJjuNy2BvmQg2pJayoThFiw
marginTrade takes inputMintPk and outputMintPk instead of inputToken and outputToken
marginTrade takes flashLoanType as an argument
Aug 8, 2022 at 18:56:04 Central European Summer Time
### Aug 8, 2022 at 18:56:04 Central European Summer Time
https://explorer.solana.com/tx/yjZggRTrcDNquMkftNvBKLv77Dk4xp5yQPYXgN3qvBHTBWWJVhLPGHxqpGwosmEq3j8byHZMa13oxLLerBWUdgW
- improved logging for off chain services
- `AccountCreate` ix takes explicit input for sizes of various features
Aug 4, 2022 at 09:30:00 Central European Summer Time
### Aug 4, 2022 at 09:30:00 Central European Summer Time
ts/client changes
@ -114,7 +127,7 @@ New features
- `TokenRegistration` and `TokenRegisterTrustless` ixs dont take a bank_num anymore, hardcoded to 0.
- Enforced a minimum maximum rate of 50% so that rates don't fall so low that they cannot recover.
Jul 14, 2022 at 09:33:52 Central European Summer Time
### Jul 14, 2022 at 09:33:52 Central European Summer Time
https://explorer.solana.com/tx/vZ5hP1vGp37fgzBfG9nb4nfA5ZdmYgk8meq53YPR4ReFxrcTwBUxTYBQUgnfAnq9u5fH36S3QTfb9mVkBXt5A6C
- Account data was rearranged to put fields that are often used with gPA first

8
Cargo.lock generated
View File

@ -1094,7 +1094,7 @@ dependencies = [
[[package]]
name = "cli"
version = "0.1.0"
version = "0.3.0"
dependencies = [
"anchor-client",
"anchor-lang",
@ -1118,7 +1118,7 @@ dependencies = [
[[package]]
name = "client"
version = "0.1.0"
version = "0.3.0"
dependencies = [
"anchor-client",
"anchor-lang",
@ -2846,7 +2846,7 @@ dependencies = [
[[package]]
name = "keeper"
version = "0.1.0"
version = "0.3.0"
dependencies = [
"anchor-client",
"anchor-lang",
@ -3121,7 +3121,7 @@ dependencies = [
[[package]]
name = "mango-v4"
version = "0.1.0"
version = "0.3.0"
dependencies = [
"anchor-lang",
"anchor-spl",

View File

@ -1,6 +1,6 @@
[package]
name = "cli"
version = "0.1.0"
version = "0.3.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,6 +1,6 @@
[package]
name = "client"
version = "0.1.0"
version = "0.3.0"
edition = "2021"
[lib]

View File

@ -1,6 +1,6 @@
[package]
name = "keeper"
version = "0.1.0"
version = "0.3.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,6 +1,6 @@
[package]
name = "mango-v4"
version = "0.1.0"
version = "0.3.0"
description = "Created with Anchor"
edition = "2021"

View File

@ -537,21 +537,6 @@ impl HealthCache {
self.has_spot_borrows() || perp_borrows
}
pub fn has_liquidatable_spot_or_perp_base(&self) -> bool {
let spot_liquidatable = self.has_spot_assets();
let serum3_cancelable = self.has_serum3_open_orders_funds();
let perp_liquidatable = self.perp_infos.iter().any(|p| {
// can use perp_liq_base_position
p.base_lots != 0
// can use perp_liq_force_cancel_orders
|| p.has_open_orders
// A remaining quote position can be reduced with perp_settle_pnl and that can improve health.
// However, since it's not guaranteed that there is a counterparty, a positive perp quote position
// does not prevent bankruptcy.
});
spot_liquidatable || serum3_cancelable || perp_liquidatable
}
pub(crate) fn compute_serum3_reservations(
&self,
health_type: HealthType,

View File

@ -1,11 +1,11 @@
use fixed::types::I80F48;
pub trait ClampedToNum {
pub trait ClampToInt {
fn clamp_to_i64(&self) -> i64;
fn clamp_to_u64(&self) -> u64;
}
impl ClampedToNum for I80F48 {
impl ClampToInt for I80F48 {
fn clamp_to_i64(&self) -> i64 {
if *self <= i64::MIN {
i64::MIN
@ -27,7 +27,7 @@ impl ClampedToNum for I80F48 {
}
}
impl ClampedToNum for f64 {
impl ClampToInt for f64 {
fn clamp_to_i64(&self) -> i64 {
if *self <= i64::MIN as f64 {
i64::MIN
@ -49,7 +49,7 @@ impl ClampedToNum for f64 {
}
}
impl ClampedToNum for u64 {
impl ClampToInt for u64 {
fn clamp_to_i64(&self) -> i64 {
if *self >= i64::MAX as u64 {
i64::MAX

View File

@ -71,24 +71,8 @@ pub fn perp_liq_base_position(
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
liqee_health_cache.require_after_phase1_liquidation()?;
// Once maint_health falls below 0, we want to start liquidating,
// we want to allow liquidation to continue until init_health is positive,
// to prevent constant oscillation between the two states
if liqee.being_liquidated() {
if liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health)
{
msg!("Liqee init_health above zero");
return Ok(());
}
} else {
let maint_health = liqee_health_cache.health(HealthType::Maint);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
liqee.fixed.set_being_liquidated(true);
if !liqee.check_liquidatable(&liqee_health_cache)? {
return Ok(());
}
let mut perp_market = ctx.accounts.perp_market.load_mut()?;

View File

@ -1,5 +1,4 @@
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use crate::error::*;
use crate::health::*;
@ -47,22 +46,20 @@ pub fn perp_liq_force_cancel_orders(
let health_cache =
new_health_cache(&account.borrow(), &retriever).context("create health cache")?;
if account.being_liquidated() {
let init_health = health_cache.health(HealthType::Init);
if account
.fixed
.maybe_recover_from_being_liquidated(init_health)
{
msg!("Liqee init_health above zero");
return Ok(());
{
let result = account.check_liquidatable(&health_cache);
if account.fixed.is_operational() {
if !result? {
return Ok(());
}
} else {
// Frozen accounts can always have their orders cancelled
if let Err(Error::AnchorError(ref inner)) = result {
if inner.error_code_number != MangoError::HealthMustBeNegative as u32 {
result?;
}
}
}
} else {
let maint_health = health_cache.health(HealthType::Maint);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
account.fixed.set_being_liquidated(true);
}
health_cache

View File

@ -118,24 +118,8 @@ pub fn perp_liq_quote_and_bankruptcy(
let liqee_settle_health = liqee_health_cache.perp_settle_health();
liqee_health_cache.require_after_phase2_liquidation()?;
// Once maint_health falls below 0, we want to start liquidating,
// we want to allow liquidation to continue until init_health is positive,
// to prevent constant oscillation between the two states
if liqee.being_liquidated() {
if liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health)
{
msg!("Liqee init_health above zero");
return Ok(());
}
} else {
let maint_health = liqee_health_cache.health(HealthType::Maint);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
liqee.fixed.set_being_liquidated(true);
if !liqee.check_liquidatable(&liqee_health_cache)? {
return Ok(());
}
// check positions exist/create them, done early for nicer error messages

View File

@ -1,6 +1,5 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{Token, TokenAccount};
use fixed::types::I80F48;
use crate::error::*;
use crate::health::*;
@ -126,22 +125,21 @@ pub fn serum3_liq_force_cancel_orders(
let health_cache =
new_health_cache(&account.borrow(), &retriever).context("create health cache")?;
if account.being_liquidated() {
let init_health = health_cache.health(HealthType::Init);
if account
.fixed
.maybe_recover_from_being_liquidated(init_health)
{
msg!("Liqee init_health above zero");
return Ok(());
{
let result = account.check_liquidatable(&health_cache);
if account.fixed.is_operational() {
if !result? {
return Ok(());
}
} else {
// Frozen accounts can always have their orders cancelled
if let Err(Error::AnchorError(ref inner)) = result {
if inner.error_code_number != MangoError::HealthMustBeNegative as u32 {
// propagate all unexpected errors
result?;
}
}
}
} else {
let maint_health = health_cache.health(HealthType::Maint);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
account.fixed.set_being_liquidated(true);
}
health_cache

View File

@ -69,21 +69,8 @@ pub fn token_liq_with_token(
let init_health = liqee_health_cache.health(HealthType::Init);
liqee_health_cache.require_after_phase1_liquidation()?;
// Once maint_health falls below 0, we want to start liquidating,
// we want to allow liquidation to continue until init_health is positive,
// to prevent constant oscillation between the two states
if liqee.being_liquidated() {
if liqee.fixed.maybe_recover_from_being_liquidated(init_health) {
msg!("Liqee init_health above zero");
return Ok(());
}
} else {
let maint_health = liqee_health_cache.health(HealthType::Maint);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
liqee.fixed.set_being_liquidated(true);
if !liqee.check_liquidatable(&liqee_health_cache)? {
return Ok(());
}
//

View File

@ -86,7 +86,7 @@ pub struct MangoAccount {
/// Init health as calculated during HealthReginBegin, rounded up.
pub health_region_begin_init_health: i64,
pub frozen_until: i64,
pub frozen_until: u64,
pub reserved: [u8; 232],
@ -968,6 +968,30 @@ impl<
Ok(())
}
pub fn check_liquidatable(&mut self, health_cache: &HealthCache) -> Result<bool> {
// Once maint_health falls below 0, we want to start liquidating,
// we want to allow liquidation to continue until init_health is positive,
// to prevent constant oscillation between the two states
if self.being_liquidated() {
let init_health = health_cache.health(HealthType::Init);
if self
.fixed_mut()
.maybe_recover_from_being_liquidated(init_health)
{
msg!("Liqee init_health above zero");
return Ok(false);
}
} else {
let maint_health = health_cache.health(HealthType::Maint);
require!(
maint_health < I80F48::ZERO,
MangoError::HealthMustBeNegative
);
self.fixed_mut().set_being_liquidated(true);
}
Ok(true)
}
// writes length of tokens vec at appropriate offset so that borsh can infer the vector length
// length used is that present in the header
fn write_token_length(&mut self) {

View File

@ -6,7 +6,7 @@ use static_assertions::const_assert_eq;
use std::cmp::Ordering;
use std::mem::size_of;
use crate::i80f48::ClampedToNum;
use crate::i80f48::ClampToInt;
use crate::state::*;
pub const FREE_ORDER_SLOT: PerpMarketIndex = PerpMarketIndex::MAX;

View File

@ -58,7 +58,7 @@ import {
} from './utils';
import { sendTransaction } from './utils/rpc';
enum AccountRetriever {
export enum AccountRetriever {
Scanning,
Fixed,
}
@ -2657,9 +2657,7 @@ export class MangoClient {
);
}
/// private
private buildHealthRemainingAccounts(
public buildHealthRemainingAccounts(
retriever: AccountRetriever,
group: Group,
mangoAccounts: MangoAccount[],
@ -2699,12 +2697,12 @@ export class MangoClient {
const tokenPositionIndices = mangoAccount.tokens.map((t) => t.tokenIndex);
for (const bank of banks) {
const tokenPositionExists =
tokenPositionIndices.indexOf(bank.tokenIndex) > 0;
tokenPositionIndices.indexOf(bank.tokenIndex) > -1;
if (!tokenPositionExists) {
const inactiveTokenPosition = tokenPositionIndices.findIndex(
(index) => index === TokenPosition.TokenIndexUnset,
);
if (inactiveTokenPosition) {
if (inactiveTokenPosition != -1) {
tokenPositionIndices[inactiveTokenPosition] = bank.tokenIndex;
}
}
@ -2724,12 +2722,12 @@ export class MangoClient {
const perpPositionIndices = mangoAccount.perps.map((p) => p.marketIndex);
for (const perpMarket of perpMarkets) {
const perpPositionExists =
perpPositionIndices.indexOf(perpMarket.perpMarketIndex) > 0;
perpPositionIndices.indexOf(perpMarket.perpMarketIndex) > -1;
if (!perpPositionExists) {
const inactivePerpPosition = perpPositionIndices.find(
const inactivePerpPosition = perpPositionIndices.findIndex(
(perpIdx) => perpIdx === PerpPosition.PerpMarketIndexUnset,
);
if (inactivePerpPosition) {
if (inactivePerpPosition != -1) {
perpPositionIndices[inactivePerpPosition] =
perpMarket.perpMarketIndex;
}
@ -2753,15 +2751,16 @@ export class MangoClient {
const ooPositionExists =
serumPositionIndices.findIndex(
(i) => i.marketIndex === serum3Market.marketIndex,
) > 0;
) > -1;
if (!ooPositionExists) {
const inactiveSerumPosition = serumPositionIndices.find(
const inactiveSerumPosition = serumPositionIndices.findIndex(
(serumPos) =>
serumPos.marketIndex === Serum3Orders.Serum3MarketIndexUnset,
);
if (inactiveSerumPosition) {
inactiveSerumPosition.marketIndex = serum3Market.marketIndex;
inactiveSerumPosition.openOrders = openOrderPk;
if (inactiveSerumPosition != -1) {
serumPositionIndices[inactiveSerumPosition].marketIndex =
serum3Market.marketIndex;
serumPositionIndices[inactiveSerumPosition].openOrders = openOrderPk;
}
}
}

View File

@ -1,5 +1,5 @@
export type MangoV4 = {
"version": "0.1.0",
"version": "0.2.0",
"name": "mango_v4",
"instructions": [
{
@ -4030,7 +4030,7 @@ export type MangoV4 = {
},
{
"name": "frozenUntil",
"type": "i64"
"type": "u64"
},
{
"name": "reserved",
@ -7717,7 +7717,7 @@ export type MangoV4 = {
};
export const IDL: MangoV4 = {
"version": "0.1.0",
"version": "0.2.0",
"name": "mango_v4",
"instructions": [
{
@ -11748,7 +11748,7 @@ export const IDL: MangoV4 = {
},
{
"name": "frozenUntil",
"type": "i64"
"type": "u64"
},
{
"name": "reserved",

View File

@ -509,6 +509,45 @@ async function makePerpMarketReduceOnly() {
);
}
async function makePerpMarketUntrusted() {
const result = await buildAdminClient();
const client = result[0];
const admin = result[1];
const creator = result[2];
const group = await client.getGroupForCreator(creator.publicKey, GROUP_NUM);
const perpMarket = group.getPerpMarketByName('BTC-PERP');
await client.perpEditMarket(
group,
perpMarket.perpMarketIndex,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
false,
null,
null,
null,
null,
null,
null,
null,
null,
null,
);
}
async function createAndPopulateAlt() {
const result = await buildAdminClient();
const client = result[0];
@ -669,6 +708,7 @@ async function main() {
try {
// await registerPerpMarkets();
// await makePerpMarketReduceOnly();
// await makePerpMarketUntrusted();
} catch (error) {
console.log(error);
}