Skip to content
379 changes: 364 additions & 15 deletions __tests__/integration/core-api/v2/handlers/delegates.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand Down
11 changes: 10 additions & 1 deletion __tests__/unit/core-utils/delegate-calculator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
});
});
});
23 changes: 23 additions & 0 deletions __tests__/unit/core-utils/has-some-property.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
40 changes: 40 additions & 0 deletions packages/core-api/src/versions/2/delegates/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
}

/**
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down
14 changes: 13 additions & 1 deletion packages/core-utils/src/delegate-calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
9 changes: 9 additions & 0 deletions packages/core-utils/src/has-some-property.ts
Original file line number Diff line number Diff line change
@@ -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));
}
7 changes: 4 additions & 3 deletions packages/core-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -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 };