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

Implement voteOnProposal command #220

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,128 changes: 7,128 additions & 0 deletions coverage/coverage-final.json

Large diffs are not rendered by default.

183 changes: 183 additions & 0 deletions src/app/modules/dexGovernance/commands/voteOnPorposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-non-null-assertion */

/*
* Copyright © 2022 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 {
BaseCommand,
CommandExecuteContext,
CommandVerifyContext,
VerificationResult,
VerifyStatus,
} from 'lisk-sdk';

import { PoSEndpoint } from 'lisk-framework/dist-node/modules/pos/endpoint';
import { PrefixedStateReadWriter } from 'lisk-framework/dist-node/state_machine/prefixed_state_read_writer';
import {
createTransientModuleEndpointContext,
InMemoryPrefixedStateDB,
} from 'lisk-framework/dist-node/testing';
import { MIN_SINT32 } from '@liskhq/lisk-validator';
import { MethodContext } from 'lisk-framework/dist-node/state_machine/method_context';
import { ProposalsStore, VotesStore } from '../stores';
import { sha256 } from '../../dexRewards/constants';

import { numberToQ96, q96ToBytes } from '../../dex/utils/q96';
import { ProposalVotedEvent } from '../events';
import {
COMMAND_ID_VOTE_ON_PORPOSAL,
LENGTH_ADDRESS,
MAX_NUM_RECORDED_VOTES,
PROPOSAL_STATUS_ACTIVE,
} from '../constants';
import { Vote, voteOnProposalParamsData } from '../types';
import { addVotes } from '../utils/auxiliaryFunctions';

export class VoteOnPorposalCommand extends BaseCommand {
public id = COMMAND_ID_VOTE_ON_PORPOSAL;
private _posEndpoint!: PoSEndpoint;
private _methodContext!: MethodContext;

public init({ posEndpoint, methodContext }): void {
this._posEndpoint = posEndpoint;
this._methodContext = methodContext;
}

// eslint-disable-next-line @typescript-eslint/require-await
public async verify(
ctx: CommandVerifyContext<voteOnProposalParamsData>,
): Promise<VerificationResult> {
const proposalStoreInfo = this.stores.get(ProposalsStore);

const result = Buffer.alloc(4);
result.writeUInt32BE(ctx.params.proposalIndex, 0);
const proposalsStoreDate = await proposalStoreInfo.get(this._methodContext, result);

if (!proposalsStoreDate) {
return {
status: VerifyStatus.FAIL,
error: new Error('Proposal does not exist'),
};
}
if (ctx.params.decision > 2) {
return {
status: VerifyStatus.FAIL,
error: new Error('Decision does not exist'),
};
}
if (
(await proposalStoreInfo.get(this._methodContext, result)).status !== PROPOSAL_STATUS_ACTIVE
) {
return {
status: VerifyStatus.FAIL,
error: new Error('Proposal does not exist'),
};
}
return {
status: VerifyStatus.OK,
};
}

public async execute(ctx: CommandExecuteContext<voteOnProposalParamsData>): Promise<void> {
const stateStore = new PrefixedStateReadWriter(new InMemoryPrefixedStateDB());
const methodContext = ctx.getMethodContext();
const votesStoreInfo: VotesStore = this.stores.get(VotesStore);
let smallestproposalIndex = 0;
let smallestproposalValue = MIN_SINT32;
let previousSavedStorescheck = false;
const senderAddress = sha256(ctx.transaction.senderPublicKey.toString()).slice(
0,
LENGTH_ADDRESS,
);

const moduleEndpointContext = createTransientModuleEndpointContext({
stateStore,
params: { address: senderAddress },
});

const index = ctx.params.proposalIndex;

const stakedAmount = (await this._posEndpoint.getLockedStakedAmount(moduleEndpointContext))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems it was already added as a method here, I think it would make more sense to use that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emiliolisk I think I am already using the method you mentioned here. I have an import at the top of file as well:
import { PoSEndpoint } from 'lisk-framework/dist-node/modules/pos/endpoint';
Can you please give a look again. Thanks

.amount;

if (!(await votesStoreInfo.get(methodContext, senderAddress))) {
votesStoreInfo.set(methodContext, senderAddress, { voteInfos: [] });
}

const newVoteInfo: Vote = {
voteInfos: [
{
proposalIndex: index,
decision: ctx.params.decision,
amount: BigInt(stakedAmount),
},
],
};
const { voteInfos } = await votesStoreInfo.get(methodContext, senderAddress);

for (let itr = 0; itr < voteInfos.length; itr += 1) {
if (voteInfos[itr]?.proposalIndex === index) {
addVotes(
methodContext,
this.stores.get(ProposalsStore),
index,
voteInfos[itr]!.amount,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be -amount, i.e. we deduce the previously cast votes from the total result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeyshemyakov true replaces this line with:
-voteStoreInfos[itr]!.amount,

voteInfos[itr]!.decision,
);
votesStoreInfo.setKey(methodContext, [senderAddress], newVoteInfo);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand the code correctly, there is an error here. I think you set the votes store entry to newVoteInfo, but voteInfos is an array that should mostly stay the same i.e. the entries for this address for other votes should not be deleted. You should reassign voteInfos[itr] = newVoteInfo.voteInfos[0].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeyshemyakov
Updated this and replaced.
votesStoreInfo.setKey(methodContext, [senderAddress], newVoteInfo);
=>
voteInfos[itr] = newVoteInfo.voteInfos[0];

previousSavedStorescheck = true;
}
if (smallestproposalValue > voteInfos[itr]!.proposalIndex) {
smallestproposalValue = voteInfos[itr]!.proposalIndex;
smallestproposalIndex = itr;
}
}

if (
!previousSavedStorescheck &&
(await votesStoreInfo.get(methodContext, senderAddress)).voteInfos.length <

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small suggestion: you could use voteInfos.length here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeyshemyakov updated it to your recommendation.

MAX_NUM_RECORDED_VOTES
) {
(await votesStoreInfo.getKey(methodContext, [senderAddress])).voteInfos.push(
newVoteInfo.voteInfos[0],
);
} else if (!previousSavedStorescheck) {
[(await votesStoreInfo.get(methodContext, senderAddress)).voteInfos[smallestproposalIndex]] =
newVoteInfo.voteInfos;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be newVoteInfo.voteInfos[0].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeyshemyakov agree and that is why changed it to:
[voteStoreInfos[smallestproposalIndex]] = newVoteInfo.voteInfos;
it is doing array destructing in TS and it is equal to:
voteStoreInfos[smallestproposalIndex] = newVoteInfo.voteInfos[0];

}

addVotes(
methodContext,
this.stores.get(ProposalsStore),
index,
BigInt(stakedAmount),
ctx.params.decision,
);
this.events.get(ProposalVotedEvent).add(
methodContext,
{
index,
voterAddress: senderAddress,
decision: ctx.params.decision,
amount: BigInt(stakedAmount),
},
[senderAddress, q96ToBytes(numberToQ96(BigInt(index)))],
true,
);
}
}
2 changes: 2 additions & 0 deletions src/app/modules/dexGovernance/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ export const DECISION_NO = 1; // Code for the vote decision "No".
export const DECISION_PASS = 2; // Code for the vote decision "Pass".

export const defaultConfig = {};

export const COMMAND_ID_VOTE_ON_PORPOSAL = Buffer.from('0002', 'hex');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have command IDs anymore, instead we have command names:

COMMAND_VOTE_ON_PROPOSAL | string | "voteOnProposal" | Command name of the vote command.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeyshemyakov chnaged it to:

export const COMMAND_VOTE_ON_PORPOSAL = 'voteOnProposal';

2 changes: 1 addition & 1 deletion src/app/modules/dexGovernance/events/proposalVoted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const ProposalVotedEventSchema = {
},
voterAddress: {
dataType: 'bytes',
length: LENGTH_ADDRESS,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The address of the voter should be exactly 20 bytes (i.e. LENGTH_ADDRESS). So the previous version was correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergeyshemyakov if change it to length it gives a schema validator error
Rejected to value: [Error: Lisk validator found 1 error[s]: strict mode: unknown keyword: "length"]

maxLength: LENGTH_ADDRESS,
fieldNumber: 2,
},
decision: {
Expand Down
35 changes: 33 additions & 2 deletions src/app/modules/dexGovernance/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { BaseCommand, BaseModule, ModuleMetadata, PoSMethod, TokenMethod } from 'lisk-sdk';
import { PoSEndpoint } from 'lisk-framework/dist-node/modules/pos/endpoint';
import {
BaseCommand,
BaseModule,
ModuleInitArgs,
ModuleMetadata,
PoSMethod,
TokenMethod,
utils,
} from 'lisk-sdk';
import { ModuleConfig } from '../dex/types';
import { VoteOnPorposalCommand } from './commands/voteOnPorposal';
import { defaultConfig } from './constants';

import { DexGovernanceEndpoint } from './endpoint';
import {
Expand Down Expand Up @@ -41,8 +53,16 @@ export class DexGovernanceModule extends BaseModule {
public method = new DexGovernanceMethod(this.stores, this.events);
public _tokenMethod!: TokenMethod;
public _posMethod!: PoSMethod;
public _moduleConfig!: ModuleConfig;
public _posEndpoint!: PoSEndpoint;
public _methodContext!: PoSEndpoint;

private readonly __createvoteOnPorposalCommand = new VoteOnPorposalCommand(
this.stores,
this.events,
);

public commands = [];
public commands = [this.__createvoteOnPorposalCommand];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the typos to "Proposal" and add proper caps/names where needed, for example this would be this._voteOnProposalCommand

Copy link
Contributor Author

@Irfan-Personal Irfan-Personal Feb 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it was not aligned with command name so change it to
this.__createvoteOnPorposalCommand => __voteOnPorposalCommand at the required places in code.
I will push the code soon.


public constructor() {
super();
Expand Down Expand Up @@ -104,4 +124,15 @@ export class DexGovernanceModule extends BaseModule {
this._tokenMethod = tokenMethod;
this._posMethod = posMethod;
}

// eslint-disable-next-line @typescript-eslint/require-await
public async init(args: ModuleInitArgs) {
const { moduleConfig } = args;
this._moduleConfig = utils.objects.mergeDeep({}, defaultConfig, moduleConfig) as ModuleConfig;

this.__createvoteOnPorposalCommand.init({
posEndpoint: this._posEndpoint,
methodContext: this._methodContext,
});
}
}
16 changes: 16 additions & 0 deletions src/app/modules/dexGovernance/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,19 @@ export const getIndexStoreResponseSchema = {
},
},
};

export const voteOnProposalParamsSchema = {
$id: '/dexGovernance/voteOnProposalParamsSchema',
type: 'object',
required: ['proposalIndex', 'decision'],
properties: {
proposalIndex: {
dataType: 'uint32',
fieldNumber: 1,
},
decision: {
dataType: 'uint32',
fieldNumber: 2,
},
},
};
5 changes: 5 additions & 0 deletions src/app/modules/dexGovernance/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ export interface Index {
nextOutcomeCheckIndex: number;
nextQuorumCheckIndex: number;
}

export interface voteOnProposalParamsData {
proposalIndex: number;
decision: number;
}
Loading