Skip to content

Commit

Permalink
add dao module in tfchain client and tfgrid client (#1089)
Browse files Browse the repository at this point in the history
* add dao module in tfchain client and tfgrid client

* add dao module

* add DaoVoteModel

* add dao to tfgrid modules

* add dao vote and get scripts

* add vote to tfchain module in gridclient

* Update config.json

* fix naming

* remove type any

* remove unnecessary file

* remove dao as it is on parent class

* remove unnecessary import

* fix types

* fix farmId type

* remove hex2a method

* remove nonce

* add init in vote script

* fix hash type, add min(1) to farmId

* remove nowblock any type

* change hash function parameter

* add return types

* add patchExtrinsic

* fix hashes type

* update types casting, add apply() to vote call
  • Loading branch information
amiraabouhadid authored Sep 24, 2023
1 parent 885bada commit a1a475e
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 2 deletions.
10 changes: 10 additions & 0 deletions packages/grid_client/scripts/get_dao_proposals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getClient } from "./client_loader";

async function main() {
const grid3 = await getClient();
const proposals = await grid3.dao.get();
console.log(proposals);
await grid3.disconnect();
}

main();
17 changes: 17 additions & 0 deletions packages/grid_client/scripts/tfchain/vote_dao_proposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getClient } from "../client_loader";

async function main() {
const grid3 = await getClient();
await grid3.tfchain.init({ name: "harby", secret: grid3.clientOptions.mnemonic });
await grid3.tfchain.vote({
name: "harby",
address: "5FWW1F7XHaiRgPEqJdkv9nVgz94AVKXkTKNyfbLcY4rqpaNM",
farmId: 246,
hash: "0xa539b59dcf7ba10764a49c9effb88aea400d3c20f0071c3b85494423079757fe",
approve: true,
});

await grid3.disconnect();
}

main();
1 change: 1 addition & 0 deletions packages/grid_client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class GridClient {
farmerbot: modules.farmerbot;
farms: modules.farms;
networks: modules.networks;
dao: modules.dao;
bridge: modules.bridge;
modules: string[] = [];

Expand Down
22 changes: 22 additions & 0 deletions packages/grid_client/src/modules/dao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { TFClient } from "../clients/tf-grid/client";
import { GridClientConfig } from "../config";
import { expose } from "../helpers/expose";
import { validateInput } from "../helpers/validator";
import { DaoVoteModel } from "./models";
class Dao {
client: TFClient;
constructor(config: GridClientConfig) {
this.client = new TFClient(config.substrateURL, config.mnemonic, config.storeSecret, config.keypairType);
}
@expose
@validateInput
async get() {
return await this.client.dao.get();
}
@expose
@validateInput
async vote(options: DaoVoteModel) {
return (await this.client.dao.vote(options)).apply();
}
}
export { Dao as dao };
1 change: 1 addition & 0 deletions packages/grid_client/src/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./k8s";
export * from "./dao";
export * from "./machines";
export * from "./models";
export * from "./zdb";
Expand Down
17 changes: 15 additions & 2 deletions packages/grid_client/src/modules/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,12 @@ class KVStoreRemoveModel {
class KVStoreBatchRemoveModel {
@Expose() @ArrayNotEmpty() @IsString({ each: true }) keys: string[];
}

class DaoVoteModel {
@Expose() @IsString() @IsNotEmpty() address: string;
@Expose() @IsInt() @IsNotEmpty() @Min(1) farmId: number;
@Expose() @IsBoolean() approve: boolean;
@Expose() @IsString() @IsNotEmpty() hash: string;
}
class BalanceGetModel {
@Expose() @IsString() @IsNotEmpty() address: string;
}
Expand Down Expand Up @@ -423,7 +428,13 @@ class TfchainWalletInitModel {
class TfchainWalletBalanceByAddressModel {
@Expose() @IsString() @IsNotEmpty() address: string;
}

class TfchainDaoVoteModel {
@Expose() @IsString() @IsNotEmpty() @IsAlphanumeric() @MaxLength(NameLength) name: string;
@Expose() @IsString() @IsNotEmpty() address: string;
@Expose() @IsInt() @IsNotEmpty() @Min(1) farmId: number;
@Expose() @IsBoolean() approve: boolean;
@Expose() @IsString() @IsNotEmpty() hash: string;
}
class TfchainWalletTransferModel {
@Expose() @IsString() @IsNotEmpty() @IsAlphanumeric() @MaxLength(NameLength) name: string;
@Expose() @IsString() @IsNotEmpty() address_dest: string;
Expand Down Expand Up @@ -665,6 +676,7 @@ export {
AlgorandCreateTransactionModel,
AlgorandTransferModel,
DiskModel,
DaoVoteModel,
NetworkModel,
MachineModel,
MachinesModel,
Expand Down Expand Up @@ -740,6 +752,7 @@ export {
TfchainWalletBalanceByAddressModel,
TfchainWalletTransferModel,
TfchainCreateModel,
TfchainDaoVoteModel,
WalletMessageSignModel,
BlockchainCreateModel,
BlockchainCreateResultModel,
Expand Down
20 changes: 20 additions & 0 deletions packages/grid_client/src/modules/tfchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
BlockchainListResultModel,
BlockchainSignModel,
TfchainCreateModel,
TfchainDaoVoteModel,
TfchainWalletBalanceByAddressModel,
TfchainWalletInitModel,
TfchainWalletTransferModel,
Expand Down Expand Up @@ -221,6 +222,25 @@ class TFChain implements blockchainInterface {
throw Error(`Could not complete transfer transaction: ${e}`);
}
}
@expose
@validateInput
async vote(options: TfchainDaoVoteModel) {
const mnemonics = await this.getMnemonics(options.name);
const sourceClient = new TFClient(this.substrateURL, mnemonics, this.storeSecret, this.keypairType);
await sourceClient.connect();
try {
await (
await sourceClient.dao.vote({
address: options.address,
farmId: options.farmId,
hash: options.hash,
approve: options.approve,
})
).apply();
} catch (e) {
throw Error(`Could not complete transfer transaction: ${e}`);
}
}

@expose
@validateInput
Expand Down
4 changes: 4 additions & 0 deletions packages/tfchain_client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { validateMnemonic } from "bip39";

import { Balances, QueryBalances } from "./balances";
import { Contracts, QueryContracts } from "./contracts";
import { Dao, QueryDao } from "./dao";
import { QueryFarms } from "./farms";
import { KVStore } from "./kvstore";
import { Nodes, QueryNodes } from "./nodes";
Expand Down Expand Up @@ -44,6 +45,7 @@ class QueryClient {
api: ApiPromise;
contracts: QueryContracts = new QueryContracts(this);
balances: QueryBalances = new QueryBalances(this);
dao: QueryDao = new QueryDao(this);
farms: QueryFarms = new QueryFarms(this);
tftBridge: QueryBridge = new QueryBridge(this);
tftPrice: QueryTFTPrice = new QueryTFTPrice(this);
Expand Down Expand Up @@ -228,8 +230,10 @@ class Client extends QueryClient {
termsAndConditions: TermsAndConditions = new TermsAndConditions(this);
kvStore: KVStore = new KVStore(this);
twins: Twins = new Twins(this);
dao: Dao = new Dao(this);
tftBridge: Bridge = new Bridge(this);


declare url: string;
mnemonicOrSecret?: string;
keypairType: KeypairType;
Expand Down
151 changes: 151 additions & 0 deletions packages/tfchain_client/src/dao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import moment from "moment";

import { Client, QueryClient } from "./client";
import { checkConnection } from "./utils";
interface Proposal {
threshold: number;
ayes: AyesAndNayes[];
nayes: AyesAndNayes[];
vetos: number;
end: moment.Moment;
hash: string;
action: string;
description: string;
link: string;
ayesProgress: number;
nayesProgress: number;
}
interface AyesAndNayes {
farmId: number;
weight: number;
}
interface ProposalRemark {
args: { remark: string };
}
interface DaoProposal {
description: string;
link: string;
}
interface ProposalVotes {
ayes: AyesAndNayes[];
nays: AyesAndNayes[];
threshold: number;
vetos: number;
end: number;
}
export interface Proposals {
active: Proposal[];
inactive: Proposal[];
}
export interface DaoVoteOptions {
address: string;
farmId: number;
hash: string;
approve: boolean;
}

class QueryDao {
constructor(public client: QueryClient) {
this.client = client;
}

@checkConnection
async get(): Promise<Proposals> {
const hashesJson = await this.client.api.query.dao.proposalList();
const hashes = hashesJson.toPrimitive() as string[];
const activeProposals: Proposal[] = [];
const inactiveProposals: Proposal[] = [];
for await (const hash of hashes) {
const daoProposal = await this.getDaoProposal(hash);
const proposal = await this.getProposal(hash);
const proposalVotes = await this.getProposalVotes(hash);

const nowBlock: number = +(await this.client.api.query.system.number());
const timeUntilEnd = (proposalVotes.end - nowBlock) * 6;
if (proposal && daoProposal) {
if (proposalVotes.end < nowBlock) {
inactiveProposals.push({
threshold: proposalVotes.threshold,
ayes: proposalVotes.ayes, //[{farmId: number, weight: number}]
nayes: proposalVotes.nays,
vetos: proposalVotes.vetos,
end: moment().add(timeUntilEnd, "second"),
hash: hash,
action: proposal.args.remark,
description: daoProposal.description,
link: daoProposal.link,
ayesProgress: this.getProgress(proposalVotes.ayes, proposalVotes.nays, true),
nayesProgress: this.getProgress(proposalVotes.ayes, proposalVotes.nays, false),
});
} else {
activeProposals.push({
threshold: proposalVotes.threshold,
ayes: proposalVotes.ayes, //[{farmId: number, weight: number}]
nayes: proposalVotes.nays,
vetos: proposalVotes.vetos,
end: moment().add(timeUntilEnd, "second"),
hash: hash,
action: proposal.args.remark,
description: daoProposal.description,
link: daoProposal.link,
ayesProgress: this.getProgress(proposalVotes.ayes, proposalVotes.nays, true),
nayesProgress: this.getProgress(proposalVotes.ayes, proposalVotes.nays, false),
});
}
}
}
return { active: activeProposals, inactive: inactiveProposals };
}
private getVotesWithWeights(votes: AyesAndNayes[]): number {
return votes.reduce((total: number, vote: AyesAndNayes) => {
return total + vote.weight;
}, 0);
}
private getProgress(ayes: AyesAndNayes[], nayes: AyesAndNayes[], typeAye: boolean): number {
const totalAyeWeight = ayes ? this.getVotesWithWeights(ayes) : 0;
const totalNayeWeight = nayes ? this.getVotesWithWeights(nayes) : 0;
const total = totalAyeWeight + totalNayeWeight;
if (total > 0) {
if (typeAye) {
return (totalAyeWeight / total) * 100;
}
return (totalNayeWeight / total) * 100;
}
return 0;
}
@checkConnection
private async getDaoProposal(hash: string): Promise<DaoProposal> {
const proposalJson = await this.client.api.query.dao.proposals(hash);
const proposal: DaoProposal = proposalJson.toPrimitive() as unknown as DaoProposal;
return proposal;
}

@checkConnection
private async getProposal(hash: string): Promise<ProposalRemark | undefined> {
try {
const proposalJson = await this.client.api.query.dao.proposalOf(hash);
const proposalRemark: ProposalRemark = proposalJson.toPrimitive() as unknown as ProposalRemark;
return proposalRemark;
} catch (error) {
console.warn("Couldn't decode a proposal");
}
}
@checkConnection
private async getProposalVotes(hash: string): Promise<ProposalVotes> {
const votesJson = await this.client.api.query.dao.voting(hash);
const proposalVotes: ProposalVotes = votesJson.toPrimitive() as unknown as ProposalVotes;
return proposalVotes;
}
}
class Dao extends QueryDao {
constructor(public client: Client) {
super(client);
this.client = client;
}
@checkConnection
async vote(options: DaoVoteOptions) {
const extrinsic = this.client.api.tx.dao.vote(options.farmId, options.hash, options.approve);
return this.client.patchExtrinsic(extrinsic);
}
}
export { Dao, QueryDao };
1 change: 1 addition & 0 deletions packages/tfchain_client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./balances";
export * from "./dao";
export * from "./client";
export * from "./contracts";
export * from "./farms";
Expand Down

0 comments on commit a1a475e

Please sign in to comment.