implement deployer class
This commit is contained in:
parent
9c6199e270
commit
e223511bc3
|
@ -13,6 +13,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ltd/j-toml": "^1.6.0",
|
||||||
"@solana/web3.js": "^0.90.5",
|
"@solana/web3.js": "^0.90.5",
|
||||||
"buffer-layout": "^1.2.0",
|
"buffer-layout": "^1.2.0",
|
||||||
"commander": "^6.2.0",
|
"commander": "^6.2.0",
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { decodeOracleInfo } from "./utils"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// import BufferLayout from "buffer-layout";
|
// import BufferLayout from "buffer-layout";
|
||||||
|
|
||||||
import { schema } from "./schema"
|
import { AggregatorConfig, IAggregatorConfig, schema } from "./schema"
|
||||||
import * as encoding from "./schema"
|
import * as encoding from "./schema"
|
||||||
import { deserialize, serialize } from "borsh"
|
import { deserialize, serialize } from "borsh"
|
||||||
import { conn } from "./context"
|
import { conn } from "./context"
|
||||||
|
@ -51,7 +51,7 @@ export const SubmissionLayout = BufferLayout.struct([
|
||||||
])
|
])
|
||||||
|
|
||||||
interface InitializeParams {
|
interface InitializeParams {
|
||||||
config: encoding.AggregatorConfig
|
config: IAggregatorConfig
|
||||||
owner: Account
|
owner: Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,15 +90,17 @@ interface SubmitParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WithdrawParams {
|
interface WithdrawParams {
|
||||||
|
accounts: {
|
||||||
aggregator: PublicKey
|
aggregator: PublicKey
|
||||||
// withdraw to
|
|
||||||
receiver: PublicKey
|
faucet: { write: PublicKey },
|
||||||
// withdraw amount
|
faucetOwner: PublicKey,
|
||||||
amount: bigint
|
oracle: { write: PublicKey },
|
||||||
tokenAccount: PublicKey
|
oracleOwner: Account,
|
||||||
tokenOwner: PublicKey
|
receiver: { write: PublicKey },
|
||||||
// signer
|
}
|
||||||
authority: Account
|
|
||||||
|
faucetOwnerSeed: Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WithdrawInstructionParams extends WithdrawParams {}
|
interface WithdrawInstructionParams extends WithdrawParams {}
|
||||||
|
@ -115,7 +117,9 @@ export default class FluxAggregator extends BaseProgram {
|
||||||
const answer_submissions = new Account()
|
const answer_submissions = new Account()
|
||||||
const round_submissions = new Account()
|
const round_submissions = new Account()
|
||||||
|
|
||||||
const input = encoding.Initialize.serialize({ config: params.config })
|
const input = encoding.Initialize.serialize({
|
||||||
|
config: new AggregatorConfig(params.config),
|
||||||
|
})
|
||||||
|
|
||||||
await this.sendTx(
|
await this.sendTx(
|
||||||
[
|
[
|
||||||
|
@ -182,96 +186,95 @@ export default class FluxAggregator extends BaseProgram {
|
||||||
return oracle
|
return oracle
|
||||||
}
|
}
|
||||||
|
|
||||||
public async oracleInfo(pubkey: PublicKey) {
|
// public async oracleInfo(pubkey: PublicKey) {
|
||||||
const info = await this.conn.getAccountInfo(pubkey)
|
// const info = await this.conn.getAccountInfo(pubkey)
|
||||||
return decodeOracleInfo(info)
|
// return decodeOracleInfo(info)
|
||||||
}
|
// }
|
||||||
|
|
||||||
public async removeOracle(params: RemoveOracleParams): Promise<void> {
|
// public async removeOracle(params: RemoveOracleParams): Promise<void> {
|
||||||
await this.sendTx(
|
// await this.sendTx(
|
||||||
[this.removeOracleInstruction(params)],
|
// [this.removeOracleInstruction(params)],
|
||||||
[this.account, params.authority || this.wallet.account]
|
// [this.account, params.authority || this.wallet.account]
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
private removeOracleInstruction(
|
// private removeOracleInstruction(
|
||||||
params: RemoveOracleInstructionParams
|
// params: RemoveOracleInstructionParams
|
||||||
): TransactionInstruction {
|
// ): TransactionInstruction {
|
||||||
const { authority, aggregator, oracle } = params
|
// const { authority, aggregator, oracle } = params
|
||||||
|
|
||||||
const layout = BufferLayout.struct([
|
// const layout = BufferLayout.struct([
|
||||||
BufferLayout.u8("instruction"),
|
// BufferLayout.u8("instruction"),
|
||||||
BufferLayout.blob(32, "oracle"),
|
// BufferLayout.blob(32, "oracle"),
|
||||||
])
|
// ])
|
||||||
|
|
||||||
return this.instructionEncode(
|
// return this.instructionEncode(
|
||||||
layout,
|
// layout,
|
||||||
{
|
// {
|
||||||
instruction: 2, // remove oracle instruction
|
// instruction: 2, // remove oracle instruction
|
||||||
oracle: oracle.toBuffer(),
|
// oracle: oracle.toBuffer(),
|
||||||
},
|
// },
|
||||||
[
|
// [
|
||||||
//
|
// //
|
||||||
{ write: aggregator },
|
// { write: aggregator },
|
||||||
authority || this.wallet.account,
|
// authority || this.wallet.account,
|
||||||
]
|
// ]
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
public async submit(params: SubmitParams): Promise<void> {
|
public async submit(params: SubmitParams): Promise<void> {
|
||||||
const input = encoding.Submit.serialize(params)
|
const input = encoding.Submit.serialize(params)
|
||||||
|
|
||||||
let auths = [
|
let auths = [SYSVAR_CLOCK_PUBKEY, ...Object.values(params.accounts)]
|
||||||
SYSVAR_CLOCK_PUBKEY,
|
|
||||||
...Object.values(params.accounts),
|
|
||||||
]
|
|
||||||
|
|
||||||
await this.sendTx(
|
await this.sendTx(
|
||||||
[
|
[this.instruction(input, auths)],
|
||||||
this.instruction(input, auths),
|
|
||||||
],
|
|
||||||
[this.account, params.accounts.oracle_owner]
|
[this.account, params.accounts.oracle_owner]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withdraw(params: WithdrawParams): Promise<void> {
|
public async withdraw(params: WithdrawParams): Promise<void> {
|
||||||
|
const input = encoding.Withdraw.serialize(params)
|
||||||
|
|
||||||
|
let auths = [SPLToken.programID, ...Object.values(params.accounts)]
|
||||||
|
|
||||||
await this.sendTx(
|
await this.sendTx(
|
||||||
[this.withdrawInstruction(params)],
|
[this.instruction(input, auths)],
|
||||||
[this.account, params.authority]
|
[this.account, params.accounts.oracleOwner]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private withdrawInstruction(
|
// private withdrawInstruction(
|
||||||
params: WithdrawInstructionParams
|
// params: WithdrawInstructionParams
|
||||||
): TransactionInstruction {
|
// ): TransactionInstruction {
|
||||||
const {
|
// const {
|
||||||
aggregator,
|
// aggregator,
|
||||||
receiver,
|
// receiver,
|
||||||
amount,
|
// amount,
|
||||||
tokenOwner,
|
// tokenOwner,
|
||||||
tokenAccount,
|
// tokenAccount,
|
||||||
authority,
|
// authority,
|
||||||
} = params
|
// } = params
|
||||||
|
|
||||||
const layout = BufferLayout.struct([
|
// const layout = BufferLayout.struct([
|
||||||
BufferLayout.u8("instruction"),
|
// BufferLayout.u8("instruction"),
|
||||||
uint64("amount"),
|
// uint64("amount"),
|
||||||
])
|
// ])
|
||||||
|
|
||||||
return this.instructionEncode(
|
// return this.instructionEncode(
|
||||||
layout,
|
// layout,
|
||||||
{
|
// {
|
||||||
instruction: 4, // withdraw instruction
|
// instruction: 4, // withdraw instruction
|
||||||
amount: u64LEBuffer(amount),
|
// amount: u64LEBuffer(amount),
|
||||||
},
|
// },
|
||||||
[
|
// [
|
||||||
{ write: aggregator },
|
// { write: aggregator },
|
||||||
{ write: tokenAccount },
|
// { write: tokenAccount },
|
||||||
{ write: receiver },
|
// { write: receiver },
|
||||||
SPLToken.programID,
|
// SPLToken.programID,
|
||||||
tokenOwner,
|
// tokenOwner,
|
||||||
{ write: authority },
|
// { write: authority },
|
||||||
]
|
// ]
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ interface SubmitterConfig {
|
||||||
|
|
||||||
export class Submitter {
|
export class Submitter {
|
||||||
public aggregator!: Aggregator
|
public aggregator!: Aggregator
|
||||||
|
public oracle!: Oracle
|
||||||
public roundSubmissions!: Submissions
|
public roundSubmissions!: Submissions
|
||||||
public answerSubmissions!: Submissions
|
public answerSubmissions!: Submissions
|
||||||
public program: FluxAggregator
|
public program: FluxAggregator
|
||||||
|
@ -40,7 +41,7 @@ export class Submitter {
|
||||||
public oraclePK: PublicKey,
|
public oraclePK: PublicKey,
|
||||||
private oracleOwnerWallet: Wallet,
|
private oracleOwnerWallet: Wallet,
|
||||||
private priceFeed: IPriceFeed,
|
private priceFeed: IPriceFeed,
|
||||||
private cfg: SubmitterConfig,
|
private cfg: SubmitterConfig
|
||||||
) {
|
) {
|
||||||
this.program = new FluxAggregator(this.oracleOwnerWallet, programID)
|
this.program = new FluxAggregator(this.oracleOwnerWallet, programID)
|
||||||
|
|
||||||
|
@ -66,6 +67,10 @@ export class Submitter {
|
||||||
await Promise.all([this.observeAggregatorState(), this.observePriceFeed()])
|
await Promise.all([this.observeAggregatorState(), this.observePriceFeed()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async withdrawRewards() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private async observeAggregatorState() {
|
private async observeAggregatorState() {
|
||||||
conn.onAccountChange(this.aggregatorPK, async (info) => {
|
conn.onAccountChange(this.aggregatorPK, async (info) => {
|
||||||
this.aggregator = Aggregator.deserialize(info.data)
|
this.aggregator = Aggregator.deserialize(info.data)
|
||||||
|
@ -96,9 +101,13 @@ export class Submitter {
|
||||||
|
|
||||||
this.currentValue = new BN(price.value)
|
this.currentValue = new BN(price.value)
|
||||||
|
|
||||||
const valueDiff = this.aggregator.answer.median.sub(this.currentValue).abs()
|
const valueDiff = this.aggregator.answer.median
|
||||||
|
.sub(this.currentValue)
|
||||||
|
.abs()
|
||||||
if (valueDiff.lten(this.cfg.minValueChangeForNewRound)) {
|
if (valueDiff.lten(this.cfg.minValueChangeForNewRound)) {
|
||||||
this.logger.debug("price did not change enough to start a new round", { diff: valueDiff.toNumber()});
|
this.logger.debug("price did not change enough to start a new round", {
|
||||||
|
diff: valueDiff.toNumber(),
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +119,9 @@ export class Submitter {
|
||||||
// TODO: make it possible to be triggered by chainlink task
|
// TODO: make it possible to be triggered by chainlink task
|
||||||
// TODO: If from chainlink node, update state before running
|
// TODO: If from chainlink node, update state before running
|
||||||
|
|
||||||
|
this.oracle = await Oracle.load(this.oraclePK)
|
||||||
|
this.logger.debug("oracle", { oracle: this.oracle })
|
||||||
|
|
||||||
const { round } = this.aggregator
|
const { round } = this.aggregator
|
||||||
|
|
||||||
if (this.canSubmitToCurrentRound) {
|
if (this.canSubmitToCurrentRound) {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import toml from "@ltd/j-toml"
|
||||||
|
|
||||||
|
import fs from "fs"
|
||||||
|
import { Oracle } from "./schema"
|
||||||
|
|
||||||
|
function loadJSON(file: string): any {
|
||||||
|
return JSON.parse(fs.readFileSync(file, "utf8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
const aggregatorConfigDefaults = {
|
||||||
|
decimals: 0,
|
||||||
|
minSubmissions: 0,
|
||||||
|
maxSubmissions: 1,
|
||||||
|
restartDelay: 0,
|
||||||
|
rewardAmount: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadAggregatorSetup(file: string): AggregatorSetupFile {
|
||||||
|
let obj: AggregatorSetupFile = loadJSON(file)
|
||||||
|
|
||||||
|
for (let key of Object.keys(obj.aggregators)) {
|
||||||
|
obj.aggregators[key] = {
|
||||||
|
...aggregatorConfigDefaults,
|
||||||
|
...obj.aggregators[key],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OracleConfig {
|
||||||
|
owner: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AggregatorSetupConfig {
|
||||||
|
decimals: number
|
||||||
|
minSubmissions: number
|
||||||
|
maxSubmissions: number
|
||||||
|
restartDelay: number
|
||||||
|
rewardAmount: number
|
||||||
|
rewardTokenAccount?: string
|
||||||
|
|
||||||
|
oracles?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AggregatorSetupFile {
|
||||||
|
programID: string
|
||||||
|
|
||||||
|
aggregators: {
|
||||||
|
[key: string]: AggregatorSetupConfig
|
||||||
|
}
|
||||||
|
oracles: {
|
||||||
|
[key: string]: OracleConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// //
|
||||||
|
// export interface DeployManifest {
|
||||||
|
// programID:
|
||||||
|
// }
|
|
@ -0,0 +1,191 @@
|
||||||
|
import { stateFromJSON } from "./state"
|
||||||
|
import fs from "fs"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
BPFLoader,
|
||||||
|
ProgramAccount,
|
||||||
|
PublicKey,
|
||||||
|
SPLToken,
|
||||||
|
Wallet,
|
||||||
|
} from "solray"
|
||||||
|
import {
|
||||||
|
AggregatorSetupFile,
|
||||||
|
AggregatorSetupConfig,
|
||||||
|
loadAggregatorSetup,
|
||||||
|
OracleConfig,
|
||||||
|
} from "./config"
|
||||||
|
import FluxAggregator from "./FluxAggregator"
|
||||||
|
import { AggregatorConfig, IAggregatorConfig } from "./schema"
|
||||||
|
// import { AggregatorConfig } from "./schema"
|
||||||
|
|
||||||
|
interface OracleDeployInfo {
|
||||||
|
pubkey: PublicKey
|
||||||
|
owner: PublicKey
|
||||||
|
}
|
||||||
|
interface AggregatorDeployInfo {
|
||||||
|
pubkey: PublicKey
|
||||||
|
config: IAggregatorConfig
|
||||||
|
|
||||||
|
oracles: {
|
||||||
|
[key: string]: OracleDeployInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AggregatorDeployFile {
|
||||||
|
programID: PublicKey
|
||||||
|
|
||||||
|
aggregators: {
|
||||||
|
[key: string]: AggregatorDeployInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FLUX_AGGREGATOR_SO = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../build/flux_aggregator.so"
|
||||||
|
)
|
||||||
|
|
||||||
|
function jsonReviver(_key: string, val: any) {
|
||||||
|
if (val && typeof val == "object") {
|
||||||
|
if (val["type"] == "PublicKey") {
|
||||||
|
return new PublicKey(val.base58)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonReplacer(key: string, value: any) {
|
||||||
|
if (value && typeof value != "object") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.constructor == PublicKey) {
|
||||||
|
return {
|
||||||
|
type: "PublicKey",
|
||||||
|
base58: value.toBase58(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Deployer {
|
||||||
|
// file backed json state
|
||||||
|
public setup: AggregatorSetupFile
|
||||||
|
public state: AggregatorDeployFile
|
||||||
|
constructor(statePath: string, setupFile: string, private wallet: Wallet) {
|
||||||
|
this.state = stateFromJSON(
|
||||||
|
statePath,
|
||||||
|
{
|
||||||
|
aggregators: {},
|
||||||
|
} as any,
|
||||||
|
{
|
||||||
|
replacer: jsonReplacer,
|
||||||
|
reviver: jsonReviver,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.setup = loadAggregatorSetup(setupFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
async runAll() {
|
||||||
|
await this.deployProgram()
|
||||||
|
await this.createAggregators()
|
||||||
|
}
|
||||||
|
|
||||||
|
async deployProgram() {
|
||||||
|
if (this.state.programID) {
|
||||||
|
console.log("program deployed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO)
|
||||||
|
|
||||||
|
console.log(`deploying ${FLUX_AGGREGATOR_SO}...`)
|
||||||
|
const bpfLoader = new BPFLoader(this.wallet)
|
||||||
|
|
||||||
|
const account = await bpfLoader.load(programBinary)
|
||||||
|
this.state.programID = account.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAggregators() {
|
||||||
|
for (let key of Object.keys(this.setup.aggregators)) {
|
||||||
|
const aggregatorSetup = this.setup.aggregators[key]
|
||||||
|
|
||||||
|
let info = this.state.aggregators[key]
|
||||||
|
if (!info) {
|
||||||
|
this.state.aggregators[key] = await this.createAggregator(
|
||||||
|
key,
|
||||||
|
aggregatorSetup
|
||||||
|
)
|
||||||
|
info = this.state.aggregators[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${key} aggregator deployed`)
|
||||||
|
for (let oracleName of aggregatorSetup.oracles || []) {
|
||||||
|
const oracleSetup = this.setup.oracles[oracleName]
|
||||||
|
// TODO: check that key exists
|
||||||
|
|
||||||
|
let oinfo = info.oracles[oracleName]
|
||||||
|
if (!oinfo) {
|
||||||
|
oinfo = await this.createOracle(info, oracleName, oracleSetup)
|
||||||
|
|
||||||
|
// hmm... not triggering save
|
||||||
|
info.oracles[oracleName] = oinfo
|
||||||
|
}
|
||||||
|
console.log(`${key} added oracle:`, oracleSetup.owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get program() {
|
||||||
|
return new FluxAggregator(this.wallet, this.state.programID)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOracle(
|
||||||
|
aggregatorInfo: AggregatorDeployInfo,
|
||||||
|
name: string,
|
||||||
|
setup: OracleConfig
|
||||||
|
): Promise<OracleDeployInfo> {
|
||||||
|
const config = {
|
||||||
|
description: name,
|
||||||
|
aggregator: aggregatorInfo.pubkey,
|
||||||
|
aggregatorOwner: this.wallet.account,
|
||||||
|
oracleOwner: new PublicKey(setup.owner),
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await this.program.addOracle(config)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pubkey: account.publicKey,
|
||||||
|
owner: config.oracleOwner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAggregator(
|
||||||
|
name: string,
|
||||||
|
cfg: AggregatorSetupConfig
|
||||||
|
): Promise<AggregatorDeployInfo> {
|
||||||
|
const config = {
|
||||||
|
description: name,
|
||||||
|
decimals: cfg.decimals,
|
||||||
|
minSubmissions: cfg.minSubmissions,
|
||||||
|
maxSubmissions: cfg.maxSubmissions,
|
||||||
|
restartDelay: cfg.restartDelay,
|
||||||
|
rewardTokenAccount: new PublicKey(cfg.rewardTokenAccount || 0),
|
||||||
|
rewardAmount: cfg.rewardAmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await this.program.initialize({
|
||||||
|
// FIXME: move this into initialize method
|
||||||
|
config: new AggregatorConfig(config),
|
||||||
|
owner: this.wallet.account,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
pubkey: account.publicKey,
|
||||||
|
config,
|
||||||
|
oracles: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -104,7 +104,19 @@ class Submission {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AggregatorConfig extends Serialization {
|
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 decimals!: number
|
||||||
public description!: string
|
public description!: string
|
||||||
public restartDelay!: number
|
public restartDelay!: number
|
||||||
|
@ -248,7 +260,6 @@ export class AddOracle extends InstructionSerialization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class RemoveOracle extends InstructionSerialization {
|
export class RemoveOracle extends InstructionSerialization {
|
||||||
public static schema = {
|
public static schema = {
|
||||||
kind: "struct",
|
kind: "struct",
|
||||||
|
@ -256,6 +267,13 @@ export class RemoveOracle extends InstructionSerialization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Withdraw extends InstructionSerialization {
|
||||||
|
public static schema = {
|
||||||
|
kind: "struct",
|
||||||
|
fields: [["faucetOwnerSeed", ["u8"]]],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Submit extends InstructionSerialization {
|
export class Submit extends InstructionSerialization {
|
||||||
public static schema = {
|
public static schema = {
|
||||||
kind: "struct",
|
kind: "struct",
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import fs from "fs"
|
||||||
|
|
||||||
|
// stateFromJSON returns a JSON file backed object. When object mutates, will
|
||||||
|
// serialize the object as JSON to the specified path.
|
||||||
|
//
|
||||||
|
// with recursive proxy support similar to:
|
||||||
|
//
|
||||||
|
// https://stackoverflow.com/questions/41299642/how-to-use-javascript-proxy-for-nested-objects
|
||||||
|
export function stateFromJSON<T extends Object>(
|
||||||
|
filepath: string,
|
||||||
|
defaultObject: T,
|
||||||
|
opts: {
|
||||||
|
replacer?: Function
|
||||||
|
reviver?: Function
|
||||||
|
} = {}
|
||||||
|
): T {
|
||||||
|
let root = defaultObject
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync(filepath, "utf8")
|
||||||
|
// TODO: support custom revive
|
||||||
|
root = JSON.parse(data, opts.reviver as any)
|
||||||
|
console.log(`Loaded state object: ${filepath}`)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Init state object: ${filepath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
// should be sync to avoid write races
|
||||||
|
fs.writeFileSync(filepath, JSON.stringify(root, opts.replacer as any, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy = {
|
||||||
|
get(target, key) {
|
||||||
|
if (typeof target[key] === "object" && target[key] !== null) {
|
||||||
|
return new Proxy(target[key], proxy)
|
||||||
|
} else {
|
||||||
|
return target[key]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set(obj, prop, val) {
|
||||||
|
obj[prop] = val
|
||||||
|
save()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Proxy(root, proxy) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
// import { BigNumber } from "ethers";
|
||||||
|
// function jsonRevive(_key: string, val: any) {
|
||||||
|
// if (val && typeof val == "object") {
|
||||||
|
// if (val["type"] == "BigNumber") {
|
||||||
|
// return BigNumber.from(val["hex"]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return val;
|
||||||
|
// }
|
207
test.ts
207
test.ts
|
@ -3,11 +3,11 @@ dotenv.config()
|
||||||
|
|
||||||
import BN from "bn.js"
|
import BN from "bn.js"
|
||||||
|
|
||||||
import { BPFLoader, ProgramAccount, SPLToken, Wallet } from "solray"
|
import { ProgramAccount, SPLToken, Wallet } from "solray"
|
||||||
import { AppContext, conn, network } from "./src/context"
|
import { AppContext, conn, network } from "./src/context"
|
||||||
|
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import path from "path"
|
|
||||||
import { AggregatorConfig } from "./src/schema"
|
import { AggregatorConfig } from "./src/schema"
|
||||||
import FluxAggregator from "./src/FluxAggregator"
|
import FluxAggregator from "./src/FluxAggregator"
|
||||||
|
|
||||||
|
@ -15,138 +15,121 @@ import * as encoding from "./src/schema"
|
||||||
import { Account, AccountInfo, Connection, PublicKey } from "@solana/web3.js"
|
import { Account, AccountInfo, Connection, PublicKey } from "@solana/web3.js"
|
||||||
import { coinbase } from "./src/PriceFeed"
|
import { coinbase } from "./src/PriceFeed"
|
||||||
import { Submitter } from "./src/Submitter"
|
import { Submitter } from "./src/Submitter"
|
||||||
|
import { Deployer } from "./src/deploy"
|
||||||
|
|
||||||
const FLUX_AGGREGATOR_SO = path.resolve(__dirname, "build/flux_aggregator.so")
|
import { loadAggregatorSetup } from "./src/config"
|
||||||
|
|
||||||
|
import { stateFromJSON } from "./src/state"
|
||||||
async function main() {
|
async function main() {
|
||||||
|
|
||||||
let ctx = new AppContext()
|
let ctx = new AppContext()
|
||||||
|
|
||||||
let deployer = await ctx.deployer()
|
|
||||||
let adminWallet = await ctx.adminWallet()
|
let adminWallet = await ctx.adminWallet()
|
||||||
let oracleWallet = await ctx.oracleWallet()
|
const deployer = new Deployer(
|
||||||
|
`deploy2.${network}.json`,
|
||||||
console.log(network)
|
`config/setup.${network}.json`,
|
||||||
|
adminWallet
|
||||||
await conn.requestAirdrop(adminWallet.pubkey, 10 * 1e9)
|
|
||||||
console.log((await conn.getBalance(adminWallet.pubkey)) / 1e9)
|
|
||||||
|
|
||||||
let aggregatorProgram = await deployer.ensure(
|
|
||||||
"aggregatorProgram",
|
|
||||||
async () => {
|
|
||||||
const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO)
|
|
||||||
|
|
||||||
console.log(`deploying ${FLUX_AGGREGATOR_SO}...`)
|
|
||||||
const bpfLoader = new BPFLoader(adminWallet)
|
|
||||||
|
|
||||||
return bpfLoader.load(programBinary)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const spltoken = new SPLToken(adminWallet)
|
await deployer.runAll()
|
||||||
const rewardToken = await deployer.ensure("create reward token", async () => {
|
console.log("done")
|
||||||
return spltoken.initializeMint({
|
|
||||||
mintAuthority: adminWallet.pubkey,
|
|
||||||
decimals: 8,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const rewardTokenOwner = await ProgramAccount.forSeed(
|
return
|
||||||
Buffer.from("solink"),
|
|
||||||
aggregatorProgram.publicKey
|
|
||||||
)
|
|
||||||
|
|
||||||
const rewardTokenAccount = await deployer.ensure(
|
// let deployer = await ctx.deployer()
|
||||||
"initialize reward token account",
|
|
||||||
async () => {
|
|
||||||
const vault = await spltoken.initializeAccount({
|
|
||||||
token: rewardToken.publicKey,
|
|
||||||
owner: rewardTokenOwner.pubkey,
|
|
||||||
})
|
|
||||||
|
|
||||||
await spltoken.mintTo({
|
// let oracleWallet = await ctx.oracleWallet()
|
||||||
token: rewardToken.publicKey,
|
|
||||||
to: vault.publicKey,
|
|
||||||
amount: BigInt(1e6 * 1e8), // 1M
|
|
||||||
authority: adminWallet.pubkey,
|
|
||||||
})
|
|
||||||
|
|
||||||
return vault
|
// console.log(network)
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(await spltoken.mintInfo(rewardToken.publicKey))
|
// await conn.requestAirdrop(adminWallet.pubkey, 10 * 1e9)
|
||||||
|
// console.log((await conn.getBalance(adminWallet.pubkey)) / 1e9)
|
||||||
|
|
||||||
const program = new FluxAggregator(adminWallet, aggregatorProgram.publicKey)
|
// const spltoken = new SPLToken(adminWallet)
|
||||||
|
// const rewardToken = await deployer.ensure("create reward token", async () => {
|
||||||
|
// return spltoken.initializeMint({
|
||||||
|
// mintAuthority: adminWallet.pubkey,
|
||||||
|
// decimals: 8,
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
let aggregator = await deployer.ensure(
|
// const rewardTokenOwner = await ProgramAccount.forSeed(
|
||||||
"create btc:usd aggregator",
|
// Buffer.from("solink"),
|
||||||
async () => {
|
// aggregatorProgram.publicKey
|
||||||
let name = "btc:usd"
|
// )
|
||||||
return program.initialize({
|
|
||||||
config: new AggregatorConfig({
|
|
||||||
description: name,
|
|
||||||
decimals: 2,
|
|
||||||
minSubmissions: 1,
|
|
||||||
maxSubmissions: 3,
|
|
||||||
restartDelay: 0,
|
|
||||||
rewardAmount: BigInt(10),
|
|
||||||
rewardTokenAccount: rewardTokenAccount.publicKey,
|
|
||||||
}),
|
|
||||||
owner: adminWallet.account,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const N_ORACLES = 4
|
// const rewardTokenAccount = await deployer.ensure(
|
||||||
interface OracleRole {
|
// "initialize reward token account",
|
||||||
owner: Account
|
// async () => {
|
||||||
oracle: PublicKey
|
// const vault = await spltoken.initializeAccount({
|
||||||
}
|
// token: rewardToken.publicKey,
|
||||||
|
// owner: rewardTokenOwner.pubkey,
|
||||||
|
// })
|
||||||
|
|
||||||
const oracleRoles: OracleRole[] = []
|
// await spltoken.mintTo({
|
||||||
|
// token: rewardToken.publicKey,
|
||||||
|
// to: vault.publicKey,
|
||||||
|
// amount: BigInt(1e6 * 1e8), // 1M
|
||||||
|
// authority: adminWallet.pubkey,
|
||||||
|
// })
|
||||||
|
|
||||||
for (let i = 0; i < N_ORACLES; i++) {
|
// return vault
|
||||||
// TODO: probably put the desired oracles in a config file...
|
// }
|
||||||
let owner = await deployer.ensure(`create oracle[${i}] owner`, async () => {
|
// )
|
||||||
return new Account()
|
|
||||||
})
|
|
||||||
|
|
||||||
let oracle = await deployer.ensure(
|
// console.log(await spltoken.mintInfo(rewardToken.publicKey))
|
||||||
`add oracle[${i}] to btc:usd`,
|
|
||||||
async () => {
|
|
||||||
return program.addOracle({
|
|
||||||
description: "test-oracle",
|
|
||||||
aggregator: aggregator.publicKey,
|
|
||||||
aggregatorOwner: adminWallet.account,
|
|
||||||
oracleOwner: owner.publicKey,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
oracleRoles.push({ owner, oracle: oracle.publicKey })
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const role of oracleRoles) {
|
|
||||||
// const wallet = Wallet.from
|
|
||||||
const owner = Wallet.fromAccount(role.owner, conn)
|
|
||||||
await conn.requestAirdrop(owner.pubkey, 10 * 1e9)
|
|
||||||
console.log(owner.address, await conn.getBalance(owner.pubkey))
|
|
||||||
|
|
||||||
const priceFeed = coinbase("BTC/USD")
|
// const N_ORACLES = 4
|
||||||
const submitter = new Submitter(
|
// interface OracleRole {
|
||||||
aggregatorProgram.publicKey,
|
// owner: Account
|
||||||
aggregator.publicKey,
|
// oracle: PublicKey
|
||||||
role.oracle,
|
// }
|
||||||
owner,
|
|
||||||
priceFeed,
|
|
||||||
{
|
|
||||||
// don't submit value unless btc changes at least a dollar
|
|
||||||
minValueChangeForNewRound: 100,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
submitter.start()
|
// const oracleRoles: OracleRole[] = []
|
||||||
}
|
|
||||||
|
// for (let i = 0; i < N_ORACLES; i++) {
|
||||||
|
// // TODO: probably put the desired oracles in a config file...
|
||||||
|
// let owner = await deployer.ensure(`create oracle[${i}] owner`, async () => {
|
||||||
|
// return new Account()
|
||||||
|
// })
|
||||||
|
|
||||||
|
// let oracle = await deployer.ensure(
|
||||||
|
// `add oracle[${i}] to btc:usd`,
|
||||||
|
// async () => {
|
||||||
|
// return program.addOracle({
|
||||||
|
// description: "test-oracle",
|
||||||
|
// aggregator: aggregator.publicKey,
|
||||||
|
// aggregatorOwner: adminWallet.account,
|
||||||
|
// oracleOwner: owner.publicKey,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// oracleRoles.push({ owner, oracle: oracle.publicKey })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for (const role of oracleRoles) {
|
||||||
|
// // const wallet = Wallet.from
|
||||||
|
// const owner = Wallet.fromAccount(role.owner, conn)
|
||||||
|
// await conn.requestAirdrop(owner.pubkey, 10 * 1e9)
|
||||||
|
// console.log(owner.address, await conn.getBalance(owner.pubkey))
|
||||||
|
|
||||||
|
// const priceFeed = coinbase("BTC/USD")
|
||||||
|
// const submitter = new Submitter(
|
||||||
|
// aggregatorProgram.publicKey,
|
||||||
|
// aggregator.publicKey,
|
||||||
|
// role.oracle,
|
||||||
|
// owner,
|
||||||
|
// priceFeed,
|
||||||
|
// {
|
||||||
|
// // don't submit value unless btc changes at least a dollar
|
||||||
|
// minValueChangeForNewRound: 100,
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// submitter.start()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => console.log(err))
|
main().catch((err) => console.log(err))
|
||||||
|
|
Loading…
Reference in New Issue