Skip to content

Commit

Permalink
apply vote amount cap to votes (#259)
Browse files Browse the repository at this point in the history
* apply vote amount cap to votes

* refactor: move voteAmountCap definition to general chain configuration

---------

Co-authored-by: Massimiliano Mirra <[email protected]>
  • Loading branch information
gravityblast and bard authored Aug 25, 2023
1 parent 721da24 commit 2aa2534
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 15 deletions.
21 changes: 14 additions & 7 deletions src/calculator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import csv from "csv-parser";
import { linearQF, Contribution, Calculation } from "pluralistic";
import type { PassportProvider } from "../passport/index.js";
import { PriceProvider } from "../prices/provider.js";
import { tokenDecimals } from "../config.js";
import { Chain, tokenDecimals } from "../config.js";
import type { Round, Application, Vote } from "../indexer/types.js";
import { getVotesWithCoefficients } from "./votes.js";
import {
Expand Down Expand Up @@ -99,6 +99,7 @@ export type CalculatorOptions = {
enablePassport?: boolean;
ignoreSaturation?: boolean;
overrides: Overrides;
chain: Chain;
};

export type AugmentedResult = Calculation & {
Expand All @@ -113,7 +114,8 @@ export default class Calculator {
private passportProvider: PassportProvider;
private priceProvider: PriceProvider;
private dataProvider: DataProvider;
private chainId: number;
private chainId: number; // XXX remove
private chain: Chain;
private roundId: string;
private minimumAmountUSD: number | undefined;
private matchingCapAmount: bigint | undefined;
Expand All @@ -126,14 +128,15 @@ export default class Calculator {
this.passportProvider = options.passportProvider;
this.priceProvider = options.priceProvider;
this.dataProvider = options.dataProvider;
this.chainId = options.chainId;
this.chainId = options.chainId; // XXX remove
this.roundId = options.roundId;
this.minimumAmountUSD = options.minimumAmountUSD;
this.enablePassport = options.enablePassport;
this.passportThreshold = options.passportThreshold;
this.matchingCapAmount = options.matchingCapAmount;
this.overrides = options.overrides;
this.ignoreSaturation = options.ignoreSaturation;
this.chain = options.chain;
}

async calculate(): Promise<Array<AugmentedResult>> {
Expand Down Expand Up @@ -188,6 +191,7 @@ export default class Calculator {
}

const votesWithCoefficients = await getVotesWithCoefficients(
this.chain,
round,
applications,
votes,
Expand Down Expand Up @@ -229,10 +233,13 @@ export default class Calculator {

const augmented: Array<AugmentedResult> = [];

const applicationsMap = applications.reduce((all, current) => {
all[current.id] = current;
return all;
}, {} as Record<string, Application>);
const applicationsMap = applications.reduce(
(all, current) => {
all[current.id] = current;
return all;
},
{} as Record<string, Application>
);

for (const id in results) {
const calc = results[id];
Expand Down
55 changes: 55 additions & 0 deletions src/calculator/tokensSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Chain } from "../config.js";
import type { Vote } from "../indexer/types.js";

enum ChainId {
Fantom = 250,
}

type TokenAddress = Lowercase<`0x${string}`>;

type TokensSettings = {
[chainId in ChainId]: {
[tokenAddress: TokenAddress]: {
voteAmountCap: bigint | undefined;
};
};
};

export const FANTOM_GCV: TokenAddress =
"0x83791638da5eb2faa432aff1c65fba47c5d29510";

// For now this mapping contains one single token.
// We can continue adding tokens here, but in the future it will
// be better to save the voteAmountCap in the roundMetadata and having
// this fields editable in Grants Stack.
export const tokensSettings: TokensSettings = {
250: {
[FANTOM_GCV]: {
voteAmountCap: BigInt(10e18),
},
},
};

export function applyVoteCap(chain: Chain, vote: Vote): Vote {
const tokenConfig = chain.tokens.find(
(t) => t.address.toLowerCase() === vote.token.toLowerCase()
);

if (tokenConfig === undefined) {
throw new Error(`Unknown token: ${vote.token}`);
}

const { voteAmountCap } = tokenConfig;
if (voteAmountCap === undefined) {
return vote;
} else {
// amount : amountRoundToken = voteAmountCap : newAmountRoundToken
const newAmountRoundToken =
(BigInt(vote.amountRoundToken) * voteAmountCap) / BigInt(vote.amount);

return {
...vote,
amountRoundToken: newAmountRoundToken.toString(),
};
}
}
17 changes: 12 additions & 5 deletions src/calculator/votes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Chain } from "../config.js";
import type { Round, Application, Vote } from "../indexer/types.js";
import type { PassportScore, PassportProvider } from "../passport/index.js";
import { applyVoteCap } from "./tokensSettings.js";

type VoteWithCoefficient = Vote & {
coefficient: number;
passportScore?: PassportScore;
};

export async function getVotesWithCoefficients(
chain: Chain,
round: Round,
applications: Array<Application>,
votes: Array<Vote>,
Expand All @@ -17,10 +20,13 @@ export async function getVotesWithCoefficients(
passportThreshold?: number;
}
): Promise<Array<VoteWithCoefficient>> {
const applicationMap = applications.reduce((map, application) => {
map[application.id] = application;
return map;
}, {} as Record<string, Application>);
const applicationMap = applications.reduce(
(map, application) => {
map[application.id] = application;
return map;
},
{} as Record<string, Application>
);

const enablePassport =
options.enablePassport ??
Expand All @@ -33,7 +39,8 @@ export async function getVotesWithCoefficients(
0
);

const votePromises = votes.map(async (vote) => {
const votePromises = votes.map(async (originalVote) => {
const vote = applyVoteCap(chain, originalVote);
const voter = vote.voter.toLowerCase();
const application = applicationMap[vote.applicationId];

Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type Token = {
address: string;
decimals: number;
priceSource: { chainId: CoingeckoSupportedChainId; address: string };
voteAmountCap?: bigint;
};

export type Subscription = {
Expand Down Expand Up @@ -275,6 +276,7 @@ export const CHAINS: Chain[] = [
code: "GcV",
address: "0x83791638da5EB2fAa432aff1c65fbA47c5D29510",
decimals: 18,
voteAmountCap: BigInt(10e18),
priceSource: {
chainId: 250,
address: "0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E",
Expand Down
16 changes: 13 additions & 3 deletions src/http/api/v1/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,23 @@ export const createHandler = (config: HttpApiConfig): express.Router => {
.concat(csv.stringifyRecords(pricesDuringRound));
}

async function exportVoteCoefficientsCSV(db: JsonStorage, round: Round) {
async function exportVoteCoefficientsCSV(
chainId: number,
db: JsonStorage,
round: Round
) {
const [applications, votes] = await Promise.all([
db.collection<Application>(`rounds/${round.id}/applications`).all(),
db.collection<Vote>(`rounds/${round.id}/votes`).all(),
]);

const chainConfig = config.chains.find((c) => c.id === chainId);
if (chainConfig === undefined) {
throw new Error(`Chain ${chainId} not configured`);
}

const votesWithCoefficients = await getVotesWithCoefficients(
chainConfig,
round,
applications,
votes,
Expand Down Expand Up @@ -240,7 +250,7 @@ export const createHandler = (config: HttpApiConfig): express.Router => {
throw new ClientError("Round not found", 404);
}

const body = await exportVoteCoefficientsCSV(db, round);
const body = await exportVoteCoefficientsCSV(chainId, db, round);

res.setHeader("content-type", "text/csv");
res.setHeader(
Expand Down Expand Up @@ -285,7 +295,7 @@ export const createHandler = (config: HttpApiConfig): express.Router => {
break;
}
case "vote_coefficients": {
body = await exportVoteCoefficientsCSV(db, round);
body = await exportVoteCoefficientsCSV(chainId, db, round);
break;
}
default: {
Expand Down
6 changes: 6 additions & 0 deletions src/http/api/v1/matches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ export const createHandler = (config: HttpApiConfig): express.Router => {
overrides = await parseOverrides(buf);
}

const chainConfig = config.chains.find((c) => c.id === chainId);
if (chainConfig === undefined) {
throw new Error(`Chain ${chainId} not configured`);
}

const calculatorOptions: CalculatorOptions = {
priceProvider: config.priceProvider,
dataProvider: config.dataProvider,
Expand All @@ -93,6 +98,7 @@ export const createHandler = (config: HttpApiConfig): express.Router => {
enablePassport: enablePassport,
ignoreSaturation: ignoreSaturation,
overrides,
chain: chainConfig,
};

const calculator = new Calculator(calculatorOptions);
Expand Down
2 changes: 2 additions & 0 deletions src/http/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createHandler as createApiHandler } from "./api/v1/index.js";
import { PriceProvider } from "../prices/provider.js";
import { PassportProvider } from "../passport/index.js";
import { DataProvider } from "../calculator/index.js";
import { Chain } from "../config.js";

export interface HttpApiConfig {
logger: Logger;
Expand All @@ -20,6 +21,7 @@ export interface HttpApiConfig {
priceProvider: PriceProvider;
dataProvider: DataProvider;
passportProvider: PassportProvider;
chains: Chain[];
}

interface HttpApi {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ async function main(): Promise<void> {
port: config.apiHttpPort,
logger: baseLogger.child({ subsystem: "HttpApi" }),
buildTag: config.buildTag,
chains: config.chains,
});

await httpApi.start();
Expand Down
Loading

0 comments on commit 2aa2534

Please sign in to comment.