Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Add reward module endpoint - Closes #6689 #6751

Merged
31 changes: 31 additions & 0 deletions framework/src/modules/reward/calculate_reward.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright © 2021 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/

import { CalculateDefaultRewardArgs } from './types';

export const calculateDefaultReward = (args: CalculateDefaultRewardArgs): bigint => {
const { height, distance, offset, brackets } = args;

if (height < offset) {
return BigInt(0);
}

const rewardDistance = Math.floor(distance);
const location = Math.trunc((height - offset) / rewardDistance);
const lastBracket = brackets[brackets.length - 1];

const bracket = location > brackets.length - 1 ? brackets.lastIndexOf(lastBracket) : location;

return brackets[bracket];
};
36 changes: 35 additions & 1 deletion framework/src/modules/reward/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { ModuleEndpointContext } from '../..';
import { BaseEndpoint } from '../base_endpoint';
import { calculateDefaultReward } from './calculate_reward';
import { DefaultReward, EndpointInitArgs } from './types';

export class RewardEndpoint extends BaseEndpoint {}
export class RewardEndpoint extends BaseEndpoint {
private _brackets!: ReadonlyArray<bigint>;
private _offset!: number;
private _distance!: number;

public init(args: EndpointInitArgs) {
this._brackets = args.config.brackets;
this._offset = args.config.offset;
this._distance = args.config.distance;
}

public getDefaultRewardAtHeight(ctx: ModuleEndpointContext): DefaultReward {
const { height } = ctx.params;

if (typeof height !== 'number') {
throw new Error('Parameter height must be a number.');
}

if (height < 0) {
throw new Error('Parameter height cannot be smaller than 0.');
}

const reward = calculateDefaultReward({
height,
brackets: this._brackets,
distance: this._distance,
offset: this._offset,
});

return { reward: reward.toString() };
}
}
8 changes: 8 additions & 0 deletions framework/src/modules/reward/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ export class RewardModule extends BaseModule {
const { moduleConfig } = args;
this._moduleConfig = (moduleConfig as unknown) as ModuleConfig;
this._tokenIDReward = this._moduleConfig.tokenIDReward;

this.endpoint.init({
config: {
brackets: this._moduleConfig.brackets.map(bracket => BigInt(bracket)),
offset: this._moduleConfig.offset,
distance: this._moduleConfig.distance,
},
});
}

// eslint-disable-next-line @typescript-eslint/require-await
Expand Down
17 changes: 16 additions & 1 deletion framework/src/modules/reward/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ export const configSchema = {
},
required: ['chainID', 'localID'],
},
offset: {
type: 'integer',
minimum: 1,
},
distance: {
type: 'integer',
minimum: 1,
},
brackets: {
type: 'array',
items: {
type: 'string',
format: 'uint64',
},
},
},
required: ['tokenIDReward'],
required: ['tokenIDReward', 'offset', 'distance', 'brackets'],
};
22 changes: 22 additions & 0 deletions framework/src/modules/reward/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export interface TokenIDReward {

export interface ModuleConfig {
tokenIDReward: TokenIDReward;
brackets: ReadonlyArray<string>;
offset: number;
distance: number;
}

export interface TokenAPI {
Expand All @@ -44,3 +47,22 @@ export interface RandomAPI {
export interface LiskBFTAPI {
impliesMaximalPrevotes(apiContext: APIContext, blockHeader: BlockHeader): Promise<boolean>;
}

export interface DefaultReward {
reward: string;
}

export interface EndpointInitArgs {
config: {
brackets: ReadonlyArray<bigint>;
offset: number;
distance: number;
};
}

export interface CalculateDefaultRewardArgs {
brackets: ReadonlyArray<bigint>;
offset: number;
distance: number;
height: number;
}
103 changes: 103 additions & 0 deletions framework/test/unit/modules/reward/endpoint.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright © 2021 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/
import { Logger } from '../../../../src/logger';
import { RewardModule } from '../../../../src/modules/reward';
import { fakeLogger } from '../../../utils/node';

describe('RewardModuleEndpoint', () => {
const genesisConfig: any = {};
const moduleConfig: any = {
distance: 3000000,
offset: 2160,
brackets: [
BigInt('500000000'), // Initial Reward
BigInt('400000000'), // Milestone 1
BigInt('300000000'), // Milestone 2
BigInt('200000000'), // Milestone 3
BigInt('100000000'), // Milestone 4
],
tokenIDReward: { chainID: 0, localID: 0 },
};
const generatorConfig: any = {};

const logger: Logger = fakeLogger;
let rewardModule: RewardModule;

beforeAll(async () => {
rewardModule = new RewardModule();
await rewardModule.init({ genesisConfig, moduleConfig, generatorConfig });
rewardModule.addDependencies(
{ mint: jest.fn() } as any,
{ isValidSeedReveal: jest.fn() } as any,
{ impliesMaximalPrevotes: jest.fn() } as any,
);
});

const { brackets, offset, distance } = moduleConfig as {
brackets: ReadonlyArray<bigint>;
offset: number;
distance: number;
};

for (const [index, rewardFromConfig] of Object.entries(brackets)) {
const nthBracket = +index;
const currentHeight = offset + nthBracket * distance;
// eslint-disable-next-line no-loop-func
it(`should getDefaultRewardAtHeight work for the ${nthBracket}th bracket`, () => {
const rewardFromEndpoint = rewardModule.endpoint.getDefaultRewardAtHeight({
getStore: jest.fn(),
logger,
params: {
height: currentHeight,
},
});
expect(rewardFromEndpoint).toEqual({ reward: rewardFromConfig.toString() });
});
}

it('should getDefaultRewardAtHeight work for the height below offset', () => {
const rewardFromEndpoint = rewardModule.endpoint.getDefaultRewardAtHeight({
getStore: jest.fn(),
logger,
params: {
height: offset - 1,
},
});
expect(rewardFromEndpoint).toEqual({ reward: '0' });
});

it('should throw an error when parameter height is not a number', () => {
expect(() =>
rewardModule.endpoint.getDefaultRewardAtHeight({
getStore: jest.fn(),
logger,
params: {
height: 'Not a number',
},
}),
).toThrow('Parameter height must be a number.');
});

it('should throw an error when parameter height is below 0', () => {
expect(() =>
rewardModule.endpoint.getDefaultRewardAtHeight({
getStore: jest.fn(),
logger,
params: {
height: -1,
},
}),
).toThrow('Parameter height cannot be smaller than 0.');
});
});
49 changes: 49 additions & 0 deletions framework/test/unit/modules/reward/reward_module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright © 2021 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/
import { RewardModule } from '../../../../src/modules/reward';

describe('RewardModule', () => {
const genesisConfig: any = {};
const moduleConfig: any = {
distance: 3000000,
offset: 2160,
brackets: [
BigInt('500000000'), // Initial Reward
BigInt('400000000'), // Milestone 1
BigInt('300000000'), // Milestone 2
BigInt('200000000'), // Milestone 3
BigInt('100000000'), // Milestone 4
],
tokenIDReward: { chainID: 0, localID: 0 },
};
const generatorConfig: any = {};

let rewardModule: RewardModule;

beforeAll(async () => {
rewardModule = new RewardModule();
await rewardModule.init({ genesisConfig, moduleConfig, generatorConfig });
rewardModule.addDependencies(
{ mint: jest.fn() } as any,
{ isValidSeedReveal: jest.fn() } as any,
{ impliesMaximalPrevotes: jest.fn() } as any,
);
});

describe('init', () => {
it('should set the moduleConfig property', () => {
expect(rewardModule['_moduleConfig']).toEqual(moduleConfig);
});
});
});