Add oracleLastUpdatedSlot to Bank and PerpMarket (#395)
* Parse last updated slot from oracle * Fix typo * Remove unused package
This commit is contained in:
parent
71b13efc95
commit
2f78bd336f
|
@ -32,7 +32,6 @@
|
||||||
"validate": "npm run typecheck && npm run lint && npm run format"
|
"validate": "npm run typecheck && npm run lint && npm run format"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jup-ag/core": "^1.0.0-beta.28",
|
|
||||||
"@tsconfig/recommended": "^1.0.1",
|
"@tsconfig/recommended": "^1.0.1",
|
||||||
"@types/bs58": "^4.0.1",
|
"@types/bs58": "^4.0.1",
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
|
|
|
@ -59,6 +59,7 @@ export class Bank implements BankForHealth {
|
||||||
public util1: I80F48;
|
public util1: I80F48;
|
||||||
public _price: I80F48 | undefined;
|
public _price: I80F48 | undefined;
|
||||||
public _uiPrice: number | undefined;
|
public _uiPrice: number | undefined;
|
||||||
|
public _oracleLastUpdatedSlot: number | undefined;
|
||||||
public collectedFeesNative: I80F48;
|
public collectedFeesNative: I80F48;
|
||||||
public loanFeeRate: I80F48;
|
public loanFeeRate: I80F48;
|
||||||
public loanOriginationFeeRate: I80F48;
|
public loanOriginationFeeRate: I80F48;
|
||||||
|
@ -235,6 +236,7 @@ export class Bank implements BankForHealth {
|
||||||
this.dust = I80F48.from(dust);
|
this.dust = I80F48.from(dust);
|
||||||
this._price = undefined;
|
this._price = undefined;
|
||||||
this._uiPrice = undefined;
|
this._uiPrice = undefined;
|
||||||
|
this._oracleLastUpdatedSlot = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
@ -351,6 +353,15 @@ export class Bank implements BankForHealth {
|
||||||
return this._uiPrice;
|
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 {
|
nativeDeposits(): I80F48 {
|
||||||
return this.indexedDeposits.mul(this.depositIndex);
|
return this.indexedDeposits.mul(this.depositIndex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,15 +292,17 @@ export class Group {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Undefined accountInfo object in reloadBankOraclePrices for ${bank.oracle}!`,
|
`Undefined accountInfo object in reloadBankOraclePrices for ${bank.oracle}!`,
|
||||||
);
|
);
|
||||||
const { price, uiPrice } = await this.decodePriceFromOracleAi(
|
const { price, uiPrice, lastUpdatedSlot } =
|
||||||
coder,
|
await this.decodePriceFromOracleAi(
|
||||||
bank.oracle,
|
coder,
|
||||||
ai,
|
bank.oracle,
|
||||||
this.getMintDecimals(bank.mint),
|
ai,
|
||||||
client,
|
this.getMintDecimals(bank.mint),
|
||||||
);
|
client,
|
||||||
|
);
|
||||||
bank._price = price;
|
bank._price = price;
|
||||||
bank._uiPrice = uiPrice;
|
bank._uiPrice = uiPrice;
|
||||||
|
bank._oracleLastUpdatedSlot = lastUpdatedSlot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,15 +326,18 @@ export class Group {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Undefined ai object in reloadPerpMarketOraclePrices for ${perpMarket.oracle}!`,
|
`Undefined ai object in reloadPerpMarketOraclePrices for ${perpMarket.oracle}!`,
|
||||||
);
|
);
|
||||||
const { price, uiPrice } = await this.decodePriceFromOracleAi(
|
|
||||||
coder,
|
const { price, uiPrice, lastUpdatedSlot } =
|
||||||
perpMarket.oracle,
|
await this.decodePriceFromOracleAi(
|
||||||
ai,
|
coder,
|
||||||
perpMarket.baseDecimals,
|
perpMarket.oracle,
|
||||||
client,
|
ai,
|
||||||
);
|
perpMarket.baseDecimals,
|
||||||
|
client,
|
||||||
|
);
|
||||||
perpMarket._price = price;
|
perpMarket._price = price;
|
||||||
perpMarket._uiPrice = uiPrice;
|
perpMarket._uiPrice = uiPrice;
|
||||||
|
perpMarket._oracleLastUpdatedSlot = lastUpdatedSlot;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -343,8 +348,8 @@ export class Group {
|
||||||
ai: AccountInfo<Buffer>,
|
ai: AccountInfo<Buffer>,
|
||||||
baseDecimals: number,
|
baseDecimals: number,
|
||||||
client: MangoClient,
|
client: MangoClient,
|
||||||
): Promise<{ price: I80F48; uiPrice: number }> {
|
): Promise<{ price: I80F48; uiPrice: number; lastUpdatedSlot: number }> {
|
||||||
let price, uiPrice;
|
let price, uiPrice, lastUpdatedSlot;
|
||||||
if (
|
if (
|
||||||
!BorshAccountsCoder.accountDiscriminator('stubOracle').compare(
|
!BorshAccountsCoder.accountDiscriminator('stubOracle').compare(
|
||||||
ai.data.slice(0, 8),
|
ai.data.slice(0, 8),
|
||||||
|
@ -353,21 +358,26 @@ export class Group {
|
||||||
const stubOracle = coder.decode('stubOracle', ai.data);
|
const stubOracle = coder.decode('stubOracle', ai.data);
|
||||||
price = new I80F48(stubOracle.price.val);
|
price = new I80F48(stubOracle.price.val);
|
||||||
uiPrice = this.toUiPrice(price, baseDecimals);
|
uiPrice = this.toUiPrice(price, baseDecimals);
|
||||||
|
lastUpdatedSlot = stubOracle.lastUpdated.val;
|
||||||
} else if (isPythOracle(ai)) {
|
} else if (isPythOracle(ai)) {
|
||||||
uiPrice = parsePriceData(ai.data).previousPrice;
|
const priceData = parsePriceData(ai.data);
|
||||||
|
uiPrice = priceData.previousPrice;
|
||||||
price = this.toNativePrice(uiPrice, baseDecimals);
|
price = this.toNativePrice(uiPrice, baseDecimals);
|
||||||
|
lastUpdatedSlot = parseInt(priceData.lastSlot.toString());
|
||||||
} else if (isSwitchboardOracle(ai)) {
|
} else if (isSwitchboardOracle(ai)) {
|
||||||
uiPrice = await parseSwitchboardOracle(
|
const priceData = await parseSwitchboardOracle(
|
||||||
ai,
|
ai,
|
||||||
client.program.provider.connection,
|
client.program.provider.connection,
|
||||||
);
|
);
|
||||||
|
uiPrice = priceData.price;
|
||||||
price = this.toNativePrice(uiPrice, baseDecimals);
|
price = this.toNativePrice(uiPrice, baseDecimals);
|
||||||
|
lastUpdatedSlot = priceData.lastUpdatedSlot;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown oracle provider (parsing not implemented) for oracle ${oracle}, with owner ${ai.owner}!`,
|
`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> {
|
public async reloadVaults(client: MangoClient): Promise<void> {
|
||||||
|
|
|
@ -48,21 +48,29 @@ export class StubOracle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://gist.github.com/microwavedcola1/b741a11e6ee273a859f3ef00b35ac1f0
|
// https://gist.github.com/microwavedcola1/b741a11e6ee273a859f3ef00b35ac1f0
|
||||||
export function parseSwitcboardOracleV1(
|
export function parseSwitchboardOracleV1(accountInfo: AccountInfo<Buffer>): {
|
||||||
accountInfo: AccountInfo<Buffer>,
|
price: number;
|
||||||
): number {
|
lastUpdatedSlot: number;
|
||||||
return accountInfo.data.readDoubleLE(1 + 32 + 4 + 4);
|
} {
|
||||||
|
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,
|
program: SwitchboardProgram,
|
||||||
accountInfo: AccountInfo<Buffer>,
|
accountInfo: AccountInfo<Buffer>,
|
||||||
): number {
|
): { price: number; lastUpdatedSlot: number } {
|
||||||
const aggregatorAccountData =
|
const price = program.decodeLatestAggregatorValue(accountInfo)!.toNumber();
|
||||||
program.decodeLatestAggregatorValue(accountInfo);
|
const lastUpdatedSlot = program
|
||||||
if (!aggregatorAccountData)
|
.decodeAggregator(accountInfo)
|
||||||
|
.latestConfirmedRound!.roundOpenSlot!.toNumber();
|
||||||
|
|
||||||
|
if (!price || !lastUpdatedSlot)
|
||||||
throw new Error('Unable to parse Switchboard Oracle V2');
|
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(
|
export async function parseSwitchboardOracle(
|
||||||
accountInfo: AccountInfo<Buffer>,
|
accountInfo: AccountInfo<Buffer>,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
): Promise<number> {
|
): Promise<{ price: number; lastUpdatedSlot: number }> {
|
||||||
if (accountInfo.owner.equals(SwitchboardProgram.devnetPid)) {
|
if (accountInfo.owner.equals(SwitchboardProgram.devnetPid)) {
|
||||||
if (!sbv2DevnetProgram) {
|
if (!sbv2DevnetProgram) {
|
||||||
sbv2DevnetProgram = await SwitchboardProgram.loadDevnet(connection);
|
sbv2DevnetProgram = await SwitchboardProgram.loadDevnet(connection);
|
||||||
}
|
}
|
||||||
return parseSwitcboardOracleV2(sbv2DevnetProgram, accountInfo);
|
return parseSwitchboardOracleV2(sbv2DevnetProgram, accountInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accountInfo.owner.equals(SwitchboardProgram.mainnetPid)) {
|
if (accountInfo.owner.equals(SwitchboardProgram.mainnetPid)) {
|
||||||
if (!sbv2MainnetProgram) {
|
if (!sbv2MainnetProgram) {
|
||||||
sbv2MainnetProgram = await SwitchboardProgram.loadMainnet(connection);
|
sbv2MainnetProgram = await SwitchboardProgram.loadMainnet(connection);
|
||||||
}
|
}
|
||||||
return parseSwitcboardOracleV2(sbv2MainnetProgram, accountInfo);
|
return parseSwitchboardOracleV2(sbv2MainnetProgram, accountInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
accountInfo.owner.equals(SBV1_DEVNET_PID) ||
|
accountInfo.owner.equals(SBV1_DEVNET_PID) ||
|
||||||
accountInfo.owner.equals(SBV1_MAINNET_PID)
|
accountInfo.owner.equals(SBV1_MAINNET_PID)
|
||||||
) {
|
) {
|
||||||
return parseSwitcboardOracleV1(accountInfo);
|
return parseSwitchboardOracleV1(accountInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Should not be reached!`);
|
throw new Error(`Should not be reached!`);
|
||||||
|
|
|
@ -48,6 +48,7 @@ export class PerpMarket {
|
||||||
|
|
||||||
public _price: I80F48;
|
public _price: I80F48;
|
||||||
public _uiPrice: number;
|
public _uiPrice: number;
|
||||||
|
public _oracleLastUpdatedSlot: number;
|
||||||
|
|
||||||
private priceLotsToUiConverter: number;
|
private priceLotsToUiConverter: number;
|
||||||
private baseLotsToUiConverter: number;
|
private baseLotsToUiConverter: number;
|
||||||
|
@ -246,6 +247,15 @@ export class PerpMarket {
|
||||||
return this._uiPrice;
|
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 {
|
get minOrderSize(): number {
|
||||||
return this.baseLotsToUiConverter;
|
return this.baseLotsToUiConverter;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue