Skip to content

Commit

Permalink
MEX-316: fixes and add resolver fields for user related info
Browse files Browse the repository at this point in the history
  • Loading branch information
dragos-rebegea committed Jul 13, 2023
1 parent 81158b1 commit 782613a
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 106 deletions.
10 changes: 10 additions & 0 deletions src/modules/auth/optional.jwt.or.native.auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { JwtOrNativeAuthGuard } from './jwt.or.native.auth.guard';

@Injectable()
export class OptionalJwtOrNativeAuthGuard extends JwtOrNativeAuthGuard {
async canActivate(context: ExecutionContext): Promise<boolean> {
await super.canActivate(context);
return true;
}
}
7 changes: 6 additions & 1 deletion src/modules/governance/governance.propose.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
import { GovernanceAbiService } from './services/governance.abi.service';
import { GovernanceProposal } from './models/governance.proposal.model';
import { ProposalVotes } from './models/proposal.votes.model';
import { GovernanceService } from './services/governance.service';

@Resolver(() => GovernanceProposal)
export class GovernanceProposalResolver {
constructor(
private readonly governanceAbi: GovernanceAbiService,
private readonly governanceService: GovernanceService,
) {
}

Expand All @@ -15,10 +17,13 @@ export class GovernanceProposalResolver {
return this.governanceAbi.proposalStatus(governanceProposal.contractAddress, governanceProposal.proposalId);
}

//votes
@ResolveField()
async votes(@Parent() governanceProposal: GovernanceProposal): Promise<ProposalVotes> {
return this.governanceAbi.proposalVotes(governanceProposal.contractAddress, governanceProposal.proposalId);
}

@ResolveField()
async hasVoted(@Parent() governanceProposal: GovernanceProposal): Promise<boolean> {
return this.governanceService.hasUserVoted(governanceProposal.contractAddress, governanceProposal.proposalId, governanceProposal.userAddress);
}
}
12 changes: 10 additions & 2 deletions src/modules/governance/governance.query.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { Args, Query, Resolver } from '@nestjs/graphql';
import { GovernanceContractsFiltersArgs } from './models/contracts.filter.args';
import { GovernanceService } from './services/governance.service';
import { GovernanceContract } from './models/governance.contract.model';
import { UseGuards } from '@nestjs/common';
import { OptionalJwtOrNativeAuthGuard } from '../auth/optional.jwt.or.native.auth.guard';
import { AuthUser } from '../auth/auth.user';
import { UserAuthResult } from '../auth/user.auth.result';

@Resolver()
export class GovernanceQueryResolver {
Expand All @@ -11,7 +15,11 @@ export class GovernanceQueryResolver {
}

@Query(() => [GovernanceContract])
async governanceContracts(@Args() filters: GovernanceContractsFiltersArgs): Promise<GovernanceContract[]> {
return this.governanceService.getGovernanceContracts(filters);
@UseGuards(OptionalJwtOrNativeAuthGuard)
async governanceContracts(
@AuthUser() user: UserAuthResult,
@Args() filters: GovernanceContractsFiltersArgs
): Promise<GovernanceContract[]> {
return this.governanceService.getGovernanceContracts(filters, user.address);
}
}
2 changes: 2 additions & 0 deletions src/modules/governance/models/governance.contract.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export class GovernanceContract {
@Field()
address: string;
@Field()
userAddress?: string;
@Field()
minEnergyForPropose: string;
@Field()
minFeeForPropose: string;
Expand Down
19 changes: 17 additions & 2 deletions src/modules/governance/models/governance.proposal.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { GovernanceAction } from './governance.action.model';
import { EsdtTokenPaymentModel } from '../../tokens/models/esdt.token.payment.model';
import { ProposalVotes } from './proposal.votes.model'; //Assuming you will create GovernanceAction model separately

export enum GovernanceProposalStatus {
None ='None',
Pending ='Pending',
Active ='Active',
Defeated ='Defeated',
DefeatedWithVeto ='DefeatedWithVeto',
Succeeded ='Succeeded',
}

@ObjectType()
export class Description {
@Field()
Expand Down Expand Up @@ -43,11 +52,17 @@ export class GovernanceProposal {
totalEnergy: string;
@Field(() => Int)
proposalStartBlock: number;
@Field()
status: string;
@Field( () => GovernanceProposalStatus)
status: GovernanceProposalStatus;
@Field( () => ProposalVotes )
votes: ProposalVotes;

// user
@Field()
userAddress?: string;
@Field()
hasVoted?: boolean;

constructor(init: Partial<GovernanceProposal>) {
Object.assign(this, init);
}
Expand Down
16 changes: 8 additions & 8 deletions src/modules/governance/models/proposal.votes.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class ProposalVotes {
@Field()
upVotes: string;
upVotes: number;
@Field()
downVotes: string;
downVotes: number;
@Field()
downVetoVotes: string;
downVetoVotes: number;
@Field()
abstainVotes: string;
abstainVotes: number;
@Field()
quorum: string;

Expand All @@ -19,10 +19,10 @@ export class ProposalVotes {

static default(): ProposalVotes {
return new ProposalVotes({
upVotes: '0',
downVotes: '0',
downVetoVotes: '0',
abstainVotes: '0',
upVotes: 0,
downVotes: 0,
downVetoVotes: 0,
abstainVotes: 0,
quorum: '0',
});
}
Expand Down
109 changes: 17 additions & 92 deletions src/modules/governance/services/governance.abi.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { GetOrSetCache } from 'src/helpers/decorators/caching.decorator';
import { CacheTtlInfo } from 'src/services/caching/cache.ttl.info';
import { ErrorLoggerAsync } from 'src/helpers/decorators/error.logger';
import { ProposalVotes } from '../models/proposal.votes.model';
import { Description, GovernanceProposal } from '../models/governance.proposal.model';
import { Description, GovernanceProposal, GovernanceProposalStatus } from '../models/governance.proposal.model';
import { GovernanceAction } from '../models/governance.action.model';
import { EsdtTokenPaymentModel } from '../../tokens/models/esdt.token.payment.model';
import { EsdtTokenPayment } from '@multiversx/sdk-exchange';
Expand Down Expand Up @@ -152,11 +152,11 @@ export class GovernanceAbiService
remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
localTtl: CacheTtlInfo.ContractState.localTtl,
})
async proposals(scAddress: string): Promise<GovernanceProposal[]> {
return await this.proposalsRaw(scAddress);
async proposals(scAddress: string, userAddress?: string): Promise<GovernanceProposal[]> {
return await this.proposalsRaw(scAddress, userAddress);
}

async proposalsRaw(scAddress: string): Promise<GovernanceProposal[]> {
async proposalsRaw(scAddress: string, userAddress?: string): Promise<GovernanceProposal[]> {
const contract = await this.mxProxy.getGovernanceSmartContract(scAddress);
const interaction = contract.methodsExplicit.getProposals();
const response = await this.getGenericData(interaction);
Expand All @@ -165,13 +165,14 @@ export class GovernanceAbiService
const actions = proposal.actions?.map((action: any) => {
return new GovernanceAction({
arguments: action.arguments.toString().split(','),
destAddress: action.dest_address.toString(),
destAddress: action.dest_address.bech32(),
functionName: action.function_name.toString(),
gasLimit: action.gas_limit.toNumber(),
});
});
return new GovernanceProposal({
contractAddress: scAddress,
userAddress,
proposalId: proposal.proposal_id.toNumber(),
proposer: proposal.proposer.bech32(),
actions,
Expand All @@ -195,20 +196,16 @@ export class GovernanceAbiService
remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
localTtl: CacheTtlInfo.ContractState.localTtl,
})
async userVotedProposals(scAddress: string, userAddress: string): Promise<GovernanceProposal[]> {
async userVotedProposals(scAddress: string, userAddress: string): Promise<number[]> {
return await this.userVotedProposalsRaw(scAddress, userAddress);
}

async userVotedProposalsRaw(scAddress: string, userAddress: string): Promise<GovernanceProposal[]> {
async userVotedProposalsRaw(scAddress: string, userAddress: string): Promise<number[]> {
const contract = await this.mxProxy.getGovernanceSmartContract(scAddress);
const interaction = contract.methods.getUserVotedProposals([userAddress]);
const response = await this.getGenericData(interaction);

return response.firstValue.valueOf().map((proposal: any) => {
return new GovernanceProposal({

});
});
return response.firstValue.valueOf();
}

@ErrorLoggerAsync({className: GovernanceAbiService.name})
Expand All @@ -231,10 +228,10 @@ export class GovernanceAbiService
}
const votes = response.firstValue.valueOf();
return new ProposalVotes({
upVotes: votes.up_votes.toFixed(),
downVotes: votes.down_votes.toFixed(),
downVetoVotes: votes.down_veto_votes.toFixed(),
abstainVotes: votes.abstain_votes.toFixed(),
upVotes: votes.up_votes.toNumber(),
downVotes: votes.down_votes.toNumber(),
downVetoVotes: votes.down_veto_votes.toNumber(),
abstainVotes: votes.abstain_votes.toNumber(),
quorum: votes.quorum.toFixed()
});
}
Expand All @@ -245,91 +242,19 @@ export class GovernanceAbiService
remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
localTtl: CacheTtlInfo.ContractState.localTtl,
})
async proposalStatus(scAddress: string, proposalId: number): Promise<string> {
async proposalStatus(scAddress: string, proposalId: number): Promise<GovernanceProposalStatus> {
return await this.proposalStatusRaw(scAddress, proposalId);
}

async proposalStatusRaw(scAddress: string, proposalId: number): Promise<string> {
async proposalStatusRaw(scAddress: string, proposalId: number): Promise<GovernanceProposalStatus> {
const contract = await this.mxProxy.getGovernanceSmartContract(scAddress);
const interaction = contract.methods.getProposalStatus([proposalId]);
const response = await this.getGenericData(interaction);

return response.firstValue.valueOf().name;
const valueAsString = response.firstValue.valueOf().toString();
return GovernanceProposalStatus[valueAsString as keyof typeof GovernanceProposalStatus];;
}

// TODO: decide if remove or not
// @ErrorLoggerAsync({className: GovernanceAbiService.name})
// @GetOrSetCache({
// baseKey: 'governance',
// remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
// localTtl: CacheTtlInfo.ContractState.localTtl,
// })
// async currentQuorum(): Promise<string> {
// return await this.currentQuorumRaw();
// }
//
// async currentQuorumRaw(): Promise<string> {
// const contract = await this.mxProxy.getGovernanceSmartContract();
// const interaction = contract.methods.getCurrentQuorum();
// const response = await this.getGenericData(interaction);
//
// return response.firstValue.valueOf().toFixed();
// }

// @ErrorLoggerAsync({className: GovernanceAbiService.name})
// @GetOrSetCache({
// baseKey: 'governance',
// remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
// localTtl: CacheTtlInfo.ContractState.localTtl,
// })
// async proposer(proposalId: number): Promise<string> {
// return await this.proposerRaw(proposalId);
// }
//
// async proposerRaw(proposalId: number): Promise<string> {
// const contract = await this.mxProxy.getGovernanceSmartContract();
// const interaction = contract.methods.getProposer([proposalId]);
// const response = await this.getGenericData(interaction);
//
// return response.firstValue.valueOf();
// }

// @ErrorLoggerAsync({className: GovernanceAbiService.name})
// @GetOrSetCache({
// baseKey: 'governance',
// remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
// localTtl: CacheTtlInfo.ContractState.localTtl,
// })
// async proposalDescription(proposalId: number): Promise<string> {
// return await this.proposalDescriptionRaw(proposalId);
// }
//
// async proposalDescriptionRaw(proposalId: number): Promise<string> {
// const contract = await this.mxProxy.getGovernanceSmartContract();
// const interaction = contract.methods.getProposalDescription([proposalId]);
// const response = await this.getGenericData(interaction);
//
// return response.firstValue.valueOf();
// }
//
// @ErrorLoggerAsync({className: GovernanceAbiService.name})
// @GetOrSetCache({
// baseKey: 'governance',
// remoteTtl: CacheTtlInfo.ContractState.remoteTtl,
// localTtl: CacheTtlInfo.ContractState.localTtl,
// })
// async proposalActions(proposalId: number): Promise<string[]> {
// return await this.proposalActionsRaw(proposalId);
// }
//
// async proposalActionsRaw(proposalId: number): Promise<string[]> {
// const contract = await this.mxProxy.getGovernanceSmartContract();
// const interaction = contract.methods.getProposalActions([proposalId]);
// const response = await this.getGenericData(interaction);
//
// return response.values.map((value) => value.valueOf());
// }

@ErrorLoggerAsync({className: GovernanceAbiService.name})
@GetOrSetCache({
baseKey: 'governance',
Expand Down
17 changes: 16 additions & 1 deletion src/modules/governance/services/governance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@ import { Injectable } from '@nestjs/common';
import { GovernanceContract } from '../models/governance.contract.model';
import { governanceContractsAddresses } from '../../../utils/governance';
import { GovernanceContractsFiltersArgs } from '../models/contracts.filter.args';
import { GovernanceAbiService } from './governance.abi.service';

@Injectable()
export class GovernanceService {
async getGovernanceContracts(filters: GovernanceContractsFiltersArgs): Promise<GovernanceContract[]> {
constructor(
private readonly governanceAbi: GovernanceAbiService,
) {
}
async getGovernanceContracts(filters: GovernanceContractsFiltersArgs, userAddress?: string): Promise<GovernanceContract[]> {
const governanceAddresses = governanceContractsAddresses();

const governance: GovernanceContract[] = [];
for (const address of governanceAddresses) {
governance.push(
new GovernanceContract({
userAddress,
address,
}),
);
}

return governance;
}

async hasUserVoted(contractAddress: string, proposalId: number, userAddress?: string): Promise<boolean> {
if (!userAddress) {
return false;
}

const userVotedProposals = await this.governanceAbi.userVotedProposals(contractAddress, userAddress);
return userVotedProposals.includes(proposalId);
}
}

0 comments on commit 782613a

Please sign in to comment.