Add oracleLastUpdatedSlot to Bank and PerpMarket (#395)

* Parse last updated slot from oracle

* Fix typo

* Remove unused package
This commit is contained in:
riordanp 2023-01-17 18:03:16 +00:00 committed by GitHub
parent 71b13efc95
commit 2f78bd336f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 34 deletions

View File

@ -32,7 +32,6 @@
"validate": "npm run typecheck && npm run lint && npm run format"
},
"devDependencies": {
"@jup-ag/core": "^1.0.0-beta.28",
"@tsconfig/recommended": "^1.0.1",
"@types/bs58": "^4.0.1",
"@types/chai": "^4.3.0",

View File

@ -59,6 +59,7 @@ export class Bank implements BankForHealth {
public util1: I80F48;
public _price: I80F48 | undefined;
public _uiPrice: number | undefined;
public _oracleLastUpdatedSlot: number | undefined;
public collectedFeesNative: I80F48;
public loanFeeRate: I80F48;
public loanOriginationFeeRate: I80F48;
@ -235,6 +236,7 @@ export class Bank implements BankForHealth {
this.dust = I80F48.from(dust);
this._price = undefined;
this._uiPrice = undefined;
this._oracleLastUpdatedSlot = undefined;
}
toString(): string {
@ -351,6 +353,15 @@ export class Bank implements BankForHealth {
return this._uiPrice;
}
get oracleLastUpdatedSlot(): number {
if (!this._oracleLastUpdatedSlot) {
throw new Error(
`Undefined oracleLastUpdatedSlot for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`,
);
}
return this._oracleLastUpdatedSlot;
}
nativeDeposits(): I80F48 {
return this.indexedDeposits.mul(this.depositIndex);
}

View File

@ -292,15 +292,17 @@ export class Group {
throw new Error(
`Undefined accountInfo object in reloadBankOraclePrices for ${bank.oracle}!`,
);
const { price, uiPrice } = await this.decodePriceFromOracleAi(
coder,
bank.oracle,
ai,
this.getMintDecimals(bank.mint),
client,
);
const { price, uiPrice, lastUpdatedSlot } =
await this.decodePriceFromOracleAi(
coder,
bank.oracle,
ai,
this.getMintDecimals(bank.mint),
client,
);
bank._price = price;
bank._uiPrice = uiPrice;
bank._oracleLastUpdatedSlot = lastUpdatedSlot;
}
}
}
@ -324,15 +326,18 @@ export class Group {
throw new Error(
`Undefined ai object in reloadPerpMarketOraclePrices for ${perpMarket.oracle}!`,
);
const { price, uiPrice } = await this.decodePriceFromOracleAi(
coder,
perpMarket.oracle,
ai,
perpMarket.baseDecimals,
client,
);
const { price, uiPrice, lastUpdatedSlot } =
await this.decodePriceFromOracleAi(
coder,
perpMarket.oracle,
ai,
perpMarket.baseDecimals,
client,
);
perpMarket._price = price;
perpMarket._uiPrice = uiPrice;
perpMarket._oracleLastUpdatedSlot = lastUpdatedSlot;
}),
);
}
@ -343,8 +348,8 @@ export class Group {
ai: AccountInfo<Buffer>,
baseDecimals: number,
client: MangoClient,
): Promise<{ price: I80F48; uiPrice: number }> {
let price, uiPrice;
): Promise<{ price: I80F48; uiPrice: number; lastUpdatedSlot: number }> {
let price, uiPrice, lastUpdatedSlot;
if (
!BorshAccountsCoder.accountDiscriminator('stubOracle').compare(
ai.data.slice(0, 8),
@ -353,21 +358,26 @@ export class Group {
const stubOracle = coder.decode('stubOracle', ai.data);
price = new I80F48(stubOracle.price.val);
uiPrice = this.toUiPrice(price, baseDecimals);
lastUpdatedSlot = stubOracle.lastUpdated.val;
} else if (isPythOracle(ai)) {
uiPrice = parsePriceData(ai.data).previousPrice;
const priceData = parsePriceData(ai.data);
uiPrice = priceData.previousPrice;
price = this.toNativePrice(uiPrice, baseDecimals);
lastUpdatedSlot = parseInt(priceData.lastSlot.toString());
} else if (isSwitchboardOracle(ai)) {
uiPrice = await parseSwitchboardOracle(
const priceData = await parseSwitchboardOracle(
ai,
client.program.provider.connection,
);
uiPrice = priceData.price;
price = this.toNativePrice(uiPrice, baseDecimals);
lastUpdatedSlot = priceData.lastUpdatedSlot;
} else {
throw new Error(
`Unknown oracle provider (parsing not implemented) for oracle ${oracle}, with owner ${ai.owner}!`,
);
}
return { price, uiPrice };
return { price, uiPrice, lastUpdatedSlot };
}
public async reloadVaults(client: MangoClient): Promise<void> {

View File

@ -48,21 +48,29 @@ export class StubOracle {
}
// https://gist.github.com/microwavedcola1/b741a11e6ee273a859f3ef00b35ac1f0
export function parseSwitcboardOracleV1(
accountInfo: AccountInfo<Buffer>,
): number {
return accountInfo.data.readDoubleLE(1 + 32 + 4 + 4);
export function parseSwitchboardOracleV1(accountInfo: AccountInfo<Buffer>): {
price: number;
lastUpdatedSlot: number;
} {
const price = accountInfo.data.readDoubleLE(1 + 32 + 4 + 4);
const lastUpdatedSlot = parseInt(
accountInfo.data.readBigUInt64LE(1 + 32 + 4 + 4 + 8).toString(),
);
return { price, lastUpdatedSlot };
}
export function parseSwitcboardOracleV2(
export function parseSwitchboardOracleV2(
program: SwitchboardProgram,
accountInfo: AccountInfo<Buffer>,
): number {
const aggregatorAccountData =
program.decodeLatestAggregatorValue(accountInfo);
if (!aggregatorAccountData)
): { price: number; lastUpdatedSlot: number } {
const price = program.decodeLatestAggregatorValue(accountInfo)!.toNumber();
const lastUpdatedSlot = program
.decodeAggregator(accountInfo)
.latestConfirmedRound!.roundOpenSlot!.toNumber();
if (!price || !lastUpdatedSlot)
throw new Error('Unable to parse Switchboard Oracle V2');
return aggregatorAccountData?.toNumber();
return { price, lastUpdatedSlot };
}
/**
@ -73,26 +81,26 @@ export function parseSwitcboardOracleV2(
export async function parseSwitchboardOracle(
accountInfo: AccountInfo<Buffer>,
connection: Connection,
): Promise<number> {
): Promise<{ price: number; lastUpdatedSlot: number }> {
if (accountInfo.owner.equals(SwitchboardProgram.devnetPid)) {
if (!sbv2DevnetProgram) {
sbv2DevnetProgram = await SwitchboardProgram.loadDevnet(connection);
}
return parseSwitcboardOracleV2(sbv2DevnetProgram, accountInfo);
return parseSwitchboardOracleV2(sbv2DevnetProgram, accountInfo);
}
if (accountInfo.owner.equals(SwitchboardProgram.mainnetPid)) {
if (!sbv2MainnetProgram) {
sbv2MainnetProgram = await SwitchboardProgram.loadMainnet(connection);
}
return parseSwitcboardOracleV2(sbv2MainnetProgram, accountInfo);
return parseSwitchboardOracleV2(sbv2MainnetProgram, accountInfo);
}
if (
accountInfo.owner.equals(SBV1_DEVNET_PID) ||
accountInfo.owner.equals(SBV1_MAINNET_PID)
) {
return parseSwitcboardOracleV1(accountInfo);
return parseSwitchboardOracleV1(accountInfo);
}
throw new Error(`Should not be reached!`);

View File

@ -48,6 +48,7 @@ export class PerpMarket {
public _price: I80F48;
public _uiPrice: number;
public _oracleLastUpdatedSlot: number;
private priceLotsToUiConverter: number;
private baseLotsToUiConverter: number;
@ -246,6 +247,15 @@ export class PerpMarket {
return this._uiPrice;
}
get oracleLastUpdatedSlot(): number {
if (!this._oracleLastUpdatedSlot) {
throw new Error(
`Undefined oracleLastUpdatedSlot for perpMarket ${this.publicKey} with marketIndex ${this.perpMarketIndex}!`,
);
}
return this._oracleLastUpdatedSlot;
}
get minOrderSize(): number {
return this.baseLotsToUiConverter;
}