diff --git a/package.json b/package.json index b90c80e..1afd6dd 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,7 @@ "@project-serum/sol-wallet-adapter": "^0.1.4", "@solana/web3.js": "^0.90.0", "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0", - "borsh": "https://github.com/defactojob/borsh-js#field-mapper" + "buffer-layout": "^1.2.0" }, "browserslist": [ diff --git a/src/client.ts b/src/client.ts index 100d011..1082749 100644 --- a/src/client.ts +++ b/src/client.ts @@ -25,7 +25,7 @@ import { SRM_DECIMALS, TOKEN_PROGRAM_ID } from '@project-serum/serum/lib/token-i import { Order } from '@project-serum/serum/lib/market'; import Wallet from '@project-serum/sol-wallet-adapter'; import { makeCancelOrderInstruction, makeSettleFundsInstruction } from './instruction'; -import { Aggregator } from './schema' +// import { Aggregator } from './schema' export class MangoGroup { publicKey: PublicKey; @@ -58,11 +58,11 @@ export class MangoGroup { connection: Connection, ): Promise { - const aggs = await Promise.all(this.oracles.map((pk) => (Aggregator.loadWithConnection(pk, connection)))) - return aggs.map((agg) => (agg.answer.median.toNumber())).concat(1.0) + // const aggs = await Promise.all(this.oracles.map((pk) => (Aggregator.loadWithConnection(pk, connection)))) + // return aggs.map((agg) => (agg.answer.median.toNumber())).concat(1.0) - // const oracleAccs = await getMultipleAccounts(connection, this.oracles); - // return oracleAccs.map((oa) => decodeAggregatorInfo(oa.accountInfo).submissionValue).concat(1.0) + const oracleAccs = await getMultipleAccounts(connection, this.oracles); + return oracleAccs.map((oa) => decodeAggregatorInfo(oa.accountInfo).submissionValue).concat(1.0) } getMarketIndex(spotMarket: Market): number { diff --git a/src/schema.ts b/src/schema.ts index 0d1e3ce..2aeb156 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,406 +1,406 @@ -import BN from "bn.js" -import { deserialize, serialize } from "borsh" -import { Connection, PublicKey } from '@solana/web3.js'; - - -// const conn = new Connection("https://devnet.solana.com", 'singleGossip') - -const MAX_ORACLES = 13 - -const boolMapper = { - encode: boolToInt, - decode: intToBool, -} - -const pubkeyMapper = { - encode: (key: PublicKey) => { - // if (key.constructor == PublicKey) { - // // key. - // } else { - // key - // } - // TODO: support either account or public key - return key.toBuffer() - }, - - decode: (buf: Uint8Array) => { - return new PublicKey(buf) - }, -} - -// support strings that can be contained in at most 32 bytes -const str32Mapper = { - encode: (str: String) => { - str = str.substr(0, 32).padEnd(32) - return Buffer.from(str, "utf8").slice(0, 32) // truncate at 32 bytes - }, - - decode: (bytes: Uint8Array) => { - return Buffer.from(bytes).toString("utf8").trim() - }, -} - -const u64Date = { - encode: (date: Date) => { - return new BN(Math.floor(date.getTime() / 1000)) - }, - - decode: (unixtime: BN) => { - return new Date(unixtime.toNumber() * 1000) - }, -} - -export abstract class Serialization { - public static async loadWithConnection( - this: { new (data: any): T }, - key: PublicKey, - connection: Connection - ): Promise { - const info = await connection.getAccountInfo(key) - if (!info) { - throw new Error("account does not exist") - } - - return deserialize(schema, this, info.data) - } - // public static async load( - // this: { new (data: any): T }, - // key: PublicKey - // ): Promise { - // const info = await conn.getAccountInfo(key, "recent") - // if (!info) { - // throw new Error("account does not exist") - // } - // - // return deserialize(schema, this, info.data) - // } - - public static deserialize(this: { new (data: any): T }, data: Buffer): T { - return deserialize(schema, this, data) - } - - public static serialize( - this: { new (data: any): T }, - data: object - ): Buffer { - return new this(data).serialize() - } - - public serialize(): Buffer { - let buf = Buffer.from(serialize(schema, this)) - if (buf.length == 0) { - throw new Error("serialized buffer is 0. something wrong with schema") - } - return buf - } - - // public toJSON(pretty = true) { - // return JSON.stringify( - // this[Serialization.DATA_KEY], - // jsonReplacer, - // pretty ? 2 : 0 - // ) - // } - - // public static DATA_KEY = Symbol("DATA") - - constructor(data) { - // this[Serialization.DATA_KEY] = data - Object.assign(this, data) - } -} - -class Submission { - public updatedAt!: BN - public value!: BN - public oracle!: PublicKey - - public static schema = { - kind: "struct", - fields: [ - ["updatedAt", "u64"], - ["value", "u64"], - ["oracle", [32], pubkeyMapper], - ], - } - - constructor(data: any) { - Object.assign(this, data) - } -} - -export interface IAggregatorConfig { - decimals: number - description: string - restartDelay: number - rewardAmount: number - maxSubmissions: number - minSubmissions: number - rewardTokenAccount: PublicKey -} - -export class AggregatorConfig - extends Serialization - implements IAggregatorConfig { - public decimals!: number - public description!: string - public restartDelay!: number - public rewardAmount!: number - public maxSubmissions!: number - public minSubmissions!: number - public rewardTokenAccount!: PublicKey - - public static schema = { - kind: "struct", - fields: [ - ["description", [32], str32Mapper], - ["decimals", "u8"], - ["restartDelay", "u8"], - ["maxSubmissions", "u8"], - ["minSubmissions", "u8"], - ["rewardAmount", "u64"], - ["rewardTokenAccount", [32], pubkeyMapper], - ], - } -} - -export class Submissions extends Serialization { - public isInitialized!: boolean - public submissions!: Submission[] - - public static size = 625 - public static schema = { - kind: "struct", - fields: [ - ["isInitialized", "u8", boolMapper], - ["submissions", [Submission, MAX_ORACLES]], - ], - } - - // if not already submitted, and has empty spot - public canSubmit(pk: PublicKey, cfg: AggregatorConfig): boolean { - if (this.hadSubmitted(pk)) { - return false - } - - let emptyIndex = this.submissions.findIndex((s) => { - return s.updatedAt.isZero() - }) - - return emptyIndex > 0 && emptyIndex < cfg.maxSubmissions - } - - public hadSubmitted(pk: PublicKey): boolean { - return !!this.submissions.find((s) => { - return s.oracle.equals(pk) - }) - } -} - -export class Round extends Serialization { - public id!: BN - public createdAt!: BN - public updatedAt!: BN - - public static schema = { - kind: "struct", - fields: [ - ["id", "u64"], - ["createdAt", "u64"], - ["updatedAt", "u64"], - ], - } -} - -export class Answer extends Serialization { - public roundID!: BN - public median!: BN - public createdAt!: BN - public updatedAt!: BN - - public static schema = { - kind: "struct", - fields: [ - ["roundID", "u64"], - ["median", "u64"], - ["createdAt", "u64"], - ["updatedAt", "u64"], - ], - } -} - -export class Aggregator extends Serialization { - public static size = 229 - - public config!: AggregatorConfig - public roundSubmissions!: PublicKey - public answerSubmissions!: PublicKey - public answer!: Answer - public round!: Round - - public static schema = { - kind: "struct", - fields: [ - ["config", AggregatorConfig], - ["isInitialized", "u8", boolMapper], - ["owner", [32], pubkeyMapper], - ["round", Round], - ["roundSubmissions", [32], pubkeyMapper], - ["answer", Answer], - ["answerSubmissions", [32], pubkeyMapper], - ], - } - - -} - -abstract class InstructionSerialization extends Serialization { - public serialize(): Buffer { - return new Instruction({ [this.constructor.name]: this }).serialize() - } -} - -export class Initialize extends InstructionSerialization { - // public submitInterval!: number - // public minSubmissionValue!: number - // public maxSubmissionValue!: number - // public submissionDecimals!: number - // /// A short description of what is being reported - // public description!: string - - public static schema = { - kind: "struct", - fields: [["config", AggregatorConfig]], - } -} - -export class Configure extends InstructionSerialization { - public static schema = { - kind: "struct", - fields: [["config", AggregatorConfig]], - } -} - -export class AddOracle extends InstructionSerialization { - public static schema = { - kind: "struct", - fields: [["description", [32], str32Mapper]], - } -} - -export class RemoveOracle extends InstructionSerialization { - public static schema = { - kind: "struct", - fields: [], - } -} - -export class Withdraw extends InstructionSerialization { - public static schema = { - kind: "struct", - fields: [["faucetOwnerSeed", ["u8"]]], - } -} - -export class Submit extends InstructionSerialization { - public static schema = { - kind: "struct", - fields: [ - ["round_id", "u64"], - ["value", "u64"], - ], - } -} - -export class Instruction extends Serialization { - public enum!: string - - public static schema = { - kind: "enum", - field: "enum", - values: [ - [Initialize.name, Initialize], - [Configure.name, Configure], - [AddOracle.name, AddOracle], - [RemoveOracle.name, RemoveOracle], - [Submit.name, Submit], - ], - } - - public constructor(prop: { [key: string]: any }) { - super({}) - // deserializer calls the construction with `{ [enum]: value }`, so we need - // to figure out the enum type - // - // expect only one key-value (what a retarded interface) - for (let key of Object.keys(prop)) { - this.enum = key - this[key] = prop[key] - return - } - - throw new Error("not an expected enum object") - } - - public get value() { - return this[this.enum] - } -} - -function intToBool(i: number) { - if (i == 0) { - return false - } else { - return true - } -} - -function boolToInt(t: boolean) { - if (t) { - return 1 - } else { - return 0 - } -} - -export class Oracle extends Serialization { - public static size = 113 - public allowStartRound!: BN - public withdrawable!: BN - - public static schema = { - kind: "struct", - fields: [ - ["description", [32], str32Mapper], - ["isInitialized", "u8", boolMapper], - ["withdrawable", "u64"], - ["allowStartRound", "u64"], - ["aggregator", [32], pubkeyMapper], - ["owner", [32], pubkeyMapper], - ], - } - - public canStartNewRound(round: BN): boolean { - return this.allowStartRound.lte(round) - } -} - -// if there is optional or variable length items, what is: borsh_utils::get_packed_len::()? +// import BN from "bn.js" +// import { deserialize, serialize } from "borsh" +// import { Connection, PublicKey } from '@solana/web3.js'; // -// would panic given variable sized types - -export const schema = new Map([ - [Aggregator, Aggregator.schema], - [Oracle, Oracle.schema], - [Round, Round.schema], - [Answer, Answer.schema], - [AggregatorConfig, AggregatorConfig.schema], - [Submissions, Submissions.schema], - [Submission, Submission.schema], - - [Instruction, Instruction.schema], - [Initialize, Initialize.schema], - [AddOracle, AddOracle.schema], - [Submit, Submit.schema], - -] as any) as any +// +// // const conn = new Connection("https://devnet.solana.com", 'singleGossip') +// +// const MAX_ORACLES = 13 +// +// const boolMapper = { +// encode: boolToInt, +// decode: intToBool, +// } +// +// const pubkeyMapper = { +// encode: (key: PublicKey) => { +// // if (key.constructor == PublicKey) { +// // // key. +// // } else { +// // key +// // } +// // TODO: support either account or public key +// return key.toBuffer() +// }, +// +// decode: (buf: Uint8Array) => { +// return new PublicKey(buf) +// }, +// } +// +// // support strings that can be contained in at most 32 bytes +// const str32Mapper = { +// encode: (str: String) => { +// str = str.substr(0, 32).padEnd(32) +// return Buffer.from(str, "utf8").slice(0, 32) // truncate at 32 bytes +// }, +// +// decode: (bytes: Uint8Array) => { +// return Buffer.from(bytes).toString("utf8").trim() +// }, +// } +// +// const u64Date = { +// encode: (date: Date) => { +// return new BN(Math.floor(date.getTime() / 1000)) +// }, +// +// decode: (unixtime: BN) => { +// return new Date(unixtime.toNumber() * 1000) +// }, +// } +// +// export abstract class Serialization { +// public static async loadWithConnection( +// this: { new (data: any): T }, +// key: PublicKey, +// connection: Connection +// ): Promise { +// const info = await connection.getAccountInfo(key) +// if (!info) { +// throw new Error("account does not exist") +// } +// +// return deserialize(schema, this, info.data) +// } +// // public static async load( +// // this: { new (data: any): T }, +// // key: PublicKey +// // ): Promise { +// // const info = await conn.getAccountInfo(key, "recent") +// // if (!info) { +// // throw new Error("account does not exist") +// // } +// // +// // return deserialize(schema, this, info.data) +// // } +// +// public static deserialize(this: { new (data: any): T }, data: Buffer): T { +// return deserialize(schema, this, data) +// } +// +// public static serialize( +// this: { new (data: any): T }, +// data: object +// ): Buffer { +// return new this(data).serialize() +// } +// +// public serialize(): Buffer { +// let buf = Buffer.from(serialize(schema, this)) +// if (buf.length == 0) { +// throw new Error("serialized buffer is 0. something wrong with schema") +// } +// return buf +// } +// +// // public toJSON(pretty = true) { +// // return JSON.stringify( +// // this[Serialization.DATA_KEY], +// // jsonReplacer, +// // pretty ? 2 : 0 +// // ) +// // } +// +// // public static DATA_KEY = Symbol("DATA") +// +// constructor(data) { +// // this[Serialization.DATA_KEY] = data +// Object.assign(this, data) +// } +// } +// +// class Submission { +// public updatedAt!: BN +// public value!: BN +// public oracle!: PublicKey +// +// public static schema = { +// kind: "struct", +// fields: [ +// ["updatedAt", "u64"], +// ["value", "u64"], +// ["oracle", [32], pubkeyMapper], +// ], +// } +// +// constructor(data: any) { +// Object.assign(this, data) +// } +// } +// +// export interface IAggregatorConfig { +// decimals: number +// description: string +// restartDelay: number +// rewardAmount: number +// maxSubmissions: number +// minSubmissions: number +// rewardTokenAccount: PublicKey +// } +// +// export class AggregatorConfig +// extends Serialization +// implements IAggregatorConfig { +// public decimals!: number +// public description!: string +// public restartDelay!: number +// public rewardAmount!: number +// public maxSubmissions!: number +// public minSubmissions!: number +// public rewardTokenAccount!: PublicKey +// +// public static schema = { +// kind: "struct", +// fields: [ +// ["description", [32], str32Mapper], +// ["decimals", "u8"], +// ["restartDelay", "u8"], +// ["maxSubmissions", "u8"], +// ["minSubmissions", "u8"], +// ["rewardAmount", "u64"], +// ["rewardTokenAccount", [32], pubkeyMapper], +// ], +// } +// } +// +// export class Submissions extends Serialization { +// public isInitialized!: boolean +// public submissions!: Submission[] +// +// public static size = 625 +// public static schema = { +// kind: "struct", +// fields: [ +// ["isInitialized", "u8", boolMapper], +// ["submissions", [Submission, MAX_ORACLES]], +// ], +// } +// +// // if not already submitted, and has empty spot +// public canSubmit(pk: PublicKey, cfg: AggregatorConfig): boolean { +// if (this.hadSubmitted(pk)) { +// return false +// } +// +// let emptyIndex = this.submissions.findIndex((s) => { +// return s.updatedAt.isZero() +// }) +// +// return emptyIndex > 0 && emptyIndex < cfg.maxSubmissions +// } +// +// public hadSubmitted(pk: PublicKey): boolean { +// return !!this.submissions.find((s) => { +// return s.oracle.equals(pk) +// }) +// } +// } +// +// export class Round extends Serialization { +// public id!: BN +// public createdAt!: BN +// public updatedAt!: BN +// +// public static schema = { +// kind: "struct", +// fields: [ +// ["id", "u64"], +// ["createdAt", "u64"], +// ["updatedAt", "u64"], +// ], +// } +// } +// +// export class Answer extends Serialization { +// public roundID!: BN +// public median!: BN +// public createdAt!: BN +// public updatedAt!: BN +// +// public static schema = { +// kind: "struct", +// fields: [ +// ["roundID", "u64"], +// ["median", "u64"], +// ["createdAt", "u64"], +// ["updatedAt", "u64"], +// ], +// } +// } +// +// export class Aggregator extends Serialization { +// public static size = 229 +// +// public config!: AggregatorConfig +// public roundSubmissions!: PublicKey +// public answerSubmissions!: PublicKey +// public answer!: Answer +// public round!: Round +// +// public static schema = { +// kind: "struct", +// fields: [ +// ["config", AggregatorConfig], +// ["isInitialized", "u8", boolMapper], +// ["owner", [32], pubkeyMapper], +// ["round", Round], +// ["roundSubmissions", [32], pubkeyMapper], +// ["answer", Answer], +// ["answerSubmissions", [32], pubkeyMapper], +// ], +// } +// +// +// } +// +// abstract class InstructionSerialization extends Serialization { +// public serialize(): Buffer { +// return new Instruction({ [this.constructor.name]: this }).serialize() +// } +// } +// +// export class Initialize extends InstructionSerialization { +// // public submitInterval!: number +// // public minSubmissionValue!: number +// // public maxSubmissionValue!: number +// // public submissionDecimals!: number +// // /// A short description of what is being reported +// // public description!: string +// +// public static schema = { +// kind: "struct", +// fields: [["config", AggregatorConfig]], +// } +// } +// +// export class Configure extends InstructionSerialization { +// public static schema = { +// kind: "struct", +// fields: [["config", AggregatorConfig]], +// } +// } +// +// export class AddOracle extends InstructionSerialization { +// public static schema = { +// kind: "struct", +// fields: [["description", [32], str32Mapper]], +// } +// } +// +// export class RemoveOracle extends InstructionSerialization { +// public static schema = { +// kind: "struct", +// fields: [], +// } +// } +// +// export class Withdraw extends InstructionSerialization { +// public static schema = { +// kind: "struct", +// fields: [["faucetOwnerSeed", ["u8"]]], +// } +// } +// +// export class Submit extends InstructionSerialization { +// public static schema = { +// kind: "struct", +// fields: [ +// ["round_id", "u64"], +// ["value", "u64"], +// ], +// } +// } +// +// export class Instruction extends Serialization { +// public enum!: string +// +// public static schema = { +// kind: "enum", +// field: "enum", +// values: [ +// [Initialize.name, Initialize], +// [Configure.name, Configure], +// [AddOracle.name, AddOracle], +// [RemoveOracle.name, RemoveOracle], +// [Submit.name, Submit], +// ], +// } +// +// public constructor(prop: { [key: string]: any }) { +// super({}) +// // deserializer calls the construction with `{ [enum]: value }`, so we need +// // to figure out the enum type +// // +// // expect only one key-value (what a retarded interface) +// for (let key of Object.keys(prop)) { +// this.enum = key +// this[key] = prop[key] +// return +// } +// +// throw new Error("not an expected enum object") +// } +// +// public get value() { +// return this[this.enum] +// } +// } +// +// function intToBool(i: number) { +// if (i == 0) { +// return false +// } else { +// return true +// } +// } +// +// function boolToInt(t: boolean) { +// if (t) { +// return 1 +// } else { +// return 0 +// } +// } +// +// export class Oracle extends Serialization { +// public static size = 113 +// public allowStartRound!: BN +// public withdrawable!: BN +// +// public static schema = { +// kind: "struct", +// fields: [ +// ["description", [32], str32Mapper], +// ["isInitialized", "u8", boolMapper], +// ["withdrawable", "u64"], +// ["allowStartRound", "u64"], +// ["aggregator", [32], pubkeyMapper], +// ["owner", [32], pubkeyMapper], +// ], +// } +// +// public canStartNewRound(round: BN): boolean { +// return this.allowStartRound.lte(round) +// } +// } +// +// // if there is optional or variable length items, what is: borsh_utils::get_packed_len::()? +// // +// // would panic given variable sized types +// +// export const schema = new Map([ +// [Aggregator, Aggregator.schema], +// [Oracle, Oracle.schema], +// [Round, Round.schema], +// [Answer, Answer.schema], +// [AggregatorConfig, AggregatorConfig.schema], +// [Submissions, Submissions.schema], +// [Submission, Submission.schema], +// +// [Instruction, Instruction.schema], +// [Initialize, Initialize.schema], +// [AddOracle, AddOracle.schema], +// [Submit, Submit.schema], +// +// ] as any) as any diff --git a/yarn.lock b/yarn.lock index 0289e71..d7c542f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -661,7 +661,7 @@ dependencies: "@babel/types" "^7.3.0" -"@types/bn.js@^4.11.5", "@types/bn.js@^4.11.6": +"@types/bn.js@^4.11.6": version "4.11.6" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== @@ -1184,15 +1184,6 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -"borsh@https://github.com/defactojob/borsh-js#field-mapper": - version "0.3.1" - resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9" - dependencies: - "@types/bn.js" "^4.11.5" - bn.js "^5.0.0" - bs58 "^4.0.0" - text-encoding-utf-8 "^1.0.2" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1252,7 +1243,7 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" -bs58@^4.0.0, bs58@^4.0.1: +bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= @@ -4791,11 +4782,6 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-encoding-utf-8@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" - integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"