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 6dc5eea..07da69f 100644 Binary files a/src/external/boid/boid.contract.wasm and b/src/external/boid/boid.contract.wasm differ 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()) +}