diff --git a/__tests__/integration/core-api/v2/handlers/delegates.test.ts b/__tests__/integration/core-api/v2/handlers/delegates.test.ts index 694be1de6d..d53f5a6814 100644 --- a/__tests__/integration/core-api/v2/handlers/delegates.test.ts +++ b/__tests__/integration/core-api/v2/handlers/delegates.test.ts @@ -11,20 +11,34 @@ import { app } from "@arkecosystem/core-container"; import { Database } from "@arkecosystem/core-interfaces"; const delegate = { - username: "genesis_9", - address: "AG8kwwk4TsYfA2HdwaWBVAJQBj6VhdcpMo", - publicKey: "0377f81a18d25d77b100cb17e829a72259f08334d064f6c887298917a04df8f647", -}; - -const delegate2 = { username: "genesis_10", address: "AFyf2qVpX2JbpKcy29XbusedCpFDeYFX8Q", publicKey: "02f7acb179ddfddb2e220aa600921574646ac59fd3f1ae6255ada40b9a7fab75fd", + forgedFees: 50, + forgedRewards: 50, + forgedTotal: 100, + producedBlocks: 75, + missedBlocks: 25, + productivity: 75, + voteBalance: 100000, }; +const delegate2 = { + username: "genesis_11", +} + beforeAll(async () => { await setUp(); await calculateRanks(); + + const wm = app.resolvePlugin("database").walletManager; + const wallet = wm.findByUsername("genesis_10"); + wallet.forgedFees = new Bignum(delegate.forgedFees); + wallet.forgedRewards = new Bignum(delegate.forgedRewards); + wallet.producedBlocks = 75; + wallet.missedBlocks = 25; + wallet.voteBalance = new Bignum(delegate.voteBalance); + wm.reindex(wallet); }); afterAll(async () => { @@ -131,7 +145,8 @@ describe("API 2.0 - Delegates", () => { expect(response).toBeSuccessfulResponse(); expect(response.data.data).toBeObject(); - utils.expectDelegate(response.data.data, delegate); + utils.expectDelegate(response.data.data); + expect(response.data.data.username).toEqual(delegate.username); }); }, ); @@ -144,7 +159,8 @@ describe("API 2.0 - Delegates", () => { expect(response).toBeSuccessfulResponse(); expect(response.data.data).toBeObject(); - utils.expectDelegate(response.data.data, delegate); + utils.expectDelegate(response.data.data); + expect(response.data.data.address).toEqual(delegate.address); }); }, ); @@ -157,7 +173,8 @@ describe("API 2.0 - Delegates", () => { expect(response).toBeSuccessfulResponse(); expect(response.data.data).toBeObject(); - utils.expectDelegate(response.data.data, delegate); + utils.expectDelegate(response.data.data); + expect(response.data.data.publicKey).toEqual(delegate.publicKey); }); }, ); @@ -167,29 +184,361 @@ describe("API 2.0 - Delegates", () => { describe.each([["API-Version", "request"], ["Accept", "requestWithAcceptHeader"]])( "using the %s header", (header, request) => { - it("should POST a search for delegates with a username that matches the given string", async () => { + it("should POST a search for delegates with an address that matches the given string", async () => { const response = await utils[request]("POST", "delegates/search", { - username: delegate.username, + address: delegate.address }); + expect(response).toBeSuccessfulResponse(); expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.address).toBe(delegate.address); + } + }); + + it("should POST a search for delegates with a public key that matches the given string", async () => { + const response = await utils[request]("POST", "delegates/search", { + publicKey: delegate.publicKey + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.publicKey).toBe(delegate.publicKey); + } + }); + + it("should POST a search for delegates with a username that matches the given string", async () => { + const response = await utils[request]("POST", "delegates/search", { + username: delegate.username + }); + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); expect(response.data.data).toHaveLength(1); - utils.expectDelegate(response.data.data[0], delegate); + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.username).toEqual(delegate.username); + } }); it("should POST a search for delegates with any of the specified usernames", async () => { + const usernames = [delegate.username, delegate2.username] + + const response = await utils[request]("POST", "delegates/search", { + usernames + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(2); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(usernames.includes(elem.username)).toBe(true); + } + }); + + it("should POST a search for delegates with the exact specified approval", async () => { + const response = await utils[request]("POST", "delegates/search", { + approval: { + from: 0, + to: 0, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(2); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.production.approval).toEqual(0); + } + }); + + it("should POST a search for delegates with the specified approval range", async () => { + const response = await utils[request]("POST", "delegates/search", { + approval: { + from: 1, + to: 100, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(49); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.production.approval).toBeGreaterThanOrEqual(1); + expect(elem.production.approval).toBeLessThanOrEqual(100) + } + }); + + it("should POST a search for delegates with the exact specified forged fees", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedFees: { + from: delegate.forgedFees, + to: delegate.forgedFees, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.fees).toEqual(delegate.forgedFees) + } + }); + + it("should POST a search for delegates with the specified forged fees range", async () => { const response = await utils[request]("POST", "delegates/search", { - usernames: [delegate.username, delegate2.username], + forgedFees: { + from: 0, + to: delegate.forgedFees, + }, }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.fees).toBeGreaterThanOrEqual(0); + expect(elem.forged.fees).toBeLessThanOrEqual(delegate.forgedFees) + } + }); + + it("should POST a search for delegates with the exact specified forged rewards", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedRewards: { + from: delegate.forgedRewards, + to: delegate.forgedRewards, + }, + }); + expect(response).toBeSuccessfulResponse(); expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.rewards).toEqual(delegate.forgedRewards) + } + }); + + it("should POST a search for delegates with the specified forged rewards range", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedRewards: { + from: 0, + to: delegate.forgedRewards, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.rewards).toBeGreaterThanOrEqual(0); + expect(elem.forged.rewards).toBeLessThanOrEqual(delegate.forgedRewards) + } + }); + it("should POST a search for delegates with the exact specified forged total", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedTotal: { + from: delegate.forgedTotal, + to: delegate.forgedTotal, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.total).toEqual(delegate.forgedTotal) + } + }); + + it("should POST a search for delegates with the specified forged total range", async () => { + const response = await utils[request]("POST", "delegates/search", { + forgedRewards: { + from: 0, + to: delegate.forgedTotal, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.forged.total).toBeGreaterThanOrEqual(0); + expect(elem.forged.total).toBeLessThanOrEqual(delegate.forgedTotal) + } + }); + + it("should POST a search for delegates with the exact specified produced blocks", async () => { + const response = await utils[request]("POST", "delegates/search", { + producedBlocks: { + from: delegate.producedBlocks, + to: delegate.producedBlocks, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.blocks.produced).toEqual(delegate.producedBlocks) + } + }); + + it("should POST a search for delegates with the specified produced blocks range", async () => { + const response = await utils[request]("POST", "delegates/search", { + producedBlocks: { + from: 0, + to: delegate.producedBlocks, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.blocks.produced).toBeGreaterThanOrEqual(0); + expect(elem.blocks.produced).toBeLessThanOrEqual(delegate.producedBlocks) + } + }); + + it("should POST a search for delegates with the exact specified missed blocks", async () => { + const response = await utils[request]("POST", "delegates/search", { + missedBlocks: { + from: delegate.missedBlocks, + to: delegate.missedBlocks, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.blocks.missed).toEqual(delegate.missedBlocks) + } + }); + + it("should POST a search for delegates with the specified missed blocks range", async () => { + const response = await utils[request]("POST", "delegates/search", { + missedBlocks: { + from: 0, + to: delegate.missedBlocks, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.blocks.missed).toBeGreaterThanOrEqual(0); + expect(elem.blocks.missed).toBeLessThanOrEqual(delegate.missedBlocks) + } + }); + + it("should POST a search for delegates with the exact specified productivity", async () => { + const response = await utils[request]("POST", "delegates/search", { + productivity: { + from: delegate.productivity, + to: delegate.productivity, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.production.productivity).toEqual(delegate.productivity) + } + }); + + it("should POST a search for delegates with the specified productivity range", async () => { + const response = await utils[request]("POST", "delegates/search", { + productivity: { + from: 0, + to: delegate.productivity, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(51); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.production.productivity).toBeGreaterThanOrEqual(0); + expect(elem.production.productivity).toBeLessThanOrEqual(delegate.productivity) + } + }); + + it("should POST a search for delegates with the exact specified vote balance", async () => { + const response = await utils[request]("POST", "delegates/search", { + voteBalance: { + from: delegate.voteBalance, + to: delegate.voteBalance, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); + expect(response.data.data).toHaveLength(1); + + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.votes).toEqual(delegate.voteBalance) + } + }); + + it("should POST a search for delegates with the specified vote balance range", async () => { + const response = await utils[request]("POST", "delegates/search", { + voteBalance: { + from: 0, + to: delegate.voteBalance, + }, + }); + + expect(response).toBeSuccessfulResponse(); + expect(response.data.data).toBeArray(); expect(response.data.data).toHaveLength(2); - for (const delegate of response.data.data) { - utils.expectDelegate(delegate); + for (const elem of response.data.data) { + utils.expectDelegate(elem); + expect(elem.votes).toBeGreaterThanOrEqual(0); + expect(elem.votes).toBeLessThanOrEqual(delegate.voteBalance) } }); }, diff --git a/__tests__/unit/core-database/repositories/delegates-business-repository.test.ts b/__tests__/unit/core-database/repositories/delegates-business-repository.test.ts index 0afc22088a..8d47369c02 100644 --- a/__tests__/unit/core-database/repositories/delegates-business-repository.test.ts +++ b/__tests__/unit/core-database/repositories/delegates-business-repository.test.ts @@ -62,7 +62,11 @@ function generateWallets(): models.Wallet[] { describe("Delegate Repository", () => { describe("getLocalDelegates", () => { - const delegates = [{ username: "delegate-0" }, { username: "delegate-1" }, { username: "delegate-2" }]; + const delegates = [ + { username: "delegate-0", forgedFees: new Bignum(10), forgedRewards: new Bignum(10) }, + { username: "delegate-1", forgedFees: new Bignum(20), forgedRewards: new Bignum(20) }, + { username: "delegate-2", forgedFees: new Bignum(30), forgedRewards: new Bignum(30) }, + ]; const wallets = [delegates[0], {}, delegates[1], { username: "" }, delegates[2], {}]; it("should return the local wallets of the connection that are delegates", () => { @@ -74,6 +78,18 @@ describe("Delegate Repository", () => { expect(actualDelegates).toEqual(expect.arrayContaining(delegates)); expect(walletManager.allByAddress).toHaveBeenCalled(); }); + + it("should be ok with params (forgedTotal)", () => { + // @ts-ignore + jest.spyOn(walletManager, "allByAddress").mockReturnValue(wallets); + + const actualDelegates = repository.getLocalDelegates({ forgedTotal: null }); + + actualDelegates.forEach(delegate => { + expect(delegate.hasOwnProperty('forgedTotal')); + expect(+delegate.forgedTotal.toFixed()).toBe(delegateCalculator.calculateForgedTotal(delegate)); + }) + }); }); describe("findAll", () => { diff --git a/__tests__/unit/core-utils/delegate-calculator.test.ts b/__tests__/unit/core-utils/delegate-calculator.test.ts index 114ae0c83b..b5dc8c14e7 100644 --- a/__tests__/unit/core-utils/delegate-calculator.test.ts +++ b/__tests__/unit/core-utils/delegate-calculator.test.ts @@ -2,7 +2,7 @@ import "./__support__/mocks/core-container-calculator"; import { Bignum, models } from "@arkecosystem/crypto"; import "jest-extended"; -import { calculateApproval, calculateProductivity } from "../../../packages/core-utils/src/delegate-calculator"; +import { calculateApproval, calculateProductivity, calculateForgedTotal } from "../../../packages/core-utils/src/delegate-calculator"; let delegate; @@ -48,4 +48,13 @@ describe("Delegate Calculator", () => { expect(calculateProductivity(delegate)).toBe(0.0); }); }); + + describe("calculateForgedTotal", () => { + it("should calculate correctly", () => { + delegate.forgedFees = new Bignum(10); + delegate.forgedRewards = new Bignum(100); + + expect(calculateForgedTotal(delegate)).toBe(110); + }); + }); }); diff --git a/__tests__/unit/core-utils/has-some-property.test.ts b/__tests__/unit/core-utils/has-some-property.test.ts new file mode 100644 index 0000000000..4667f508b4 --- /dev/null +++ b/__tests__/unit/core-utils/has-some-property.test.ts @@ -0,0 +1,23 @@ +import "jest-extended"; + +import { hasSomeProperty } from "../../../packages/core-utils/src/has-some-property"; + +let object; + +beforeEach(() => { + object = { 'property': null }; +}); + +describe("hasSomeProperty", () => { + it("should return true if the object has a given property", () => { + expect(hasSomeProperty(object, ['property'])).toBe(true); + }); + + it("should return true if the object has any of the given properties", () => { + expect(hasSomeProperty(object, ['not-present', 'property'])).toBe(true); + }); + + it("should return false if the object doesn't have a given property", () => { + expect(hasSomeProperty(object, ['not-present'])).toBe(false); + }); +}); diff --git a/packages/core-api/src/versions/2/delegates/schema.ts b/packages/core-api/src/versions/2/delegates/schema.ts index 68d43b6c70..96fcfb4ae5 100644 --- a/packages/core-api/src/versions/2/delegates/schema.ts +++ b/packages/core-api/src/versions/2/delegates/schema.ts @@ -14,6 +14,32 @@ const schemaUsername = Joi.string() .min(1) .max(20); +const schemaIntegerBetween = Joi.object() + .keys({ + from: Joi + .number() + .integer() + .min(0), + to: Joi + .number() + .integer() + .min(0), + }) + +const schemaPercentage = Joi.object() + .keys({ + from: Joi + .number() + .precision(2) + .min(0) + .max(100), + to: Joi + .number() + .precision(2) + .min(0) + .max(100), + }) + export const index: object = { query: { ...pagination, @@ -62,12 +88,26 @@ export const search: object = { }, }, payload: { + address: Joi.string() + .alphanum() + .length(34), + publicKey: Joi.string() + .hex() + .length(66), username: schemaUsername, usernames: Joi.array() .unique() .min(1) .max(config.getMilestone().activeDelegates) .items(schemaUsername), + approval: schemaPercentage, + forgedFees: schemaIntegerBetween, + forgedRewards: schemaIntegerBetween, + forgedTotal: schemaIntegerBetween, + missedBlocks: schemaIntegerBetween, + producedBlocks: schemaIntegerBetween, + productivity: schemaPercentage, + voteBalance: schemaIntegerBetween, }, }; diff --git a/packages/core-api/src/versions/2/delegates/transformer.ts b/packages/core-api/src/versions/2/delegates/transformer.ts index 6fdb040209..1ca260088f 100644 --- a/packages/core-api/src/versions/2/delegates/transformer.ts +++ b/packages/core-api/src/versions/2/delegates/transformer.ts @@ -18,7 +18,7 @@ export function transformDelegate(delegate) { forged: { fees: +delegate.forgedFees.toFixed(), rewards: +delegate.forgedRewards.toFixed(), - total: +delegate.forgedFees.plus(delegate.forgedRewards).toFixed(), + total: delegateCalculator.calculateForgedTotal(delegate), }, }; diff --git a/packages/core-database/src/repositories/delegates-business-repository.ts b/packages/core-database/src/repositories/delegates-business-repository.ts index 2f1f353be1..10ed454a62 100644 --- a/packages/core-database/src/repositories/delegates-business-repository.ts +++ b/packages/core-database/src/repositories/delegates-business-repository.ts @@ -1,5 +1,5 @@ import { Database } from "@arkecosystem/core-interfaces"; -import { delegateCalculator } from "@arkecosystem/core-utils"; +import { delegateCalculator, hasSomeProperty } from "@arkecosystem/core-utils"; import { orderBy } from "@arkecosystem/utils"; import filterRows from "./utils/filter-rows"; import limitRows from "./utils/limit-rows"; @@ -14,12 +14,34 @@ export class DelegatesBusinessRepository implements Database.IDelegatesBusinessR /** * Get all local delegates. + * @param {Object} params + * @return {Object} */ - public getLocalDelegates() { + public getLocalDelegates(params: Database.IParameters = {}) { // TODO: What's the diff between this and just calling 'allByUsername' - return this.databaseServiceProvider() + let delegates = this.databaseServiceProvider() .walletManager.allByAddress() .filter(wallet => !!wallet.username); + + const manipulators = { + approval: delegateCalculator.calculateApproval, + productivity: delegateCalculator.calculateProductivity, + forgedTotal: delegateCalculator.calculateForgedTotal, + }; + + if (hasSomeProperty(params, Object.keys(manipulators))) { + delegates = delegates.map(delegate => { + for (const [prop, method] of Object.entries(manipulators)) { + if (params.hasOwnProperty(prop)) { + delegate[prop] = method(delegate); + } + } + + return delegate; + }); + } + + return delegates; } /** @@ -40,14 +62,54 @@ export class DelegatesBusinessRepository implements Database.IDelegatesBusinessR /** * Search all delegates. - * TODO Currently it searches by username only + * TODO Search by last block * @param {Object} [params] + * @param {Number} [params.limit] - Limit the number of results + * @param {Number} [params.offset] - Skip some results + * @param {Array} [params.orderBy] - Order of the results + * @param {String} [params.address] - Search by address + * @param {String} [params.publicKey] - Search by publicKey * @param {String} [params.username] - Search by username * @param {Array} [params.usernames] - Search by usernames + * @param {Object} [params.approval] - Search by approval + * @param {Number} [params.approval.from] - Search by approval (minimum) + * @param {Number} [params.approval.to] - Search by approval (maximum) + * @param {Object} [params.forgedFees] - Search by forgedFees + * @param {Number} [params.forgedFees.from] - Search by forgedFees (minimum) + * @param {Number} [params.forgedFees.to] - Search by forgedFees (maximum) + * @param {Object} [params.forgedRewards] - Search by forgedRewards + * @param {Number} [params.forgedRewards.from] - Search by forgedRewards (minimum) + * @param {Number} [params.forgedRewards.to] - Search by forgedRewards (maximum) + * @param {Object} [params.forgedTotal] - Search by forgedTotal + * @param {Number} [params.forgedTotal.from] - Search by forgedTotal (minimum) + * @param {Number} [params.forgedTotal.to] - Search by forgedTotal (maximum) + * @param {Object} [params.missedBlocks] - Search by missedBlocks + * @param {Number} [params.missedBlocks.from] - Search by missedBlocks (minimum) + * @param {Number} [params.missedBlocks.to] - Search by missedBlocks (maximum) + * @param {Object} [params.producedBlocks] - Search by producedBlocks + * @param {Number} [params.producedBlocks.from] - Search by producedBlocks (minimum) + * @param {Number} [params.producedBlocks.to] - Search by producedBlocks (maximum) + * @param {Object} [params.productivity] - Search by productivity + * @param {Number} [params.productivity.from] - Search by productivity (minimum) + * @param {Number} [params.productivity.to] - Search by productivity (maximum) + * @param {Object} [params.voteBalance] - Search by voteBalance + * @param {Number} [params.voteBalance.from] - Search by voteBalance (minimum) + * @param {Number} [params.voteBalance.to] - Search by voteBalance (maximum) */ public search(params: Database.IParameters) { const query: any = { + exact: ["address", "publicKey"], like: ["username"], + between: [ + "approval", + "forgedFees", + "forgedRewards", + "forgedTotal", + "missedBlocks", + "producedBlocks", + "productivity", + "voteBalance", + ], }; if (params.usernames) { @@ -61,7 +123,7 @@ export class DelegatesBusinessRepository implements Database.IDelegatesBusinessR this.applyOrder(params); - let delegates = filterRows(this.getLocalDelegates(), params, query); + let delegates = filterRows(this.getLocalDelegates(params), params, query); delegates = sortEntries(params, delegates, ["rate", "asc"]); return { @@ -116,16 +178,16 @@ export class DelegatesBusinessRepository implements Database.IDelegatesBusinessR private manipulateIteratee(iteratee): any { switch (iteratee) { - case "votes": - return "voteBalance"; - case "rank": - return "rate"; + case "approval": + return delegateCalculator.calculateApproval; case "productivity": return delegateCalculator.calculateProductivity; + case "forgedTotal": + return delegateCalculator.calculateForgedTotal; + case "rank": + return "rate"; case "votes": return "voteBalance"; - case "approval": - return delegateCalculator.calculateApproval; default: return iteratee; } diff --git a/packages/core-utils/src/delegate-calculator.ts b/packages/core-utils/src/delegate-calculator.ts index dd097f74bd..9cba6a1911 100644 --- a/packages/core-utils/src/delegate-calculator.ts +++ b/packages/core-utils/src/delegate-calculator.ts @@ -45,4 +45,16 @@ function calculateProductivity(delegate) { return +(100 - missedBlocks / ((producedBlocks + missedBlocks) / 100)).toFixed(2); } -export { calculateApproval, calculateProductivity }; +/** + * Calculate the forged total of the given delegate. + * @param {Delegate} delegate + * @return {Bignum} Forged total + */ +function calculateForgedTotal(delegate) { + const forgedFees = new Bignum(delegate.forgedFees); + const forgedRewards = new Bignum(delegate.forgedRewards); + + return +forgedFees.plus(forgedRewards).toFixed(); +} + +export { calculateApproval, calculateProductivity, calculateForgedTotal }; diff --git a/packages/core-utils/src/has-some-property.ts b/packages/core-utils/src/has-some-property.ts new file mode 100644 index 0000000000..f80c66da48 --- /dev/null +++ b/packages/core-utils/src/has-some-property.ts @@ -0,0 +1,9 @@ +/** + * Check if an object has at least one of the given properties. + * @param {Object} object + * @param {Array} props + * @return {Boolean} + */ +export function hasSomeProperty(object, props): boolean { + return props.some(prop => object.hasOwnProperty(prop)); +} diff --git a/packages/core-utils/src/index.ts b/packages/core-utils/src/index.ts index a53757ecff..9424e60cd3 100644 --- a/packages/core-utils/src/index.ts +++ b/packages/core-utils/src/index.ts @@ -1,13 +1,14 @@ import { bignumify } from "./bignumify"; import { CappedSet } from "./capped-set"; -import { calculateApproval, calculateProductivity } from "./delegate-calculator"; +import { calculateApproval, calculateProductivity, calculateForgedTotal } from "./delegate-calculator"; import { formatTimestamp } from "./format-timestamp"; +import { hasSomeProperty } from "./has-some-property"; import { NSect } from "./nsect"; import { calculateRound, isNewRound } from "./round-calculator"; import { calculate } from "./supply-calculator"; -const delegateCalculator = { calculateApproval, calculateProductivity }; +const delegateCalculator = { calculateApproval, calculateProductivity, calculateForgedTotal }; const roundCalculator = { calculateRound, isNewRound }; const supplyCalculator = { calculate }; -export { CappedSet, NSect, bignumify, delegateCalculator, formatTimestamp, roundCalculator, supplyCalculator }; +export { CappedSet, NSect, bignumify, delegateCalculator, formatTimestamp, hasSomeProperty, roundCalculator, supplyCalculator };