borsh serialization

This commit is contained in:
De Facto 2021-02-03 12:44:17 +08:00
parent 89c1e56aa9
commit d45b362dc6
5 changed files with 198 additions and 38 deletions

View File

@ -6,7 +6,8 @@
"testnetDefaultChannel": "v1.4.8",
"scripts": {
"solink": "ts-node src/cli.ts",
"build:program": "solray build program"
"build:program": "solray build program",
"watch": "yarn ts-node-dev --respawn"
},
"keywords": [],
"author": "",
@ -23,6 +24,7 @@
"@tsconfig/recommended": "^1.0.1",
"@types/node": "^14.14.12",
"ts-node": "^9.1.1",
"ts-node-dev": "^1.1.1",
"typescript": "^4.1.2"
}
}

View File

@ -17,6 +17,10 @@ import {
// @ts-ignore
// import BufferLayout from "buffer-layout";
import { schema } from "./schema"
import * as encoding from "./schema"
import { deserialize, serialize } from "borsh"
export const AggregatorLayout = BufferLayout.struct([
BufferLayout.blob(4, "submitInterval"),
uint64("minSubmissionValue"),
@ -132,43 +136,16 @@ export default class FluxAggregator extends BaseProgram {
}
private initializeInstruction(params: InitializeInstructionParams): TransactionInstruction {
let {
aggregator,
description,
submitInterval,
minSubmissionValue,
maxSubmissionValue,
submissionDecimals,
owner,
} = params;
let { aggregator, owner } = params
// FIXME: hmm... should this throw error or what?
description = description.substr(0, 32).toUpperCase().padEnd(32)
const input = encoding.Initialize.serialize(params)
// console.log(input.toString("hex"))
const layout = BufferLayout.struct([
BufferLayout.u8("instruction"),
BufferLayout.blob(4, "submitInterval"),
uint64("minSubmissionValue"),
uint64("maxSubmissionValue"),
BufferLayout.u8("submissionDecimals"),
BufferLayout.blob(32, "description"),
]);
const buf = Buffer.allocUnsafe(4);
buf.writeUInt32LE(submitInterval);
return this.instructionEncode(layout, {
instruction: 0, // initialize instruction
submitInterval: buf,
minSubmissionValue: u64LEBuffer(minSubmissionValue),
maxSubmissionValue: u64LEBuffer(maxSubmissionValue),
submissionDecimals,
description: Buffer.from(description),
}, [
return this.instruction(input, [
SYSVAR_RENT_PUBKEY,
{ write: aggregator },
owner
]);
owner,
])
}
public async addOracle(params: AddOracleParams): Promise<Account> {

View File

@ -150,9 +150,9 @@ cli
submitInterval: parseInt(submitInterval),
minSubmissionValue: BigInt(minSubmissionValue),
maxSubmissionValue: BigInt(maxSubmissionValue),
description: feedName.substr(0, 32).padEnd(32),
description: feedName,
submissionDecimals,
owner: wallet.account
owner: wallet.account,
})
})
@ -213,7 +213,7 @@ cli
const { feedAddress, oracleAddress } = opts
const { aggregator } = await AppContext.forAdmin()
await aggregator.removeOracle({
aggregator: new PublicKey(feedAddress),
oracle: new PublicKey(oracleAddress),

179
src/schema.ts Normal file
View File

@ -0,0 +1,179 @@
import { PublicKey, Account } from "solray"
import BN from "bn.js"
import { deserialize, serialize } from "borsh"
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).toUpperCase().padEnd(32)
return Buffer.from(str, "utf8").slice(0, 32) // truncate at 32 bytes
},
decode: (bytes: Uint8Array) => {
return Buffer.from(bytes).toString("utf8").trim()
},
}
abstract class Serialization {
public static deserialize<T>(this: { new (data: any): T }, data: Buffer): T {
return deserialize(schema, this, data)
}
public static serialize<T extends Serialization>(
this: { new (data: any): T },
data: object
): Buffer {
return new this(data).serialize()
}
public serialize(): Buffer {
return Buffer.from(serialize(schema, this))
}
constructor(data) {
Object.assign(this, data)
}
}
class Submission {
public time!: BN
public value!: BN
public oracle!: PublicKey
public static schema = {
kind: "struct",
fields: [
["time", "u64"],
["value", "u64"],
["oracle", [32], pubkeyMapper],
],
}
constructor(data: any) {
Object.assign(this, data)
}
}
export class Aggregator extends Serialization {
public submitInterval!: number
public minSubmissionValue!: BN
public maxSubmissionValue!: BN
public submissions!: Submission[]
public static schema = {
kind: "struct",
fields: [
["submitInterval", "u32"],
["minSubmissionValue", "u64"],
["maxSubmissionValue", "u64"],
["submissionDecimals", "u8"],
["description", [32], str32Mapper], // fixed-sized-u8-array
["isInitialized", "u8", boolMapper], // no mapping for bool?
["owner", [32], pubkeyMapper],
["submissions", [Submission, 12]],
],
}
}
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: [
["submitInterval", "u32"],
["minSubmissionValue", "u64"],
["maxSubmissionValue", "u64"],
["submissionDecimals", "u8"],
["description", [32], str32Mapper],
],
}
}
export class Instruction extends Serialization {
public enum!: string
public static schema = {
kind: "enum",
field: "enum",
values: [[Initialize.name, Initialize]],
}
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
}
}
// if there is optional or variable length items, what is: borsh_utils::get_packed_len::<Submission>()?
//
// would panic given variable sized types
export const schema = new Map([
[Aggregator, Aggregator.schema],
[Submission, Submission.schema],
[Initialize, Initialize.schema],
[Instruction, Instruction.schema],
] as any) as any

View File

@ -94,7 +94,9 @@ export async function walletFromEnv(key: string, conn: Connection): Promise<Wall
throw new Error(`Set ${key} in .env to be a mnemonic`)
}
return Wallet.fromMnemonic(mnemonic, conn)
const wallet = await Wallet.fromMnemonic(mnemonic, conn)
console.log("using wallet:", wallet.address)
return wallet
}
export async function openDeployer(): Promise<Deployer> {