feed polling and oracle removal

This commit is contained in:
De Facto 2020-12-10 11:39:52 +00:00
parent 26f529d304
commit 3e097081c4
10 changed files with 126 additions and 102 deletions

2
.env
View File

@ -1,4 +1,4 @@
NETWORK=dev NETWORK=dev
DEPLOY_FILE=deploy.json DEPLOY_FILE=deploy.json
ADMIN_MNEMONIC="summer fuel twin history item learn flip marble ginger knee mix ten" ADMIN_MNEMONIC="summer fuel twin history item learn flip marble ginger knee mix ten"
ORACLE_MNEMONIC="pet retreat peasant wing search rug dwarf high city pill giggle dinner" ORACLE_MNEMONIC="amount smoke bar coil current trial toward minimum model pass moral liberty"

9
.gitignore vendored
View File

@ -1,4 +1,7 @@
/node_modules node_modules
/program/target target
/integration-example/target target
yarn.lock yarn.lock
build
.yarn
dist

View File

@ -42,7 +42,7 @@ yarn build:program
``` ```
yarn solink deploy-program yarn solink deploy-program
deployed aggregator program. program id: 9KXbVqUrMgtti7Jx4rrV1NqXjQNxWaKgtYCEwJ8AESS5 deployed aggregator program. program id: HFHbe2uckzz9Xh633mbJPYcukzpyJRVcwL87fUrVddiq
``` ```
Create the `btc:usd` feed (that accepts max and min u64 as valid submission values): Create the `btc:usd` feed (that accepts max and min u64 as valid submission values):
@ -54,7 +54,7 @@ yarn solink add-aggregator \
--minSubmissionValue 0 \ --minSubmissionValue 0 \
--maxSubmissionValue 18446744073709551615 --maxSubmissionValue 18446744073709551615
feed initialized, pubkey: AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 feed initialized, pubkey: 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v
``` ```
## Adding an oracle ## Adding an oracle
@ -83,17 +83,36 @@ Next we create a new oracle to the feed we've created previously, and set its ow
``` ```
yarn solink add-oracle \ yarn solink add-oracle \
--index 0 \ --index 0 \
--feedAddress AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 \ --feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v \
--oracleName solink-test \ --oracleName solink-test \
--oracleOwner FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4 --oracleOwner FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4
added oracle. pubkey: 4vH5L2jSNXGfcCx42N4sqPiMzEbp1PaQjQ6XngDBu8zR added oracle. pubkey: 4jWLbd2Vm98RrqunVvaSXZuP1AFbgQSM2hAHMvZSdNCu
``` ```
Start submitting data from a price feed (e.g. coinbase BTC-USDT):
``` ```
yarn solink feed \ yarn solink feed \
--feedAddress AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 \ --feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v \
--oracleAddress 4vH5L2jSNXGfcCx42N4sqPiMzEbp1PaQjQ6XngDBu8zR --oracleAddress 4jWLbd2Vm98RrqunVvaSXZuP1AFbgQSM2hAHMvZSdNCu
```
## Read price
Poll the latest aggregated (median) value from a feed:
```
yarn solink feed-poll \
--feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v
```
## Remove oracle
```
yarn solink remove-oracle \
--index 0 \
--feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v
``` ```
## Test Token ## Test Token

Binary file not shown.

View File

@ -14,5 +14,9 @@
"tokenAccount": { "tokenAccount": {
"pubkey": "A4xJLiJrtVigYjJ2NRUrcFza7eB7Y8XeGZh5KmkoxDs1", "pubkey": "A4xJLiJrtVigYjJ2NRUrcFza7eB7Y8XeGZh5KmkoxDs1",
"secret": "19c19ac59d379f3d669b6b5957bbf99060b2b15dd4d266b3f76e164f665c512986bc739598f0e5f0252b3e2fbd0c54a9a08b295a4c5c2901b91c6e3d1e8ace5c" "secret": "19c19ac59d379f3d669b6b5957bbf99060b2b15dd4d266b3f76e164f665c512986bc739598f0e5f0252b3e2fbd0c54a9a08b295a4c5c2901b91c6e3d1e8ace5c"
},
"oracle[0]": {
"pubkey": "4jWLbd2Vm98RrqunVvaSXZuP1AFbgQSM2hAHMvZSdNCu",
"secret": "83369edeffc2f5b870da6595cd7a916723566a1307e722f29e7db6bc8fd01b643776a09078c98230dd23a3aeab3e870e7fb6651ddae3cbf772afa4eba4735e16"
} }
} }

View File

@ -16,11 +16,12 @@
"buffer-layout": "^1.2.0", "buffer-layout": "^1.2.0",
"commander": "^6.2.0", "commander": "^6.2.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"solray": "git+https://github.com/defactojob/solray.git" "solray": "git+https://github.com/defactojob/solray.git",
"ws": "^7.4.1"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/recommended": "^1.0.1", "@tsconfig/recommended": "^1.0.1",
"@types/node": "^14.14.10", "@types/node": "^14.14.12",
"ts-node": "^9.1.1", "ts-node": "^9.1.1",
"typescript": "^4.1.2" "typescript": "^4.1.2"
} }

View File

@ -73,10 +73,8 @@ interface RemoveOracleParams {
// oracle index // oracle index
index: number; index: number;
aggregator: PublicKey; aggregator: PublicKey;
// The oracle key
oracle: PublicKey;
// To prove you are the aggregator owner // To prove you are the aggregator owner
authority: Account; authority?: Account;
} }
interface RemoveOracleInstructionParams extends RemoveOracleParams { interface RemoveOracleInstructionParams extends RemoveOracleParams {
@ -172,7 +170,7 @@ export default class FluxAggregator extends BaseProgram {
]); ]);
} }
public async addOracle(params: AddOracleParams): Promise<PublicKey> { public async addOracle(params: AddOracleParams): Promise<Account> {
const account = new Account(); const account = new Account();
await this.sendTx([ await this.sendTx([
@ -187,7 +185,7 @@ export default class FluxAggregator extends BaseProgram {
}) })
], [this.account, account, params.aggregatorOwner]); ], [this.account, account, params.aggregatorOwner]);
return account.publicKey; return account;
} }
public async oracleInfo(pubkey: PublicKey) { public async oracleInfo(pubkey: PublicKey) {
@ -227,14 +225,14 @@ export default class FluxAggregator extends BaseProgram {
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.account, params.authority || this.wallet.account]);
} }
private removeOracleInstruction(params: RemoveOracleInstructionParams): TransactionInstruction { private removeOracleInstruction(params: RemoveOracleInstructionParams): TransactionInstruction {
const { const {
index, index,
authority, authority,
aggregator,
} = params; } = params;
const layout = BufferLayout.struct([ const layout = BufferLayout.struct([
@ -246,7 +244,9 @@ export default class FluxAggregator extends BaseProgram {
instruction: 2, // remove oracle instruction instruction: 2, // remove oracle instruction
index, index,
}, [ }, [
{ write: authority }, //
{ write: aggregator },
authority || this.wallet.account,
]); ]);
} }

View File

@ -16,6 +16,7 @@ import {
decodeAggregatorInfo, decodeAggregatorInfo,
walletFromEnv, walletFromEnv,
openDeployer, openDeployer,
sleep,
} from "./utils" } from "./utils"
import * as feed from "./feed" import * as feed from "./feed"
@ -48,7 +49,15 @@ class AppContext {
constructor(public deployer: Deployer, public wallet: Wallet) { } constructor(public deployer: Deployer, public wallet: Wallet) { }
get aggregatorProgram() { get aggregatorProgramID() {
return this.aggregatorProgramAccount.publicKey
}
get aggregator() {
return new FluxAggregator(this.wallet, this.aggregatorProgramID)
}
get aggregatorProgramAccount() {
const program = this.deployer.account(AppContext.AGGREGATOR_PROGRAM) const program = this.deployer.account(AppContext.AGGREGATOR_PROGRAM)
if (program == null) { if (program == null) {
@ -129,7 +138,7 @@ cli
.option("--minSubmissionValue <number>", "minSubmissionValue", "0") .option("--minSubmissionValue <number>", "minSubmissionValue", "0")
.option("--maxSubmissionValue <number>", "maxSubmissionValue", "18446744073709551615") .option("--maxSubmissionValue <number>", "maxSubmissionValue", "18446744073709551615")
.action(async (opts) => { .action(async (opts) => {
const { deployer, wallet, aggregatorProgram } = await AppContext.forAdmin() const { deployer, wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forAdmin()
const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue } = opts const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue } = opts
@ -180,25 +189,41 @@ cli
.option("--oracleName <string>", "oracle name") .option("--oracleName <string>", "oracle name")
.option("--oracleOwner <string>", "oracle owner address") .option("--oracleOwner <string>", "oracle owner address")
.action(async (opts) => { .action(async (opts) => {
const { wallet, aggregatorProgram } = await AppContext.forAdmin() const { wallet, aggregator, deployer } = await AppContext.forAdmin()
const { index, oracleName, oracleOwner, feedAddress } = opts const { index, oracleName, oracleOwner, feedAddress } = opts
if (!index || index < 0 || index > 21) { if (!index || index < 0 || index > 21) {
error("invalid index (0-20)") error("invalid index. requires (0-20)")
} }
const program = new FluxAggregator(wallet, aggregatorProgram.publicKey)
log("add oracle...") log("add oracle...")
const oracle = await program.addOracle({ const oracle = await deployer.ensure(`oracle[${index}]`, async () => {
return aggregator.addOracle({
index, index,
owner: new PublicKey(oracleOwner), owner: new PublicKey(oracleOwner),
description: oracleName.substr(0, 32).padEnd(32), description: oracleName.substr(0, 32).padEnd(32),
aggregator: new PublicKey(feedAddress), aggregator: new PublicKey(feedAddress),
aggregatorOwner: wallet.account, aggregatorOwner: wallet.account,
}) })
})
log(`added oracle. pubkey: ${color(oracle.toBase58(), "blue")}`) log(`added oracle. pubkey: ${color(oracle.publicKey.toBase58(), "blue")}`)
})
cli
.command("remove-oracle")
.option("--index <number>", "remove oracle from index (0-20)")
.option("--feedAddress <string>", "feed to remove oracle from")
.action(async (opts) => {
const { index, feedAddress } = opts
const { aggregator } = await AppContext.forAdmin()
await aggregator.removeOracle({
aggregator: new PublicKey(feedAddress),
index,
})
}) })
// cli // cli
@ -225,43 +250,20 @@ cli
// log(deployed.oracles) // log(deployed.oracles)
// }) // })
// cli cli
// .command("aggregatorInfo") .command("feed-poll")
// .description("show aggregatorInfo") .description("poll current feed value")
// .action(async () => { .option("--feedAddress <string>", "feed address to submit values to")
// // show current network .action(async (opts) => {
// showNetwork() const { feedAddress } = opts
// if (!fs.existsSync(deployedPath)) { while (true) {
// error("program haven't deployed yet") const feedInfo = await conn.getAccountInfo(new PublicKey(feedAddress))
// } log(decodeAggregatorInfo(feedInfo))
// const deployed = JSON.parse(fs.readFileSync(deployedPath).toString()) await sleep(1000)
}
// if (deployed.network != network) { })
// error("deployed network not match, please try `npm run clean:deployed`, and deploy again")
// }
// if (!deployed.programId) {
// error("program haven't deployed yet")
// }
// const inputs = await inquirer
// .prompt([
// {
// message: "Choose an aggregator", type: "list", name: "aggregator", choices: () => {
// return deployed.pairs.map(p => ({ name: p.pairName.trim() + ` [${p.aggregator}]`, value: p.aggregator }))
// }
// },
// ])
// const { aggregator } = inputs
// const conn = await connectTo(network)
// const accountInfo = await conn.getAccountInfo(new PublicKey(aggregator))
// log(decodeAggregatorInfo(accountInfo))
// })
cli cli
.command("feed") .command("feed")
@ -270,7 +272,7 @@ cli
.option("--oracleAddress <string>", "feed address to submit values to") .option("--oracleAddress <string>", "feed address to submit values to")
.action(async (opts) => { .action(async (opts) => {
const { wallet, aggregatorProgram } = await AppContext.forOracle() const { wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forOracle()
const { feedAddress, oracleAddress } = opts const { feedAddress, oracleAddress } = opts
@ -289,7 +291,7 @@ cli
.description("create test token") .description("create test token")
.option("--amount <number>", "amount of the test token") .option("--amount <number>", "amount of the test token")
.action(async (opts) => { .action(async (opts) => {
const { admin, aggregatorProgram, deployer } = await AdminContext.load() const { wallet, aggregatorProgramAccount: aggregatorProgram, deployer } = await AppContext.forAdmin()
const { amount } = opts const { amount } = opts
@ -297,12 +299,12 @@ cli
error("invalid amount") error("invalid amount")
} }
const spltoken = new SPLToken(admin) const spltoken = new SPLToken(wallet)
log(`create test token...`) log(`create test token...`)
// 1. create token // 1. create token
const token = await spltoken.initializeMint({ const token = await spltoken.initializeMint({
mintAuthority: admin.account.publicKey, mintAuthority: wallet.account.publicKey,
decimals: 8, decimals: 8,
}) })
@ -325,7 +327,7 @@ cli
token: token.publicKey, token: token.publicKey,
to: tokenAccount.publicKey, to: tokenAccount.publicKey,
amount: BigInt(amount), amount: BigInt(amount),
authority: admin.account, authority: wallet.account,
}) })
log({ log({

View File

@ -4,7 +4,7 @@ import { AggregatorLayout, SubmissionLayout, OracleLayout } from "./FluxAggregat
import { solana, Wallet, NetworkName, Deployer } from "solray" import { solana, Wallet, NetworkName, Deployer } from "solray"
export function getSubmissionValue(submissions: []): number { export function getMedian(submissions: number[]): number {
const values = submissions const values = submissions
.filter((s: any) => s.value != 0) .filter((s: any) => s.value != 0)
.map((s: any) => s.value) .map((s: any) => s.value)
@ -31,47 +31,45 @@ export function sleep(ms: number): Promise<void> {
} }
export function decodeAggregatorInfo(accountInfo) { export function decodeAggregatorInfo(accountInfo) {
const data = Buffer.from(accountInfo.data) const data = Buffer.from(accountInfo.data)
const aggregator = AggregatorLayout.decode(data) const aggregator = AggregatorLayout.decode(data)
const minSubmissionValue = aggregator.minSubmissionValue.readBigUInt64LE().toString() const minSubmissionValue = aggregator.minSubmissionValue.readBigUInt64LE()
const maxSubmissionValue = aggregator.maxSubmissionValue.readBigUInt64LE().toString() const maxSubmissionValue = aggregator.maxSubmissionValue.readBigUInt64LE()
const submitInterval = aggregator.submitInterval.readInt32LE() const submitInterval = aggregator.submitInterval.readInt32LE()
const description = aggregator.description.toString() const description = (aggregator.description.toString() as String).trim()
// decode oracles // decode oracles
let submissions: [] = [] let submissions: any[] = []
let oracles: [] = []
let submissionSpace = SubmissionLayout.span let submissionSpace = SubmissionLayout.span
let updateTime = '0' let latestUpdateTime = BigInt(0)
for (let i = 0; i < aggregator.submissions.length / submissionSpace; i++) { for (let i = 0; i < aggregator.submissions.length / submissionSpace; i++) {
let submission = SubmissionLayout.decode( let submission = SubmissionLayout.decode(
aggregator.submissions.slice(i*submissionSpace, (i+1)*submissionSpace) aggregator.submissions.slice(i*submissionSpace, (i+1)*submissionSpace)
) )
submission.oracle = new PublicKey(submission.oracle) submission.oracle = new PublicKey(submission.oracle)
submission.time = submission.time.readBigInt64LE()
submission.value = submission.value.readBigInt64LE()
submission.time = submission.time.readBigInt64LE().toString()
submission.value = submission.value.readBigInt64LE().toString()*1
if (!submission.oracle.equals(new PublicKey(0))) { if (!submission.oracle.equals(new PublicKey(0))) {
submissions.push(submission as never) submissions.push(submission)
oracles.push(submission.oracle.toBase58() as never)
} }
if (submission.time > updateTime) {
updateTime = submission.time if (submission.time > latestUpdateTime) {
latestUpdateTime = submission.time
} }
} }
return { return {
minSubmissionValue: minSubmissionValue, minSubmissionValue: minSubmissionValue,
maxSubmissionValue: maxSubmissionValue, maxSubmissionValue: maxSubmissionValue,
submissionValue: getSubmissionValue(submissions), submissionValue: getMedian(submissions),
submitInterval, submitInterval,
description, description,
oracles, oracles: submissions.map(s => s.oracle.toString()),
updateTime, latestUpdateTime: new Date(Number(latestUpdateTime)*1000),
} }
} }

View File

@ -3,7 +3,7 @@
/* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ "incremental": true, /* Enable incremental compilation */
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */ // "lib": [], /* Specify library files to be included in the compilation. */
@ -67,8 +67,5 @@
/* Advanced Options */ /* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */ "skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}, }
"include": [
"./src/**/*"
]
} }