From c4264feab4a0f00528159133771635ecff938f71 Mon Sep 17 00:00:00 2001 From: Boid Date: Mon, 12 Feb 2024 21:14:32 +0000 Subject: [PATCH] new payoutround action --- src/assembly/.eslintrc | 9 +- src/assembly/actions/1-global.ts | 19 +- src/assembly/actions/2-oracle.ts | 10 +- src/assembly/actions/3-pwrreport.ts | 55 +++- src/assembly/actions/6-ostats.ts | 139 ++++---- src/assembly/actions/8-slash.ts | 32 +- src/assembly/boid.contract.ts | 13 +- src/assembly/functions.ts | 23 ++ src/assembly/tables/config.ts | 36 ++- src/external/boid/boid.contract.abi | 464 ++++++++++++++------------- src/external/boid/boid.contract.wasm | Bin 104022 -> 107102 bytes src/tsconfig.json | 11 +- tests/defaultConfig.js | 112 +++++++ tests/deposit.spec.js | 6 +- tests/lib/config.js | 39 +++ tests/oracle.spec.js | 35 +- tests/ostats.spec copy.js | 148 +++++++++ tests/ostats.spec.js | 192 ++++------- tests/package.json | 3 +- tests/pwrreport.spec.js | 22 +- tests/scenarios.spec.js | 19 ++ tests/testRoundPayout.js | 49 +++ tests/util.js | 56 ++-- 23 files changed, 962 insertions(+), 530 deletions(-) create mode 100644 src/assembly/functions.ts create mode 100644 tests/defaultConfig.js create mode 100644 tests/lib/config.js create mode 100644 tests/ostats.spec copy.js create mode 100644 tests/scenarios.spec.js create mode 100644 tests/testRoundPayout.js diff --git a/src/assembly/.eslintrc b/src/assembly/.eslintrc index fc14b83..3594589 100644 --- a/src/assembly/.eslintrc +++ b/src/assembly/.eslintrc @@ -15,6 +15,13 @@ "@typescript-eslint" ], "rules": { + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + "allowExpressions": true, + "allowTypedFunctionExpressions": true + } + ], "spaced-comment": "off", "key-spacing": [ "error", @@ -37,7 +44,6 @@ "after": false } ], - "@typescript-eslint/explicit-function-return-type": "error", "space-before-function-paren": [ "error", "never" @@ -57,5 +63,4 @@ "./external/*", "./util/*" ] - } diff --git a/src/assembly/actions/1-global.ts b/src/assembly/actions/1-global.ts index 9a5bde6..33ef93e 100644 --- a/src/assembly/actions/1-global.ts +++ b/src/assembly/actions/1-global.ts @@ -58,7 +58,7 @@ export class GlobalActions extends Contract { /** Determine the minimum weight required for consensus */ minWeightThreshold(config:Config = this.getConfig(), global:Global = this.globalT.get()):u16 { - return u16(Math.min(u32(Math.max(global.expected_active_weight * config.consensus.min_pct, config.consensus.min_weight)), u16.MAX_VALUE)) + return u16(Math.min(u32(Math.max(global.expected_active_weight * config.consensus.min_weight_pct, config.consensus.min_weight)), u16.MAX_VALUE)) } /** Adds active oracle to global row, does not update table */ @@ -112,18 +112,19 @@ export class GlobalActions extends Contract { configSet(config:Config):void { requireAuth(this.receiver) check(config.slashLow.slash_quantity_collateral_pct >= 0, "slash_quantity_collateral_pct must be greater than or equal zero") - check(config.slashLow.slash_quantity_collateral_pct <= f32(1), "slash_quantity_collateral_pct must be less or equal to 100%") + check(config.slashLow.slash_quantity_collateral_pct <= f32(100), "slash_quantity_collateral_pct must be less or equal to 100%") check(config.slashMed.slash_quantity_collateral_pct >= 0, "slash_quantity_collateral_pct must be greater than or equal zero") - check(config.slashMed.slash_quantity_collateral_pct <= f32(1), "slash_quantity_collateral_pct must be less or equal to 100%") + check(config.slashMed.slash_quantity_collateral_pct <= f32(100), "slash_quantity_collateral_pct must be less or equal to 100%") check(config.slashHigh.slash_quantity_collateral_pct >= 0, "slash_quantity_collateral_pct must be greater than or equal zero") - check(config.slashHigh.slash_quantity_collateral_pct <= f32(1), "slash_quantity_collateral_pct must be less or equal to 100%") + check(config.slashHigh.slash_quantity_collateral_pct <= f32(100), "slash_quantity_collateral_pct must be less or equal to 100%") check(config.reports_accumulate_weight_round_pct >= 0, "reports_accumulate_weight_round_pct must be higher or equal zero") check(config.reports_accumulate_weight_round_pct <= f32(1), "reports_accumulate_weight_round_pct must be less or equal to 100%") - check(config.payment.collateral_pct_pay_per_round >= 0, "collateral_pct_pay_per_round must be higher or equal zero") - check(config.consensus.min_pct >= 0, "min_consensus_pct must be higher or equal zero") - check(config.consensus.min_pct <= f32(1), "min_consensus_pct must be less or equal to 100%") - check(config.merge_deviation_pct >= 0, "merge_deviation_pct must be higher or equal zero") - check(config.merge_deviation_pct <= f32(1), "merge_deviation_pct must be less or equal to 100%") + check(config.payment.collateral_pct_pay_per_round_mult >= 0, "collateral_pct_pay_per_round_mult must be higher or equal zero") + check(config.payment.collateral_pct_pay_per_round_mult <= 1, "collateral_pct_pay_per_round_mult must be less") + check(config.consensus.min_weight_pct >= 0, "min_consensus_pct must be higher or equal zero") + check(config.consensus.min_weight_pct <= f32(1), "min_consensus_pct must be less or equal to 100%") + check(config.consensus.merge_deviation_pct >= 0, "merge_deviation_pct must be higher or equal zero") + check(config.consensus.merge_deviation_pct <= f32(1), "merge_deviation_pct must be less or equal to 100%") check(config.min_pay_report_share_threshold >= 0, "min_pay_report_share_threshold must be higher or equal zero") check(config.min_pay_report_share_threshold <= f32(1), "min_pay_report_share_threshold must be less or equal to 100%") this.configT.set(config, this.receiver) diff --git a/src/assembly/actions/2-oracle.ts b/src/assembly/actions/2-oracle.ts index 760c32d..51ed70a 100644 --- a/src/assembly/actions/2-oracle.ts +++ b/src/assembly/actions/2-oracle.ts @@ -1,4 +1,4 @@ -import { Action, ActionData, check, EMPTY_NAME, hasAuth, isAccount, Name, requireAuth } from "proton-tsc" +import { Action, ActionData, check, EMPTY_NAME, hasAuth, isAccount, Name, requireAuth, SAME_PAYER } from "proton-tsc" import { Oracle, OracleCollateral, OracleFunds } from "../tables/oracles" import { GlobalActions } from "./1-global" @@ -64,6 +64,14 @@ export class OracleActions extends GlobalActions { action.send() } + @action("setweight") + setWeight(oracle:Name, weight:u8):void { + const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle doesn't exist") + check(oracleRow.standby, "oracle must be in standby to set weight") + oracleRow.weight = weight + this.oraclesT.update(oracleRow, SAME_PAYER) + } + /** * Action called by oracles or the contract to set an oracle in/out of standby mode. * diff --git a/src/assembly/actions/3-pwrreport.ts b/src/assembly/actions/3-pwrreport.ts index b816da7..c4c19a8 100644 --- a/src/assembly/actions/3-pwrreport.ts +++ b/src/assembly/actions/3-pwrreport.ts @@ -45,10 +45,10 @@ export class PwrReportActions extends OracleActions { let oRoundCommit = this.roundCommitT(oracle) let commitExists = oRoundCommit.getBySecondaryU128(RoundCommit.getByRoundProtocolBoidId(boid_id_scope, report.protocol_id, report.round), 1) check(commitExists == null, "oracle already commited report for this user and round") - + const activeRound = this.currentRound() - 1 // ensure the report is for a round that is valid check(report.round < this.currentRound(), "report round must target a past round") - check(report.round == this.currentRound() - 1, "round is too far in the past") + check(report.round == activeRound, "report round is too far in the past, must report for round: " + activeRound.toString()) // ensure the oracle can make reports const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle not registered") @@ -110,14 +110,15 @@ export class PwrReportActions extends OracleActions { // if report was sent, we need to update the stats for all oracles that participated if (reportSent) { - if (!existing) return - for (let i = 0; i < existing.approvals.length; i++) { - const oracleName = existing.approvals[i] - let row:Oracle|null = null - if (oracleName == oracle) row = oracleRow - else row = this.oraclesT.get(oracleName.value) - if (!row) continue - else this.updateOracleStats(row, reportSent, false, report.round) + if (existing) { + for (let i = 0; i < existing.approvals.length; i++) { + const oracleName = existing.approvals[i] + let row:Oracle|null = null + if (oracleName == oracle) row = oracleRow + else row = this.oraclesT.get(oracleName.value) + if (!row) continue + else this.updateOracleStats(row, reportSent, false, report.round) + } } } else { // otherwise we just need to update our own oracle stats @@ -162,7 +163,37 @@ export class PwrReportActions extends OracleActions { * @param {u64[]} pwrreport_ids a vector of reports that could be combined */ @action("finishreport") - finishReport(boid_id_scope:Name, pwrreport_ids:u64[]):void { + finishReport(boid_id_scope:Name, pwrreport_id:u64):void { + const config = this.getConfig() + const pwrReport = this.pwrReportsT(boid_id_scope).requireGet(pwrreport_id, "invalid id provided") + check(!pwrReport.reported, "can't merge reports already reported") + check(!pwrReport.merged, "can't merge reports already merged") + check(this.shouldFinalizeReports(u16(pwrReport.report.round), config), "can't finalize/merge reports this early in a round") + const global = this.globalT.get() + const minThreshold = this.minWeightThreshold(config, global) + check(pwrReport.approval_weight >= minThreshold, "aggregate approval_weight isn't high enough. Minimum:" + minThreshold.toString() + " report has:" + pwrReport.approval_weight.toString()) + this.sendReport(boid_id_scope, pwrReport.report) + pwrReport.reported = true + this.pwrReportsT(boid_id_scope).update(pwrReport, SAME_PAYER) + + const oStatsT = this.oracleStatsT(pwrReport.proposer) + for (let i = 0; i < pwrReport.approvals.length; i++) { + const oracleName = pwrReport.approvals[i] + let row:Oracle|null = null + row = this.oraclesT.get(oracleName.value) + if (!row) continue + else this.updateOracleStats(row, true, false, pwrReport.report.round) + } + global.reports.reported++ + global.reports.unreported_and_unmerged-- + // this saves the modified global if stats needs updating, otherwise we save global directly + const saved = this.updateStats(global, config) + if (!saved) this.globalT.set(global, SAME_PAYER) + } + + @action("mergereports") + mergeReports(boid_id_scope:Name, pwrreport_ids:u64[]):void { + check(pwrreport_ids.length > 1, "must provide at least two reports to merge") const config = this.getConfig() const targetReports:PwrReportRow[] = [] let targetProtocol:i16 = -1 @@ -199,7 +230,7 @@ export class PwrReportActions extends OracleActions { else medianUnits = u32((targetReports[half - 1].report.units + targetReports[half].report.units) / 2) //find safe min/max values - const safeAmount = Math.max(f32(medianUnits) * config.merge_deviation_pct, 1) + const safeAmount = Math.max(f32(medianUnits) * config.consensus.merge_deviation_pct, 1) const safeMax = u32(medianUnits + safeAmount) check(safeMax >= medianUnits, "safeMax max reached") const safeMin = u32(Math.max(f32(medianUnits) - safeAmount, 1)) diff --git a/src/assembly/actions/6-ostats.ts b/src/assembly/actions/6-ostats.ts index 1abc175..f31a449 100644 --- a/src/assembly/actions/6-ostats.ts +++ b/src/assembly/actions/6-ostats.ts @@ -1,4 +1,4 @@ -import { Action, ActionData, EMPTY_NAME, Name, check, print, requireAuth } from "proton-tsc" +import { Action, ActionData, EMPTY_NAME, Name, SAME_PAYER, check, print, requireAuth } from "proton-tsc" import { DepositActions } from "./5-deposit" export class OStatsActions extends DepositActions { @@ -8,74 +8,74 @@ export class OStatsActions extends DepositActions { * @param {Name} oracle * @param {u16} round */ - @action("handleostat") - handleOStat(oracle:Name, round:u16):void { - const config = this.getConfig() - check(round < this.currentRound() - 2, "can't process this round yet, not yet finalized") + // @action("handleostat") + // handleOStat(oracle:Name, round:u16):void { + // const config = this.getConfig() + // check(round < this.currentRound() - 2, "can't process this round yet, not yet finalized") - // get config, global and oracle stats - const oStatsT = this.oracleStatsT(oracle) - const oRoundData = oStatsT.requireGet(round, "oStats round doesn't exist") - check(!oRoundData.processed, "round stats is already processed") - const globalData = this.statsT.requireGet(round + 1, "round stats not yet available") - // TODO need to handle situation where no stats are available because stats generation was skipped + // // get config, global and oracle stats + // const oStatsT = this.oracleStatsT(oracle) + // const oRoundData = oStatsT.requireGet(round, "oStats round doesn't exist") + // check(!oRoundData.processed, "round stats is already processed") + // const globalData = this.statsT.requireGet(round + 1, "round stats not yet available") + // // TODO need to handle situation where no stats are available because stats generation was skipped - // find the oracle pct of the round - let unReportedShare:f32 = f32(oRoundData.reports.unreported_unmerged) / f32(globalData.unreported_unmerged_since_previous) - if (isNaN(unReportedShare)) unReportedShare = 0 - print("\n unreportedShare: " + unReportedShare.toString()) + // // find the oracle pct of the round + // let unReportedShare:f32 = f32(oRoundData.reports.unreported_unmerged) / f32(globalData.unreported_unmerged_since_previous) + // if (isNaN(unReportedShare)) unReportedShare = 0 + // print("\n unreportedShare: " + unReportedShare.toString()) - // if unreported is above the threshold, slash funds from the oracle - const validProposed = u32(Math.max(i32(oRoundData.reports.proposed) - i32(oRoundData.reports.unreported_unmerged), 0)) - const globalValidProposed = globalData.valid_proposed_since_previous + // // if unreported is above the threshold, slash funds from the oracle + // const validProposed = u32(Math.max(i32(oRoundData.reports.proposed) - i32(oRoundData.reports.unreported_unmerged), 0)) + // const globalValidProposed = globalData.valid_proposed_since_previous - print("\n reportedSincePrevious " + globalData.reported_since_previous.toString()) - print("\n proposedSincePrevious " + globalData.proposed_since_previous.toString()) - print("\n previousProposed: " + globalData.starting_global.reports.proposed.toString()) - print("\n previousReported: " + globalData.starting_global.reports.reported.toString()) + // print("\n reportedSincePrevious " + globalData.reported_since_previous.toString()) + // print("\n proposedSincePrevious " + globalData.proposed_since_previous.toString()) + // print("\n previousProposed: " + globalData.starting_global.reports.proposed.toString()) + // print("\n previousReported: " + globalData.starting_global.reports.reported.toString()) - // calculate the oracle pct of the round reported/proposed - print("\n oracle reported/merged: " + oRoundData.reports.reported_or_merged.toString()) - print("\n validProposed: " + validProposed.toString()) - print("\n globalValidProposed: " + globalValidProposed.toString()) - print("\n active Oracles: " + globalData.starting_global.active_oracles.length.toString()) - let reportedShare:f32 = f32(oRoundData.reports.reported_or_merged) / f32(globalData.reported_since_previous) / f32(globalData.starting_global.active_oracles.length) - let proposedShare:f32 = f32(validProposed) / f32(globalValidProposed) - if (!isFinite(proposedShare)) proposedShare = 1 - else if (isNaN(proposedShare)) proposedShare = 0 - if (!isFinite(reportedShare)) reportedShare = 1 - else if (isNaN(reportedShare)) reportedShare = 0 - print("\n reportedShare: " + reportedShare.toString()) - print("\n proposedShare: " + proposedShare.toString()) + // // calculate the oracle pct of the round reported/proposed + // print("\n oracle reported/merged: " + oRoundData.reports.reported_or_merged.toString()) + // print("\n validProposed: " + validProposed.toString()) + // print("\n globalValidProposed: " + globalValidProposed.toString()) + // print("\n active Oracles: " + globalData.starting_global.active_oracles.length.toString()) + // let reportedShare:f32 = f32(oRoundData.reports.reported_or_merged) / f32(globalData.reported_since_previous) / f32(globalData.starting_global.active_oracles.length) + // let proposedShare:f32 = f32(validProposed) / f32(globalValidProposed) + // if (!isFinite(proposedShare)) proposedShare = 0 + // else if (isNaN(proposedShare)) proposedShare = 0 + // if (!isFinite(reportedShare)) reportedShare = 0 + // else if (isNaN(reportedShare)) reportedShare = 0 + // print("\n reportedShare: " + reportedShare.toString()) + // print("\n proposedShare: " + proposedShare.toString()) - // oracle pay calculated based on their pct share of the round - const oracleBonusReportedPayout = f32(config.payment.round_bonus_pay_reports) * reportedShare - const oracleBonusProposedPayout = f32(config.payment.round_bonus_pay_proposed) * proposedShare - print("\n oracleBonusReportedPayout: " + oracleBonusReportedPayout.toString()) - print("\n oracleBonusProposedPayout: " + oracleBonusProposedPayout.toString()) + // // oracle pay calculated based on their pct share of the round + // const oracleBonusReportedPayout = f32(config.payment.round_bonus_pay_reports) * reportedShare + // const oracleBonusProposedPayout = f32(config.payment.round_bonus_pay_proposed) * proposedShare + // print("\n oracleBonusReportedPayout: " + oracleBonusReportedPayout.toString()) + // print("\n oracleBonusProposedPayout: " + oracleBonusProposedPayout.toString()) - let minGlobalActiveWeight:bool = globalData.starting_global.active_weight >= this.minWeightThreshold(config, globalData.starting_global) + // let minGlobalActiveWeight:bool = globalData.starting_global.active_weight >= this.minWeightThreshold(config, globalData.starting_global) - // calculate the final pay - const oracleRow = this.oraclesT.requireGet(oracle.value, "can't find oracle in oracles table") - let basePay:u32 = 0 + // // calculate the final pay + // const oracleRow = this.oraclesT.requireGet(oracle.value, "can't find oracle in oracles table") + // let basePay:u32 = 0 - if (reportedShare >= config.min_pay_report_share_threshold || proposedShare >= config.min_pay_report_share_threshold) basePay = u32(f32(oracleRow.trueCollateral) * config.payment.collateral_pct_pay_per_round) - const bonusPay:u32 = u32(oracleBonusProposedPayout + oracleBonusReportedPayout) + // if (reportedShare >= config.min_pay_report_share_threshold || proposedShare >= config.min_pay_report_share_threshold) basePay = u32(f32(oracleRow.trueCollateral) * config.payment.collateral_pct_pay_per_round) + // const bonusPay:u32 = u32(oracleBonusProposedPayout + oracleBonusReportedPayout) - // return - // call the payment action - if (basePay > 0 || bonusPay > 0) this.sendPayOracle(oracle, basePay, bonusPay) + // // return + // // call the payment action + // if (basePay > 0 || bonusPay > 0) this.sendPayOracle(oracle, basePay, bonusPay) - // don't delete the row yet - // need to keep the row so we don't accidentally slash the oracle for being absent - // safe to delete when the rounds row in stats table is cleaned up - oRoundData.processed = true - oStatsT.update(oRoundData, this.receiver) - } + // // don't delete the row yet + // // need to keep the row so we don't accidentally slash the oracle for being absent + // // safe to delete when the rounds row in stats table is cleaned up + // oRoundData.processed = true + // oStatsT.update(oRoundData, this.receiver) + // } - sendPayOracle(oracle:Name, basePay:u32, bonusPay:u32):void { - const data = new PayOracleParams(oracle, basePay, bonusPay) + sendPayOracle(oracle:Name, basePay:u32, bonusPay:u32, round:u16):void { + const data = new PayOracleParams(oracle, basePay, bonusPay, round) const action = new Action(this.receiver, Name.fromString("payoracle"), [this.codePerm], data.pack()) action.send() } @@ -88,7 +88,7 @@ export class OStatsActions extends DepositActions { * @param {u32} bonusPay */ @action("payoracle") - payOracle(oracle:Name, basePay:u32, bonusPay:u32):void { + payOracle(oracle:Name, basePay:u32, bonusPay:u32, round:u16):void { requireAuth(this.receiver) const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle doesn't exist") const payQuantity = basePay + bonusPay @@ -96,15 +96,34 @@ export class OStatsActions extends DepositActions { this.oraclesT.update(oracleRow, this.receiver) const global = this.globalT.get() global.rewards_paid += payQuantity - this.sendWholeBoid(Name.fromString("tknmint.boid"), this.receiver, payQuantity, "payoracle") + this.sendWholeBoid(Name.fromString("tknmint.boid"), this.receiver, payQuantity, "payoracle:" + oracle.toString() + " round:" + round.toString()) this.globalT.set(global, this.receiver) } + + @action("payoutround") + payoutRound(oracle:Name, round:u16):void { + const config = this.getConfig() + const global = this.globalT.get() + check(round < this.currentRound() - 2, "can't process this round yet, not yet finalized") + const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle doesn't exist") + // get config, global and oracle stats + const oStatsT = this.oracleStatsT(oracle) + const oRoundData = oStatsT.requireGet(round, "oStats round doesn't exist") + check(!oRoundData.processed, "round stats is already processed") + const basePay = u32(f32(oracleRow.trueCollateral) * config.payment.collateral_pct_pay_per_round_mult) + let bonusPay = f64(config.payment.round_bonus_pay_reports) * Math.pow(oRoundData.reports.reported_or_merged, config.payment.reports_proposed_adjust_pwr) + f64(config.payment.round_bonus_pay_proposed) * Math.pow(oRoundData.reports.proposed, config.payment.reports_proposed_adjust_pwr) + bonusPay = bonusPay / Math.pow(config.payment.num_oracles_adjust_base, global.expected_active_oracles.length) + oRoundData.processed = true + oStatsT.update(oRoundData, SAME_PAYER) + this.sendPayOracle(oracle, basePay, u32(bonusPay), round) + } } @packer export class PayOracleParams extends ActionData { constructor( public oracle:Name = EMPTY_NAME, public basePay:u32 = 0, - public bonusPay:u32 = 0 + public bonusPay:u32 = 0, + public round:u16 = 0 ) { super() } } diff --git a/src/assembly/actions/8-slash.ts b/src/assembly/actions/8-slash.ts index f16824b..e93425b 100644 --- a/src/assembly/actions/8-slash.ts +++ b/src/assembly/actions/8-slash.ts @@ -28,7 +28,7 @@ export class SlashActions extends TableCleanActions { const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle doesn't exist") check(!oracleRow.standby, "can't slash an oracle in standby") const config = this.getConfig() - const pctQuantity = oracleRow.trueCollateral * (config.slashLow.slash_quantity_collateral_pct / 100) + const pctQuantity = u32(f32(oracleRow.trueCollateral) * (config.slashLow.slash_quantity_collateral_pct / 100)) const quantity = pctQuantity + config.slashLow.slash_quantity_static oracleRow.collateral.slashed += quantity check(oracleRow.collateral.slashed >= quantity, "max collateral slashed reached") @@ -36,4 +36,34 @@ export class SlashActions extends TableCleanActions { // save oracle row this.oraclesT.update(oracleRow, this.receiver) } + + @action("slashmed") + slashOracleMed(oracle:Name):void { + requireAuth(this.receiver) + const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle doesn't exist") + check(!oracleRow.standby, "can't slash an oracle in standby") + const config = this.getConfig() + const pctQuantity = u32(f32(oracleRow.trueCollateral) * (config.slashMed.slash_quantity_collateral_pct / 100)) + const quantity = pctQuantity + config.slashMed.slash_quantity_static + oracleRow.collateral.slashed += quantity + check(oracleRow.collateral.slashed >= quantity, "max collateral slashed reached") + const weightBefore = oracleRow.weight + // save oracle row + this.oraclesT.update(oracleRow, this.receiver) + } + + @action("slashhigh") + slashOracleHigh(oracle:Name):void { + requireAuth(this.receiver) + const oracleRow = this.oraclesT.requireGet(oracle.value, "oracle doesn't exist") + check(!oracleRow.standby, "can't slash an oracle in standby") + const config = this.getConfig() + const pctQuantity = u32(f32(oracleRow.trueCollateral) * (config.slashHigh.slash_quantity_collateral_pct / 100)) + const quantity = pctQuantity + config.slashHigh.slash_quantity_static + oracleRow.collateral.slashed += quantity + check(oracleRow.collateral.slashed >= quantity, "max collateral slashed reached") + const weightBefore = oracleRow.weight + // save oracle row + this.oraclesT.update(oracleRow, this.receiver) + } } diff --git a/src/assembly/boid.contract.ts b/src/assembly/boid.contract.ts index a348d1b..76cd744 100644 --- a/src/assembly/boid.contract.ts +++ b/src/assembly/boid.contract.ts @@ -1,12 +1,11 @@ -import { check, Contract } from "proton-tsc" -import { OracleActions } from "./actions/2-oracle" -import { PwrReportActions } from "./actions/3-pwrreport" -import { ProtoActions } from "./actions/4-protocol" -import { DepositActions } from "./actions/5-deposit" -import { OStatsActions } from "./actions/6-ostats" +import { Contract, Singleton } from "proton-tsc" +// import { OracleActions } from "./actions/2-oracle" +// import { PwrReportActions } from "./actions/3-pwrreport" +// import { ProtoActions } from "./actions/4-protocol" +// import { DepositActions } from "./actions/5-deposit" +// import { OStatsActions } from "./actions/6-ostats" import { SlashActions } from "./actions/8-slash" @contract export class BoidPowerContract extends SlashActions { - } diff --git a/src/assembly/functions.ts b/src/assembly/functions.ts new file mode 100644 index 0000000..3df6d31 --- /dev/null +++ b/src/assembly/functions.ts @@ -0,0 +1,23 @@ +import { EMPTY_NAME, Name } from "proton-tsc" +import { PwrReport, PwrReportRow } from "./tables/pwrreports" +import { Global } from "./tables/global" + +@packer +class TryFinalizeReportParams { + report:PwrReportRow = new PwrReportRow() + minWeightThreashold:u16 = 0 + shouldFinalizeReport:boolean = false + sendReport:(boid_id:Name, report:PwrReport) =>void = () => {} + boid_id:Name = EMPTY_NAME + global:Global = new Global() +} +export function tryFinalizeReport(params:TryFinalizeReportParams):boolean { + const { report, minWeightThreashold, shouldFinalizeReport, sendReport, boid_id, global } = params + if (report.approval_weight >= minWeightThreashold && shouldFinalizeReport) { + sendReport(boid_id, report.report) + global.reports.reported++ + global.reports.unreported_and_unmerged-- + report.reported = true + return true + } else return false +} diff --git a/src/assembly/tables/config.ts b/src/assembly/tables/config.ts index 8e1c203..1509de6 100644 --- a/src/assembly/tables/config.ts +++ b/src/assembly/tables/config.ts @@ -6,17 +6,23 @@ export class ConsensusConfig { /**The minimum weight required to confirm a report, this is to help protect from and instance where many oracles may go into standby at once. */ public min_weight:u32 = 0 /** The minimum percentage of all weight required to confirm a report, overridden by min_consensus_weight */ - public min_pct:f32 = 0 + public min_weight_pct:f32 = 0 + /** for calculating median acceptable deviation */ + public merge_deviation_pct:f32 = 0 } @packer export class PaymentConfig { /** percentage of collateral is paid each round as long as the oracle was active that round */ - public collateral_pct_pay_per_round:f32 = 0 - /** total paid each round based on reports made by each oracle, split proportionally */ + public collateral_pct_pay_per_round_mult:f32 = 0 + /** total paid each round based on reports made */ public round_bonus_pay_reports:u32 = 0 - /** total paid each round based on proposals made by each oracle, split proportionally */ + /** total paid each round based on proposals made */ public round_bonus_pay_proposed:u32 = 0 + /** reports and proposed are raised to this power, intended to reduce the payout per account as the userbase grows to help curb inflation, should be between .9 and .4 */ + public reports_proposed_adjust_pwr:f32 = 0 + /** causes the payout to decrease as more oracles join, should be between 1 - 1.1 */ + public num_oracles_adjust_base:f32 = 0 } @packer @@ -47,22 +53,20 @@ export class Config extends Table { constructor( /** pauses most contract actions, for use during contract upgrades */ public paused:boolean = true, - public consensus = new ConsensusConfig(), - public payment = new PaymentConfig(), - public slashLow = new SlashConfig(), - public slashMed = new SlashConfig(), - public slashHigh = new SlashConfig(), - public waits = new WaitConfig(), - public collateral = new CollateralConfig(), + public consensus:ConsensusConfig = new ConsensusConfig(), + public payment:PaymentConfig = new PaymentConfig(), + public slashLow:SlashConfig = new SlashConfig(), + public slashMed:SlashConfig = new SlashConfig(), + public slashHigh:SlashConfig = new SlashConfig(), + public waits:WaitConfig = new WaitConfig(), + public collateral:CollateralConfig = new CollateralConfig(), /** how many stats rows (counted after the finalization round) should always be kept when running the stats cleanup action */ public keep_finalized_stats_rows:u32 = 0, - /** for calculating median acceptable deviation */ - public merge_deviation_pct:f32 = 0, - /** when an oracle is toggling standby, they must wait this many rounds before they can do it again */ + // /** when an oracle is toggling standby, they must wait this many rounds before they can do it again */ public standby_toggle_interval_rounds:u16 = 0, - /** min report share threshold to receive pay */ + // /** min report share threshold to receive pay */ public min_pay_report_share_threshold:f32 = 0, - /** percentage of the round (starting from beginning of the round) when reports accumulate weight but don't finalize, the purpose is to give all oracles a chance to make a report */ + // /** percentage of the round (starting from beginning of the round) when reports accumulate weight but don't finalize, the purpose is to give all oracles a chance to make a report */ public reports_accumulate_weight_round_pct:f32 = 0 ) { super() diff --git a/src/external/boid/boid.contract.abi b/src/external/boid/boid.contract.abi index 1ec84a2..1aa4fc4 100644 --- a/src/external/boid/boid.contract.abi +++ b/src/external/boid/boid.contract.abi @@ -61,6 +61,28 @@ } ] }, + { + "name": "AccountBooster", + "base": "", + "fields": [ + { + "name": "pwr_multiplier", + "type": "uint8" + }, + { + "name": "pwr_add_per_round", + "type": "uint16" + }, + { + "name": "expires_round", + "type": "uint16" + }, + { + "name": "aggregate_pwr_remaining", + "type": "uint32" + } + ] + }, { "name": "AccountCreate", "base": "", @@ -88,34 +110,20 @@ "type": "uint16" }, { - "name": "rating", + "name": "last_added_round", "type": "uint16" }, { - "name": "mods", - "type": "AccountPowerMod[]" - } - ] - }, - { - "name": "AccountPowerMod", - "base": "", - "fields": [ - { - "name": "pwr_multiplier", - "type": "uint8" - }, - { - "name": "pwr_add_per_round", + "name": "rating", "type": "uint16" }, { - "name": "expires_round", - "type": "uint16" + "name": "history", + "type": "uint16[]" }, { - "name": "aggregate_pwr_remaining", - "type": "uint32" + "name": "boosters", + "type": "AccountBooster[]" } ] }, @@ -143,7 +151,7 @@ "fields": [ { "name": "team_id", - "type": "uint16" + "type": "uint8" }, { "name": "last_edit_round", @@ -159,6 +167,20 @@ } ] }, + { + "name": "AcctMeta", + "base": "", + "fields": [ + { + "name": "boid_id", + "type": "name" + }, + { + "name": "meta", + "type": "bytes" + } + ] + }, { "name": "Action", "base": "", @@ -219,6 +241,32 @@ } ] }, + { + "name": "Booster", + "base": "", + "fields": [ + { + "name": "booster_id", + "type": "uint8" + }, + { + "name": "pwr_multiplier", + "type": "uint8" + }, + { + "name": "pwr_add_per_round", + "type": "uint16" + }, + { + "name": "expire_after_elapsed_rounds", + "type": "uint16" + }, + { + "name": "aggregate_pwr_capacity", + "type": "uint32" + } + ] + }, { "name": "Config", "base": "", @@ -255,10 +303,6 @@ "name": "nft", "type": "ConfigNft" }, - { - "name": "auto", - "type": "ConfigAutoAdjust" - }, { "name": "paused", "type": "bool" @@ -298,11 +342,7 @@ "type": "uint8" }, { - "name": "max_sponsors", - "type": "uint8" - }, - { - "name": "max_pwrmods", + "name": "max_boosters", "type": "uint8" }, { @@ -349,32 +389,6 @@ } ] }, - { - "name": "ConfigAutoAdjust", - "base": "", - "fields": [ - { - "name": "target_inflation_per_round", - "type": "uint32" - }, - { - "name": "power_mult_max_adjust", - "type": "float32" - }, - { - "name": "powered_stake_mult_max_adjust", - "type": "float32" - }, - { - "name": "adjustment_interval_rounds", - "type": "uint16" - }, - { - "name": "max_check_rounds", - "type": "uint16" - } - ] - }, { "name": "ConfigMint", "base": "", @@ -407,14 +421,6 @@ "name": "ConfigPower", "base": "", "fields": [ - { - "name": "round_decay_constant", - "type": "uint16" - }, - { - "name": "round_decay_mult", - "type": "float32" - }, { "name": "sponsor_tax_mult", "type": "float32" @@ -423,10 +429,6 @@ "name": "powered_stake_mult", "type": "float32" }, - { - "name": "powered_stake_pwr", - "type": "float32" - }, { "name": "claim_maximum_elapsed_rounds", "type": "uint16" @@ -436,8 +438,8 @@ "type": "uint16" }, { - "name": "dev_fund_tax_mult", - "type": "float32" + "name": "history_slots_length", + "type": "uint8" } ] }, @@ -490,8 +492,8 @@ "base": "", "fields": [ { - "name": "rounds_start", - "type": "time_point" + "name": "rounds_start_sec_since_epoch", + "type": "uint32" }, { "name": "round_length_sec", @@ -521,22 +523,10 @@ "name": "chain_name", "type": "name" }, - { - "name": "total_accounts", - "type": "uint64" - }, { "name": "total_power", "type": "uint64" }, - { - "name": "total_liquid_balance", - "type": "uint64" - }, - { - "name": "total_stake", - "type": "uint64" - }, { "name": "last_inflation_adjust_round", "type": "uint16" @@ -566,23 +556,27 @@ "base": "", "fields": [ { - "name": "account", + "name": "power_mint", "type": "uint32" }, { - "name": "team", + "name": "powered_stake_mint", "type": "uint32" }, { - "name": "team_owner", + "name": "account_earned", "type": "uint32" }, { - "name": "overstake", + "name": "team_cut", "type": "uint32" }, { - "name": "fundstake", + "name": "team_owner_earned", + "type": "uint32" + }, + { + "name": "overstake_mint", "type": "uint32" }, { @@ -755,7 +749,7 @@ "fields": [ { "name": "team_id", - "type": "uint16[]" + "type": "bytes" }, { "name": "min_power", @@ -796,7 +790,7 @@ "type": "uint16" }, { - "name": "activate_powermod_ids", + "name": "activate_booster_ids", "type": "bytes" } ] @@ -821,49 +815,19 @@ "fields": [ { "name": "before", - "type": "uint16" + "type": "uint32" }, { "name": "after", - "type": "uint16" - }, - { - "name": "from_mods", - "type": "uint16" - }, - { - "name": "decayed", - "type": "uint16" - }, - { - "name": "rounds", - "type": "uint16" - } - ] - }, - { - "name": "PwrMod", - "base": "", - "fields": [ - { - "name": "mod_id", - "type": "uint8" - }, - { - "name": "pwr_multiplier", - "type": "uint8" + "type": "uint32" }, { - "name": "pwr_add_per_round", - "type": "uint16" + "name": "from_boosters", + "type": "uint32" }, { - "name": "expire_after_elapsed_rounds", + "name": "elapsed_rounds", "type": "uint16" - }, - { - "name": "aggregate_pwr_capacity", - "type": "uint32" } ] }, @@ -923,28 +887,6 @@ } ] }, - { - "name": "Stats", - "base": "", - "fields": [ - { - "name": "round", - "type": "uint16" - }, - { - "name": "power_added", - "type": "uint32" - }, - { - "name": "boid_generated", - "type": "uint32" - }, - { - "name": "accounts_created", - "type": "uint32" - } - ] - }, { "name": "Team", "base": "", @@ -981,10 +923,6 @@ "name": "url_safe_name", "type": "string" }, - { - "name": "info_json_ipfs", - "type": "string" - }, { "name": "power", "type": "uint64" @@ -996,6 +934,10 @@ { "name": "last_edit_round", "type": "uint16" + }, + { + "name": "meta", + "type": "bytes" } ] }, @@ -1058,7 +1000,7 @@ "type": "name" }, { - "name": "social_ipfs_json", + "name": "meta", "type": "bytes" } ] @@ -1074,9 +1016,14 @@ ] }, { - "name": "accounts.clr", + "name": "account.rm", "base": "", - "fields": [] + "fields": [ + { + "name": "boid_id", + "type": "name" + } + ] }, { "name": "auth", @@ -1137,6 +1084,44 @@ } ] }, + { + "name": "booster.add", + "base": "", + "fields": [ + { + "name": "boid_id", + "type": "name" + }, + { + "name": "booster_id", + "type": "uint8" + } + ] + }, + { + "name": "booster.new", + "base": "", + "fields": [ + { + "name": "booster", + "type": "Booster" + } + ] + }, + { + "name": "booster.rm", + "base": "", + "fields": [ + { + "name": "boid_id", + "type": "name" + }, + { + "name": "booster_index", + "type": "int32[]" + } + ] + }, { "name": "config.clear", "base": "", @@ -1280,7 +1265,7 @@ "type": "uint16" }, { - "name": "from_mult_mods", + "name": "from_mult_boosters", "type": "uint16" }, { @@ -1301,6 +1286,10 @@ "name": "logpwrclaim", "base": "", "fields": [ + { + "name": "boid_id", + "type": "name" + }, { "name": "power", "type": "PowerClaimLog" @@ -1433,6 +1422,21 @@ } ] }, + { + "name": "offer.clean", + "base": "", + "fields": [] + }, + { + "name": "offer.rm", + "base": "", + "fields": [ + { + "name": "offer_id", + "type": "uint64" + } + ] + }, { "name": "owner.add", "base": "", @@ -1486,40 +1490,22 @@ ] }, { - "name": "pwrmod.add", + "name": "sponsor.rm", "base": "", "fields": [ { - "name": "boid_id", + "name": "sponsor_boid_id", "type": "name" - }, - { - "name": "mod_id", - "type": "uint8" } ] }, { - "name": "pwrmod.new", + "name": "sponsor.set", "base": "", "fields": [ { - "name": "mod", - "type": "PwrMod" - } - ] - }, - { - "name": "pwrmod.rm", - "base": "", - "fields": [ - { - "name": "boid_id", - "type": "name" - }, - { - "name": "pwrmod_index", - "type": "int32" + "name": "row", + "type": "Sponsor" } ] }, @@ -1559,11 +1545,6 @@ } ] }, - { - "name": "stats.clean", - "base": "", - "fields": [] - }, { "name": "team.change", "base": "", @@ -1574,7 +1555,7 @@ }, { "name": "new_team_id", - "type": "uint16" + "type": "uint8" }, { "name": "new_pwr_tax_mult", @@ -1601,10 +1582,6 @@ { "name": "url_safe_name", "type": "string" - }, - { - "name": "info_json_ipfs", - "type": "string" } ] }, @@ -1635,10 +1612,30 @@ { "name": "url_safe_name", "type": "string" + } + ] + }, + { + "name": "team.rm", + "base": "", + "fields": [ + { + "name": "team_id", + "type": "uint8" + } + ] + }, + { + "name": "team.setmeta", + "base": "", + "fields": [ + { + "name": "team_id", + "type": "uint8" }, { - "name": "info_json_ipfs", - "type": "string" + "name": "meta", + "type": "bytes" } ] }, @@ -1747,8 +1744,8 @@ "ricardian_contract": "" }, { - "name": "accounts.clr", - "type": "accounts.clr", + "name": "account.rm", + "type": "account.rm", "ricardian_contract": "" }, { @@ -1771,6 +1768,21 @@ "type": "auth.rmkey", "ricardian_contract": "" }, + { + "name": "booster.add", + "type": "booster.add", + "ricardian_contract": "" + }, + { + "name": "booster.new", + "type": "booster.new", + "ricardian_contract": "" + }, + { + "name": "booster.rm", + "type": "booster.rm", + "ricardian_contract": "" + }, { "name": "config.clear", "type": "config.clear", @@ -1866,6 +1878,16 @@ "type": "offer.claim", "ricardian_contract": "" }, + { + "name": "offer.clean", + "type": "offer.clean", + "ricardian_contract": "" + }, + { + "name": "offer.rm", + "type": "offer.rm", + "ricardian_contract": "" + }, { "name": "owner.add", "type": "owner.add", @@ -1887,18 +1909,13 @@ "ricardian_contract": "" }, { - "name": "pwrmod.add", - "type": "pwrmod.add", + "name": "sponsor.rm", + "type": "sponsor.rm", "ricardian_contract": "" }, { - "name": "pwrmod.new", - "type": "pwrmod.new", - "ricardian_contract": "" - }, - { - "name": "pwrmod.rm", - "type": "pwrmod.rm", + "name": "sponsor.set", + "type": "sponsor.set", "ricardian_contract": "" }, { @@ -1911,11 +1928,6 @@ "type": "stake.deleg", "ricardian_contract": "" }, - { - "name": "stats.clean", - "type": "stats.clean", - "ricardian_contract": "" - }, { "name": "team.change", "type": "team.change", @@ -1931,6 +1943,16 @@ "type": "team.edit", "ricardian_contract": "" }, + { + "name": "team.rm", + "type": "team.rm", + "ricardian_contract": "" + }, + { + "name": "team.setmeta", + "type": "team.setmeta", + "ricardian_contract": "" + }, { "name": "team.taxrate", "type": "team.taxrate", @@ -1975,6 +1997,13 @@ "key_names": [], "key_types": [] }, + { + "name": "acctmeta", + "type": "AcctMeta", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, { "name": "auth", "type": "Auth", @@ -1982,6 +2011,13 @@ "key_names": [], "key_types": [] }, + { + "name": "boosters", + "type": "Booster", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, { "name": "config", "type": "Config", @@ -2024,13 +2060,6 @@ "key_names": [], "key_types": [] }, - { - "name": "pwrmods", - "type": "PwrMod", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, { "name": "sponsors", "type": "Sponsor", @@ -2045,13 +2074,6 @@ "key_names": [], "key_types": [] }, - { - "name": "stats", - "type": "Stats", - "index_type": "i64", - "key_names": [], - "key_types": [] - }, { "name": "teams", "type": "Team", diff --git a/src/external/boid/boid.contract.wasm b/src/external/boid/boid.contract.wasm index 6dc5eea9c1d30f99bd579c1c28e1709cf34b5ad9..07da69fb30372a8ee768b3f601bef0da4f690f55 100644 GIT binary patch literal 107102 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�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 literal 104022 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␠DuWJ!4|LSge?ZW| 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 S-4AH49>7R}G|FY|sQ(6g*g3TT diff --git a/src/tsconfig.json b/src/tsconfig.json index 642df0d..a11596d 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,21 +4,24 @@ "declarationMap": true, "outDir": "lib", "target": "esnext", - "module": "ESNext", + "module": "CommonJS", "moduleResolution": "node", - "noImplicitAny": false, + "noLib": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noEmitOnError": true, "strictNullChecks": true, "alwaysStrict": true, "noUnusedLocals": false, "noImplicitReturns": true, - "baseUrl": "./", + "baseUrl": ".", "paths": {}, "typeRoots": [ + "types", "node_modules/assemblyscript/std" ], "types": [ "assembly", - "node", ], "allowJs": false, "checkJs": false, diff --git a/tests/defaultConfig.js b/tests/defaultConfig.js new file mode 100644 index 0000000..04d040b --- /dev/null +++ b/tests/defaultConfig.js @@ -0,0 +1,112 @@ + +export const defaultConfigTN = { + paused: true, + account: { + invite_price: 10000, + premium_purchase_price: 500000, + max_premium_prefix: 3, + max_owners: 4, + max_boosters: 4, + suffix_whitelist: ["oid", "boider", "we", "cool", "yes", "voice"], + remove_sponsor_price: 200000, + sponsor_max_invite_codes: 5, + invite_code_expire_rounds: 48 + }, + power: { + sponsor_tax_mult: 0.1, + powered_stake_mult: 10000, + claim_maximum_elapsed_rounds: 10, + soft_max_pwr_add: 1000, + history_slots_length: 10 + }, + mint: { + round_powered_stake_mult: 0.0001, + round_power_mult: 1 + }, + team: { + change_min_rounds: 1, + edit_team_min_rounds: 1, + team_edit_max_pct_change: 1, + buy_team_cost: 5e6, + owner_stake_required: 2e7, + owner_future_stake_lock_rounds_required: 80 + }, + stake: { + unstake_rounds: 1, + extra_stake_min_locked_rounds: 0 + }, + time: { + rounds_start_sec_since_epoch: 1697766227, + round_length_sec: 7200 + }, + auth: { + key_actions_whitelist: ["unstake.init", "unstake.end", "power.claim", "team.change", "stake", "account.edit", "invite.buy", "invite.add", "invite.rm", "stake.deleg", "unstke.deleg", "unstake.stop", "offer.claim", "account.buy", "owner.add", "account.free"], + key_account_max_stake: 500000, + key_account_max_balance: 500000, + account_max_keys: 6, + worker_max_bill_per_action: 100 + }, + nft: { + boid_id_maximum_nfts: 6, + whitelist_collections: ["nft.boid"] + }, + allow_deposits: true, + allow_withdrawals: true, + recoveryAccount: "recover.boid" +} +// 46800 13 hrs +export const defaultConfig = { + account: { + "invite_price": 10000, + "premium_purchase_price": 500000, + "max_premium_prefix": 3, + "max_owners": 4, + "max_boosters": 4, + "suffix_whitelist": ["oid", "boider", "we", "cool", "yes", "voice"], + "remove_sponsor_price": 200000, + "sponsor_max_invite_codes": 5, + "invite_code_expire_rounds": 10 + }, + power: { + "sponsor_tax_mult": "0.1", + "powered_stake_mult": "70000.00", + "claim_maximum_elapsed_rounds": 10, + "soft_max_pwr_add": 1000, + "history_slots_length": 24 + }, + mint: { + "round_powered_stake_mult": "0.00007", + "round_power_mult": "0.85" + }, + team: { + "change_min_rounds": 6, + "edit_team_min_rounds": 12, + "team_edit_max_pct_change": 1, + "buy_team_cost": 25000000, + "owner_stake_required": 20000000, + "owner_future_stake_lock_rounds_required": 80 + }, + stake: { + "unstake_rounds": 8, + "extra_stake_min_locked_rounds": 0 + }, + time: { + "rounds_start_sec_since_epoch": 0, + "round_length_sec": 46800 + }, + auth: { + "key_actions_whitelist": ["unstake.init", "unstake.end", "power.claim", "team.change", "stake", "account.edit", "invite.buy", "invite.add", "invite.rm", "stake.deleg", "unstke.deleg", "unstake.stop", "offer.claim", "account.buy", "owner.add", "account.free"], + "key_account_max_stake": 500000, + "key_account_max_balance": 500000, + "account_max_keys": 6, + "worker_max_bill_per_action": 100 + }, + nft: { + "boid_id_maximum_nfts": 12, + "whitelist_collections": ["nft.boid"] + }, + paused: false, + allow_deposits: true, + allow_withdrawals: false, + recoveryAccount: "dac.boid", +} diff --git a/tests/deposit.spec.js b/tests/deposit.spec.js index c6ed1bb..017ffc5 100644 --- a/tests/deposit.spec.js +++ b/tests/deposit.spec.js @@ -4,10 +4,6 @@ import { expect } from "chai" import { beforeEach, describe, it } from "mocha" import { chain, init, oracles, setupOracle, tkn } from "./util.js" -const report = { protocol_id: 0, round: 10, units: 100 } -const report2 = { protocol_id: 0, round: 11, units: 100 } -const report3 = { protocol_id: 0, round: 12, units: 100 } - beforeEach(async () => { chain.resetTables() await init() @@ -24,7 +20,7 @@ describe("deposit", async() => { await setupOracle("oracle1") await tkn("transfer", { from: "token.boid", to: "oracle1", quantity: "10000000.0000 BOID", memo: "" }) await expectToThrow( - tkn("transfer", { from: "oracle1", to: "power.boid", quantity: "100000.0000 BOID", memo: "collateral" }, "oracle1"), + tkn("transfer", { from: "oracle1", to: "power.boid", quantity: "100001.0000 BOID", memo: "collateral" }, "oracle1"), "eosio_assert: must deposit collateral in correct increments") }) }) diff --git a/tests/lib/config.js b/tests/lib/config.js new file mode 100644 index 0000000..127ffcf --- /dev/null +++ b/tests/lib/config.js @@ -0,0 +1,39 @@ +export let config = { + paused: false, + consensus: { + min_weight: 2, + min_weight_pct: 0.66, + merge_deviation_pct: 0.25 + }, + payment: { + collateral_pct_pay_per_round_mult: 0.00007415, + round_bonus_pay_reports: 10, + round_bonus_pay_proposed: 2, + reports_proposed_adjust_pwr: .42, + num_oracles_adjust_base:1.015 + }, + slashLow: { + slash_quantity_static: 500000, + slash_quantity_collateral_pct: 5, + }, + slashMed: { + slash_quantity_static: 1000000, + slash_quantity_collateral_pct: 10, + }, + slashHigh: { + slash_quantity_static: 1500000, + slash_quantity_collateral_pct: 25, + }, + waits: { + withdraw_rounds_wait: 5, + collateral_unlock_wait_rounds: 10 + }, + collateral: { + oracle_collateral_deposit_increment: 1_000_000, + oracle_collateral_minimum: 5_000_000 + }, + keep_finalized_stats_rows: 10, + standby_toggle_interval_rounds: 5, + min_pay_report_share_threshold: 0.10, + reports_accumulate_weight_round_pct: 0.20 +} diff --git a/tests/oracle.spec.js b/tests/oracle.spec.js index 0920488..32d3bb5 100644 --- a/tests/oracle.spec.js +++ b/tests/oracle.spec.js @@ -4,6 +4,8 @@ import { expectToThrow } from "@proton/vert" import { expect } from "chai" import { beforeEach, describe, it } from "mocha" import { act, addRounds, boid_id, chain, getReportId, init, oracles, setupOracle, tkn } from "./util.js" +import {config} from "./lib/config.js" + const report = { protocol_id: 0, round: 10, units: 100 } @@ -14,8 +16,8 @@ beforeEach(async () => { describe("oracle", async() => { describe("oracldeposit", async() => { it("Success", async() => { - chain.createAccount("oracle1") - await act("oracleset", { account: "oracle1", weight: 0, adding_collateral:0 }) + setupOracle("oracle1") + await act("oracleset", { oracle: "oracle1", weight: 0, adding_collateral:0 }) await act("oracldeposit", { oracle: "oracle1", depositQuantity: 10 }) }) describe("validate oracldeposit checks", async() => { @@ -63,11 +65,11 @@ describe("oracle", async() => { let oracle = "" for(let i=0; i < 255; i++) { oracle = new Name(UInt64.random()).toString() - await setupOracle(oracle, "16000000.0000 BOID", true) + await setupOracle(oracle, "16000000.0000 BOID", 1, true) } oracle = new Name(UInt64.random()).toString() await expectToThrow( - setupOracle(oracle, "16000000.0000 BOID", true), + setupOracle(oracle, "16000000.0000 BOID", 1, true), "eosio_assert: max standy_oracles reached") }).timeout(8000) }) @@ -78,6 +80,7 @@ describe("oracle", async() => { await setupOracle("oracle2") await setupOracle("oracle3") await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) + addRounds(16) report.round = 15 await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report }, "oracle1") @@ -85,14 +88,14 @@ describe("oracle", async() => { await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report: report }, "oracle3") addRounds(1) await act("roundstats") - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }) addRounds(3) - await act("handleostat", { oracle: "oracle1", round: 15 }) + await act("payoutround", { oracle: "oracle1", round: 15 }) const unclaimed = oracles()[0].funds.unclaimed await act("withdrawinit", { oracle: "oracle1" }, "oracle1") expect(oracles()[0].funds.withdrawing).eq(unclaimed) - expect(oracles()[0].funds.withdrawable_after_round).eq(40) + expect(oracles()[0].funds.withdrawable_after_round).eq(20 + config.waits.withdraw_rounds_wait) }) describe("validate withdrawinit checks", async() => { it("Missing required authority", async() => { @@ -112,14 +115,14 @@ describe("oracle", async() => { await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report: report }, "oracle3") addRounds(1) await act("roundstats") - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }) addRounds(3) - await act("handleostat", { oracle: "oracle1", round: 15 }) + await act("payoutround", { oracle: "oracle1", round: 15 }) const unclaimed = oracles()[0].funds.unclaimed await act("withdrawinit", { oracle: "oracle1" }, "oracle1") expect(oracles()[0].funds.withdrawing).eq(unclaimed) - expect(oracles()[0].funds.withdrawable_after_round).eq(40) + expect(oracles()[0].funds.withdrawable_after_round).eq(config.waits.withdraw_rounds_wait+20) addRounds(5) await expectToThrow( act("withdrawinit", { oracle: "oracle1" }, "oracle1"), @@ -147,9 +150,9 @@ describe("oracle", async() => { await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report: report }, "oracle3") addRounds(1) await act("roundstats") - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }) addRounds(3) - await act("handleostat", { oracle: "oracle1", round: 15 }) + await act("payoutround", { oracle: "oracle1", round: 15 }) await act("withdrawinit", { oracle: "oracle1" }, "oracle1") await expectToThrow( @@ -192,9 +195,9 @@ describe("oracle", async() => { await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report: report }, "oracle3") addRounds(1) await act("roundstats") - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }) addRounds(3) - await act("handleostat", { oracle: "oracle1", round: 15 }) + await act("payoutround", { oracle: "oracle1", round: 15 }) await act("withdrawinit", { oracle: "oracle1" }, "oracle1") await expectToThrow( act("withdraw", { oracle: "oracle1" }, "oracle1"), @@ -262,7 +265,7 @@ describe("oracle", async() => { ) }) it("Oracle must be in standby to be unlocked", async() => { - await setupOracle("oracle1", "10000000.0000 BOID", false) + await setupOracle("oracle1", "10000000.0000 BOID", 1,false) addRounds(40) await expectToThrow( act("unlockinit", { oracle: "oracle1" }, "oracle1"), @@ -270,7 +273,7 @@ describe("oracle", async() => { ) }) it("No valid collateral to unlock (locked - slashed)", async() => { - await setupOracle("oracle1", "10000000.0000 BOID", false) + await setupOracle("oracle1", "10000000.0000 BOID", 1,false) addRounds(40) await act("setstandby", { oracle: "oracle1", standby: true }) await act("unlockinit", { oracle: "oracle1" }, "oracle1") diff --git a/tests/ostats.spec copy.js b/tests/ostats.spec copy.js new file mode 100644 index 0000000..60a2018 --- /dev/null +++ b/tests/ostats.spec copy.js @@ -0,0 +1,148 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { expectToThrow } from "@proton/vert" +import { beforeEach, describe, it } from "mocha" +import { act, addRounds, boid_id, chain, getReportId, init, setupOracle, stats } from "./util.js" + +const report = { protocol_id: 0, round: 10, units: 100 } +const report2 = { protocol_id: 0, round: 11, units: 100 } +const report3 = { protocol_id: 0, round: 13, units: 100 } + +beforeEach(async () => { + chain.resetTables() + await init() +}) +describe("reports", async() => { + describe("handleostat", async() => { + it("Success", async() => { + addRounds(11) + await setupOracle("oracle1") + await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") + await expectToThrow( + act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }), + "eosio_assert: can't finalize/merge reports this early in a round") + // console.log(oraclestats("oracle1")) + // console.log("Global:", global()) + await setupOracle("oracle3") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") + await setupOracle("oracle4") + await act("pwrreport", { oracle: "oracle4", boid_id_scope: boid_id, report }, "oracle4") + await setupOracle("oracle5") + await act("pwrreport", { oracle: "oracle5", boid_id_scope: boid_id, report }, "oracle5") + await setupOracle("oracle2") + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") + addRounds(1) + // console.log(stats()) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) + // console.log(oraclestats("oracle1")) + // console.log(oraclestats("oracle2")) + return + await expectToThrow( + act("handleostat", { oracle: "oracle1", round: 10 }), + "eosio_assert: can't process this round yet, not yet finalized" + ) + report2.round = 10 + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report: report2 }, "oracle2") + addRounds(1) + report2.round = 11 + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") + addRounds(1) + report2.round = 12 + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") + addRounds(1) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report3 }, "oracle1") + addRounds(1) + report2.round = 14 + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") + addRounds(1) + report2.round = 15 + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") + addRounds(1) + report2.round = 16 + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") + addRounds(1) + report2.round = 17 + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") + console.log(chain.console); + // await act("thisround") + // await act("handleostat", { oracle: "oracle1", round: 11 }).catch(err => { + // console.log(err.toString()) + // console.log(chain.console) + // }) + // console.log(chain.console) + // console.log(chain.actionTraces.map(el => [el.action.toString(), JSON.stringify(el.decodedData, null, 2)])) + console.log(stats()); + + addRounds(1) + await act("handleostat", { oracle: "oracle2", round: 10 }) + await act("handleostat", { oracle: "oracle3", round: 10 }) + await act("handleostat", { oracle: "oracle4", round: 10 }) + await act("handleostat", { oracle: "oracle5", round: 10 }) + }) + it("stats", async() => { + await setupOracle("oracle1") + await setupOracle("oracle2") + await setupOracle("oracle3") + addRounds(11) + await act("roundstats") + // addRounds(1) + await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") + addRounds(1) + await act("roundstats") + addRounds(3) + await act("handleostat", { oracle: "oracle1", round: 10 }) + console.log(chain.console) + }) + describe("validate handleostat checks", async() => { + it("Can't process this round yet, not yet finalized", async() => { + addRounds(3) + await expectToThrow( + act("handleostat", { oracle: "oracle1", round: 3 }), + "eosio_assert: can't process this round yet, not yet finalized") + }) + it("OStats round doesn't exist", async() => { + addRounds(7) + await expectToThrow( + act("handleostat", { oracle: "oracle1", round: 2 }), + "eosio_assert: oStats round doesn't exist") + }) + it("Round stats not yet available", async() => { + addRounds(12) + report.round = 11 + await setupOracle("oracle1") + await setupOracle("oracle2") + await setupOracle("oracle3") + await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") + addRounds(3) + await expectToThrow( + act("handleostat", { oracle: "oracle1", round: 11 }), + "eosio_assert: round stats not yet available") + }) + it("Round stats is already processed", async() => { + await setupOracle("oracle1") + await setupOracle("oracle2") + await setupOracle("oracle3") + addRounds(11) + await act("roundstats") + addRounds(1) + await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") + addRounds(1) + await act("roundstats") + addRounds(3) + await act("handleostat", { oracle: "oracle1", round: 10 }) + await expectToThrow( + act("handleostat", { oracle: "oracle1", round: 10 }), + "eosio_assert: round stats is already processed") + }) + }) + }) +}) diff --git a/tests/ostats.spec.js b/tests/ostats.spec.js index ff4739e..80ab8b1 100644 --- a/tests/ostats.spec.js +++ b/tests/ostats.spec.js @@ -1,13 +1,11 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { expect } from "chai" -import { beforeEach, describe, it, before } from "mocha" -import { Asset, Name, TimePoint, PrivateKey, PublicKey, Action, Bytes, ABI, ABIDecoder, Authority, PermissionLevel, UInt32, Serializer, TimePointSec } from "@greymass/eosio" -import { Blockchain, nameToBigInt, symbolCodeToBigInt, protonAssert, expectToThrow, nameTypeToBigInt } from "@proton/vert" -import { init, chain, act, oracles, global, contract, reports, boid, addRounds, tkn, config, wait, setupOracle, oracleStats, stats, logActions, getReportId, boid_id } from "./util.js" +import { expectToThrow } from "@proton/vert" +import { beforeEach, describe, it } from "mocha" +import { act, addRounds, boid_id, chain, getReportId, init, oracleStats, setupOracle, global, oracle, reports, findRoundPayout } from "./util.js" const report = { protocol_id: 0, round: 10, units: 100 } -const report2 = { protocol_id: 0, round: 11, units: 100 } -const report3 = { protocol_id: 0, round: 12, units: 100 } +const report2 = { protocol_id: 0, round: 10, units: 100 } +const report3 = { protocol_id: 0, round: 13, units: 100 } beforeEach(async () => { chain.resetTables() @@ -16,130 +14,64 @@ beforeEach(async () => { describe("reports", async() => { describe("handleostat", async() => { it("Success", async() => { - addRounds(11) - await setupOracle("oracle1") - await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") - await expectToThrow( - act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }), + await setupOracle("oracle1") + await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) + addRounds(11) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") + await act("pwrreport", { oracle: "oracle1", boid_id_scope: "teamownr", report:report2 }, "oracle1") + await expectToThrow( + act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }), "eosio_assert: can't finalize/merge reports this early in a round") - // console.log(oraclestats("oracle1")) - // console.log("Global:", global()) - await setupOracle("oracle3") - await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") - await setupOracle("oracle4") - await act("pwrreport", { oracle: "oracle4", boid_id_scope: boid_id, report }, "oracle4") - await setupOracle("oracle5") - await act("pwrreport", { oracle: "oracle5", boid_id_scope: boid_id, report }, "oracle5") await setupOracle("oracle2") - await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") - addRounds(1) - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) - // console.log(oraclestats("oracle1")) - // console.log(oraclestats("oracle2")) - // console.log(stats()) - await expectToThrow( - act("handleostat", { oracle: "oracle1", round: 10 }), - "eosio_assert: can't process this round yet, not yet finalized" - ) - addRounds(1) - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") - addRounds(1) - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report3 }, "oracle1") - addRounds(1) - report2.round = 13 - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") - addRounds(1) - report2.round = 14 - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") - addRounds(1) - report2.round = 15 - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") - addRounds(1) - report2.round = 16 - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report: report2 }, "oracle1") - console.log(chain.console); - // await act("thisround") - await act("handleostat", { oracle: "oracle1", round: 11 }).catch(err => { - console.log(err.toString()) - console.log(chain.console) - }) - // console.log(chain.console) - // console.log(chain.actionTraces.map(el => [el.action.toString(), JSON.stringify(el.decodedData, null, 2)])) - - await act("handleostat", { oracle: "oracle2", round: 10 }) - await act("handleostat", { oracle: "oracle3", round: 10 }) - await act("handleostat", { oracle: "oracle4", round: 10 }) - await act("handleostat", { oracle: "oracle5", round: 10 }) - }) - it("stats", async() => { - await setupOracle("oracle1") - await setupOracle("oracle2") - await setupOracle("oracle3") - addRounds(11) - await act("roundstats") - addRounds(1) - await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") - await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") - await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") - addRounds(1) - await act("roundstats") - addRounds(3) - await act("handleostat", { oracle: "oracle1", round: 10 }) - //console.log(chain.console) - }) - describe("validate handleostat checks", async() => { - it("Chain is too recent to generate reports", async() => { - await expectToThrow( - act("handleostat", { oracle: "oracle1", round: 10 }), - "eosio_assert: chain is too recent to generate reports") - }) - it("Can't process this round yet, not yet finalized", async() => { - addRounds(3) - await expectToThrow( - act("handleostat", { oracle: "oracle1", round: 3 }), - "eosio_assert: can't process this round yet, not yet finalized") - }) - it("OStats round doesn't exist", async() => { - addRounds(7) - await expectToThrow( - act("handleostat", { oracle: "oracle1", round: 2 }), - "eosio_assert: oStats round doesn't exist") - }) - it("Round stats not yet available", async() => { - addRounds(12) - await setupOracle("oracle1") - await setupOracle("oracle2") - await setupOracle("oracle3") - await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") - await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") - await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") - addRounds(3) - await expectToThrow( - act("handleostat", { oracle: "oracle1", round: 10 }), - "eosio_assert: round stats not yet available") - }) - it("Round stats is already processed", async() => { - await setupOracle("oracle1") - await setupOracle("oracle2") - await setupOracle("oracle3") - addRounds(11) - await act("roundstats") - addRounds(1) - await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) - await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") - await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") - await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") - addRounds(1) - await act("roundstats") - addRounds(3) - await act("handleostat", { oracle: "oracle1", round: 10 }) - await expectToThrow( - act("handleostat", { oracle: "oracle1", round: 10 }), - "eosio_assert: round stats is already processed") - }) + await act("pwrreport", { oracle: "oracle2", boid_id_scope: "teamownr", report:report2 }, "oracle2") + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") + await setupOracle("oracle3") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: "teamownr", report:report2 }, "oracle3") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report }, "oracle3") + await setupOracle("oracle4") + await act("pwrreport", { oracle: "oracle4", boid_id_scope: boid_id, report }, "oracle4") + await setupOracle("oracle5") + await act("pwrreport", { oracle: "oracle5", boid_id_scope: boid_id, report }, "oracle5") + addRounds(1) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }) + await act("finishreport", { boid_id_scope: "teamownr", pwrreport_id: getReportId(report2) }) + await expectToThrow( act("payoutround", { round: 11,oracle:"oracle1" }, "oracle1"), + "eosio_assert: can't process this round yet, not yet finalized") + addRounds(1) + await act("payoutround", { oracle:"oracle1",round: 10 }, "oracle1") + await act("payoutround", { round: 10 ,oracle:"oracle2" }, "oracle1") + await act("payoutround", { round: 10 ,oracle:"oracle3" }, "oracle1") + await act("payoutround", { round: 10 ,oracle:"oracle4" }, "oracle1") + await act("payoutround", { round: 10 ,oracle:"oracle5" }, "oracle1") + console.log('oracle1 earned:',oracle("oracle1").funds.unclaimed) + console.log('oracle2 earned:',oracle("oracle2").funds.unclaimed) + console.log('oracle3 earned:',oracle("oracle3").funds.unclaimed) + console.log('oracle4 earned:',oracle("oracle4").funds.unclaimed) + console.log('oracle5 earned:',oracle("oracle5").funds.unclaimed) + console.log('o1 payout estimate:',findRoundPayout("oracle1", 10)) + console.log('o2 payout estimate:',findRoundPayout("oracle2", 10)) + console.log('o3 payout estimate:',findRoundPayout("oracle3", 10)) + console.log('o4 payout estimate:',findRoundPayout("oracle4", 10)) + console.log('o5 payout estimate:',findRoundPayout("oracle5", 10)) + addRounds(1) + await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report:report3 }, "oracle1") + await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report:report3 }, "oracle2") + await act("pwrreport", { oracle: "oracle3", boid_id_scope: boid_id, report:report3 }, "oracle3") + addRounds(1) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report3) }) + addRounds(1) + await act("payoutround", { protocol_id: 0, round: 13 ,oracle:"oracle1" }, "oracle1") + await act("payoutround", { protocol_id: 0, round: 13 ,oracle:"oracle2" }, "oracle1") + await act("payoutround", { protocol_id: 0, round: 13 ,oracle:"oracle3" }, "oracle1") + console.log('oracle1 earned:',oracle("oracle1").funds.unclaimed) + console.log('oracle2 earned:',oracle("oracle2").funds.unclaimed) + console.log('oracle3 earned:',oracle("oracle2").funds.unclaimed) + console.log(chain.console) + + + + + }) }) -}) \ No newline at end of file +}) diff --git a/tests/package.json b/tests/package.json index 9015238..128015f 100644 --- a/tests/package.json +++ b/tests/package.json @@ -5,7 +5,8 @@ "test:oracle": "mocha -b ./oracle.spec.js", "test:ostats": "mocha -b ./ostats.spec.js", "test:pwrreport": "mocha -b ./pwrreport.spec.js", - "test:slash": "mocha -b ./slash.spec.js" + "test:slash": "mocha -b ./slash.spec.js", + "test:s": "mocha -b ./scenarios.spec.js" }, "type": "module", "devDependencies": { diff --git a/tests/pwrreport.spec.js b/tests/pwrreport.spec.js index eb2c050..2e5a23c 100644 --- a/tests/pwrreport.spec.js +++ b/tests/pwrreport.spec.js @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { Name, TimePointSec } from "@greymass/eosio" +import { expectToThrow } from "@proton/vert" import { expect } from "chai" -import { beforeEach, describe, it, before } from "mocha" -import { Asset, Name, TimePoint, PrivateKey, PublicKey, Action, Bytes, ABI, ABIDecoder, Authority, PermissionLevel, UInt32, Serializer, TimePointSec } from "@greymass/eosio" -import { Blockchain, nameToBigInt, symbolCodeToBigInt, protonAssert, expectToThrow, nameTypeToBigInt } from "@proton/vert" -import { init, chain, act, oracles, global, contract, reports, boid, addRounds, tkn, config, wait, setupOracle, oracleStats, stats, logActions, getReportId, boid_id } from "./util.js" +import { beforeEach, describe, it } from "mocha" +import { act, addRounds, boid_id, chain, getReportId, global, init, reports, setupOracle } from "./util.js" const report = { protocol_id: 0, round: 10, units: 100 } const report2 = { protocol_id: 0, round: 10, units: 105 } @@ -33,7 +33,7 @@ describe("reports", async() => { await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") // console.log(await act("thisround")); // return - // console.log(reports(boid_id)) + console.log(reports(boid_id)) const rRow = reports(boid_id)[0] console.log(chain.console); console.log(rRow); @@ -145,7 +145,7 @@ describe("reports", async() => { for(let i=1; i < 5; i++) await act("pwrreport", { oracle: `oracle${i}`, boid_id_scope: boid_id, report: i % 2 ? r1 : r2 }, `oracle${i}`) - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(r1), getReportId(r2)] }) + await act("mergereports", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(r1), getReportId(r2)] }) await expectToThrow( act("pwrreport", { oracle: "oracle5", boid_id_scope: boid_id, report: r1 }, "oracle5"), "eosio_assert: report already merged") @@ -161,7 +161,7 @@ describe("reports", async() => { await act("protoset", { protocol: { protocol_id: 0, protocol_name: "testproto", unitPowerMult: 1, active:true } }) await act("pwrreport", { oracle: "oracle1", boid_id_scope: boid_id, report }, "oracle1") await expectToThrow( - act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }), + act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }), "eosio_assert: can't finalize/merge reports this early in a round") // console.log(oraclestats("oracle1")) // console.log("Global:", global()) @@ -174,7 +174,7 @@ describe("reports", async() => { await setupOracle("oracle2") await act("pwrreport", { oracle: "oracle2", boid_id_scope: boid_id, report }, "oracle2") addRounds(1) - await act("finishreport", { boid_id_scope: boid_id, pwrreport_ids: [getReportId(report)] }) + await act("finishreport", { boid_id_scope: boid_id, pwrreport_id: getReportId(report) }) }) it("merge 4", async() => { // console.log(JSON.stringify(boid.permissions, null, 2)) @@ -216,7 +216,7 @@ describe("reports", async() => { console.log(reports("testaccount")) console.log(global()) - await act("finishreport", { boid_id_scope: "testaccount", pwrreport_ids: reportsArray.map(r => getReportId(r)) }) + await act("mergereports", { boid_id_scope: "testaccount", pwrreport_ids: reportsArray.map(r => getReportId(r)) }) console.log("chain console:", chain.console) // console.log(reports("testaccount")) expect(reports("testaccount").length).eq(5) @@ -255,11 +255,11 @@ describe("reports", async() => { await act("pwrreport", { oracle: "oracle3", boid_id_scope: "testaccount", report: reportsArray[2] }, "oracle3@active") // await act("pwrreport", { oracle: "oracle4", boid_id_scope: "testaccount", report: { protocol_id: 0, round: 33, units: 13 } }, "oracle4@active") // console.log(chain.actionTraces.map(el => [el.action.toString(), JSON.stringify(el.decodedData, null, 2)])) - await act("finishreport", { boid_id_scope: "testaccount", pwrreport_ids: reportsArray.map(r => getReportId(r)) }) + await act("mergereports", { boid_id_scope: "testaccount", pwrreport_ids: reportsArray.map(r => getReportId(r)) }) // console.log(reports("testaccount")) expect(reports("testaccount").length).eq(3) expectToThrow( - act("finishreport", { boid_id_scope: "testaccount", pwrreport_ids: reportsArray.map(r => getReportId(r)) }) + act("mergereports", { boid_id_scope: "testaccount", pwrreport_ids: reportsArray.map(r => getReportId(r)) }) , "eosio_assert: can't merge reports already merged" ) }) diff --git a/tests/scenarios.spec.js b/tests/scenarios.spec.js new file mode 100644 index 0000000..d27bc50 --- /dev/null +++ b/tests/scenarios.spec.js @@ -0,0 +1,19 @@ +import { act, chain, init, setupOracle } from "./util.js" +// chain.resetTables() +const report = { protocol_id: 0, round: 10, units: 100 } + +async function scenario1(){ + try { + await init() + // await setupOracle("oracle1") + // await setupOracle("oracle2") + // await setupOracle("oracle3") + // act("thisround") + // console.log(chain.console) + // console.log(chain.actionTraces.map(el => [el.action.toString(), JSON.stringify(el.decodedData, null, 2)])) + } catch (e) { + console.error(e) + } + +} +scenario1() diff --git a/tests/testRoundPayout.js b/tests/testRoundPayout.js new file mode 100644 index 0000000..2452e45 --- /dev/null +++ b/tests/testRoundPayout.js @@ -0,0 +1,49 @@ +function calculateBonusAndAPR(roundBonusPayReports, roundBonusPayProposed, reportsProposedAdjustPwr,numOraclesAdjustBase,collateralPctPay, reportedOrMerged, proposed, investedAmount, periodsPerYear,activeOracles) { + // Calculate the bonus payment + let bonusPay = roundBonusPayReports * Math.pow(reportedOrMerged, reportsProposedAdjustPwr) + roundBonusPayProposed * Math.pow(proposed, reportsProposedAdjustPwr); + bonusPay = bonusPay / Math.pow(numOraclesAdjustBase, activeOracles); + // bonusPay = bonusPay / activeOracles + + // Calculate APR based on the bonus and invested amount + let basePay = collateralPctPay * investedAmount; + // let apr = (basePay / investedAmount) * periodsPerYear * 100; // Multiply by 100 to convert to percentage + let apr = ((basePay+bonusPay) / investedAmount) * periodsPerYear * 100; // Multiply by 100 to convert to percentage + return { bonusPay, apr, basePay }; +} + +// Configuration +const configPayment = { + collateral_pct_pay_per_round_mult: 0.000037075, // 2.5% APR + round_bonus_pay_reports: 10, + round_bonus_pay_proposed: 2, + reports_proposed_adjust_pwr: .42, + num_oracles_adjust_base:1.015 + } + + +// Additional Parameters +const activeOracles = 20 +const oracleCollateral = 5000000 +const periodsPerYear = 674.31 // for calculating oracle collateral APR + +// Scenarios +const scenarios = [100,500, 1000, 2050, 5000, 10000, 100000]; +const proposedReports = 200; // Assuming constant for simplicity + +// Calculate and print the bonus and APR for each scenario +scenarios.forEach(reportedOrMerged => { + const {bonusPay, apr,basePay} = calculateBonusAndAPR( + configPayment.round_bonus_pay_reports, + configPayment.round_bonus_pay_proposed, + configPayment.reports_proposed_adjust_pwr, + configPayment.num_oracles_adjust_base, + configPayment.collateral_pct_pay_per_round_mult, + reportedOrMerged, + proposedReports, + oracleCollateral, + periodsPerYear, + activeOracles + ); + + console.log(`Reported/Merged: ${reportedOrMerged}, Bonus: ${bonusPay.toLocaleString(undefined, {maximumFractionDigits: 2})}, Base: ${ basePay } APR: ${apr.toFixed(2)}%, total paid: ${(bonusPay*activeOracles).toLocaleString()}`); +}); diff --git a/tests/util.js b/tests/util.js index ceccd54..e16cdb9 100644 --- a/tests/util.js +++ b/tests/util.js @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Authority, Name, PermissionLevel, PermissionLevelWeight, TimePoint, UInt16, UInt32 } from "@wharfkit/antelope" import { AccountPermission, Blockchain } from "@proton/vert" +import { defaultConfig } from "./defaultConfig.js" +import { config } from "./lib/config.js" export const chain = new Blockchain() export const contract = chain.createContract("power.boid", "../build/boid.contract") @@ -105,7 +107,6 @@ token.setPermissions([ }) }) ]) - boid.setPermissions([ AccountPermission.from({ perm_name: Name.from("owner"), @@ -154,9 +155,8 @@ boid.setPermissions([ }) }) ]) + export async function initTokens() { - // const tkn = token.actions - // chain.createAccount("token.boid") const issuer = Name.from("token.boid") const maximum_supply = "25000000000.0000 BOID" await tkn("create", { issuer, maximum_supply }) @@ -164,8 +164,7 @@ export async function initTokens() { await tkn("transfer", { from: issuer, to: "tknmint.boid", quantity: "100000000.0000 BOID", memo: "" }) } -const roundLengthSec = 90000 -export const roundStartTime = TimePoint.fromMilliseconds(Date.now()) +export const roundStartTime = TimePoint.fromMilliseconds(defaultConfig.time.rounds_start_sec_since_epoch * 1000) console.log(roundStartTime.toString()) export async function wait(ms) { @@ -218,48 +217,25 @@ export const owners = ["boid"] export const sponsors = ["sponsoracct"] export const boid_id = "testaccount" -export const config = { - paused: false, - min_consensus_weight: 30, - min_consensus_pct: 0.66, - collateral_pct_pay_per_round: 0.01, - round_bonus_pay_reports: 50000, - round_bonus_pay_proposed: 200000, - slash_threshold_pct: 0.5, - slash_quantity_static: 500000, - slash_quantity_collateral_pct: 0.01, - withdraw_rounds_wait: 20, - keep_finalized_stats_rows: 2000, - reports_finalized_after_rounds: 3, - unlock_wait_rounds: 40, - first_unlock_wait_rounds: 20, - standby_toggle_interval_rounds: 20, - weight_collateral_pwr: 1.1, - oracle_collateral_deposit_increment: 1000000, - reports_accumulate_weight_round_pct: 0.20, - weight_collateral_divisor:1000000, - merge_deviation_pct: 0.25, - oracle_expected_active_after_rounds: 2, - min_pay_report_share_threshold: 0.01 -} + export function addRounds(numRounds = 0) { // @ts-ignore - chain.addTime(TimePoint.fromMilliseconds(roundLengthSec * 1000 * numRounds)) + chain.addTime(TimePoint.fromMilliseconds(defaultConfig.time.round_length_sec * 1000 * numRounds)) } -export async function setupOracle(name = "oraclename", quantity = "10000000.0000 BOID", standby = false) { +export async function setupOracle(name = "oraclename", quantity = "10000000.0000 BOID", weight = 1,standby = false) { chain.createAccount(name) await tkn("transfer", { from: "token.boid", to: name, quantity, memo: "" }) await tkn("transfer", { from: name, to: "power.boid", quantity, memo: "collateral" }, name) - if(!standby) - await act("setstandby", { oracle: name, standby: false }) + await act("setweight", { oracle: name, weight },"power.boid") + if(!standby) await act("setstandby", { oracle: name, standby: false },"power.boid") } export async function init() { chain.createAccount("recover.boid") await boid.actions["auth.init"]({ }).send() - await boid.actions["config.set"]({ config: { } }).send() + await boid.actions["config.set"]({ config: defaultConfig }).send() await boid.actions["account.add"]({ boid_id: "boid", owners: ["boid"], sponsors: [], keys: [] }).send() await boid.actions["account.add"]({ boid_id, owners: ["boid"], sponsors: [], keys: [] }).send() await boid.actions["account.add"]({ boid_id: Name.from("teamownr"), owners: ["recover.boid"], sponsors: [], keys: [] }).send() @@ -273,3 +249,15 @@ export async function init() { export function getReportId(report = {protocol_id:0,round:0,units:0}) { return Number((BigInt(report.protocol_id) << BigInt(48)) + (BigInt(report.round) << BigInt(32)) + BigInt(report.units)) } +export function trueCollateral(oracleRow) { + return oracleRow.collateral.locked - oracleRow.collateral.slashed +} +export function findRoundPayout(oracleName,roundNum){ + const oracleRow = oracle(oracleName) + const oRoundTable = oracleStats(oracleName) + const oRoundData = oRoundTable.filter(el => el.round == roundNum)[0] + if(!oRoundData) throw new Error(`Cannot find round ${roundNum} for ${oracleName}`) + const basePay = trueCollateral(oracleRow) * config.payment.collateral_pct_pay_per_round_mult + const bonusPay = config.payment.round_bonus_pay_reports * oRoundData.reports.reported_or_merged + config.payment.round_bonus_pay_proposed * oRoundData.reports.proposed + return parseInt((bonusPay + basePay).toString()) +}