Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: check env schema #131

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
46 changes: 26 additions & 20 deletions src/parser/permit-generation-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import {
PermitGenerationConfiguration,
permitGenerationConfigurationType,
} from "../configuration/permit-generation-configuration";
import { getOctokitInstance } from "../octokit";
import { IssueActivity } from "../issue-activity";
import { getOctokitInstance } from "../octokit";
import { getRepo, parseGitHubUrl } from "../start";
import envConfigSchema, { EnvConfigType } from "../types/env-type";
import envConfigSchema, { EnvConfigType, envValidator } from "../types/env-type";
import program from "./command-line";
import { Module, Result } from "./processor";

Expand Down Expand Up @@ -50,14 +50,18 @@ export class PermitGenerationModule implements Module {
node_id: program.eventPayload.issue.node_id,
};
const env = Value.Default(envConfigSchema, process.env) as EnvConfigType;
if (!Value.Check(envConfigSchema, env)) {
if (!envValidator.test(env)) {
console.warn("[PermitGenerationModule] Invalid env detected, skipping.");
for (const error of envValidator.errors(env)) {
console.error(error);
}
return Promise.resolve(result);
}
const isPrivateKeyAllowed = await this._isPrivateKeyAllowed(
payload.evmPrivateEncrypted,
program.eventPayload.repository.owner.id,
program.eventPayload.repository.id
program.eventPayload.repository.id,
env
);
if (!isPrivateKeyAllowed) {
console.warn("[PermitGenerationModule] Private key is not allowed to be used in this organization/repository.");
Expand All @@ -81,7 +85,7 @@ export class PermitGenerationModule implements Module {
const adapters = {} as ReturnType<typeof createAdapters>;

// apply fees
result = await this._applyFees(result, payload.erc20RewardToken);
result = await this._applyFees(result, payload.erc20RewardToken, env);

for (const [key, value] of Object.entries(result)) {
try {
Expand Down Expand Up @@ -125,16 +129,16 @@ export class PermitGenerationModule implements Module {
}
}

// remove treasury item from final result in order not to display permit fee in github comments
if (process.env.PERMIT_TREASURY_GITHUB_USERNAME) delete result[process.env.PERMIT_TREASURY_GITHUB_USERNAME];
// remove treasury item from final result in order not to display permit fee in GitHub comments
if (env.PERMIT_TREASURY_GITHUB_USERNAME) delete result[env.PERMIT_TREASURY_GITHUB_USERNAME];

return result;
}

/**
* Applies fees to the final result.
* How it works:
* 1. Fee (read from ENV variable) is subtracted from all of the final result items (user.total, user.task.reward, user.comments[].reward)
* 1. Fee (read from ENV variable) is subtracted from all the final result items (user.total, user.task.reward, user.comments[].reward)
* 2. Total fee is calculated
* 3. A new item is added to the final result object, example:
* ```
Expand All @@ -149,34 +153,35 @@ export class PermitGenerationModule implements Module {
* This method is meant to be called before the final permit generation.
* @param result Result object
* @param erc20RewardToken ERC20 address of the reward token
* @param env The program environment
* @returns Result object
*/
async _applyFees(result: Result, erc20RewardToken: string): Promise<Result> {
async _applyFees(result: Result, erc20RewardToken: string, env: EnvConfigType): Promise<Result> {
// validate fee related env variables
if (!process.env.PERMIT_FEE_RATE || +process.env.PERMIT_FEE_RATE === 0) {
if (!env.PERMIT_FEE_RATE || +env.PERMIT_FEE_RATE === 0) {
console.log("PERMIT_FEE_RATE is not set, skipping permit fee generation");
return result;
}
if (!process.env.PERMIT_TREASURY_GITHUB_USERNAME) {
if (!env.PERMIT_TREASURY_GITHUB_USERNAME) {
console.log("PERMIT_TREASURY_GITHUB_USERNAME is not set, skipping permit fee generation");
return result;
}
if (process.env.PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST) {
const erc20TokensNoFee = process.env.PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST.split(",");
if (env.PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST) {
const erc20TokensNoFee = env.PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST.split(",");
if (erc20TokensNoFee.includes(erc20RewardToken)) {
console.log(`Token address ${erc20RewardToken} is whitelisted to be fee free, skipping permit fee generation`);
return result;
}
}

// Get treasury github user id
// Get treasury GitHub user id
const octokit = getOctokitInstance();
const { data: treasuryGithubData } = await octokit.users.getByUsername({
username: process.env.PERMIT_TREASURY_GITHUB_USERNAME,
username: env.PERMIT_TREASURY_GITHUB_USERNAME,
});
if (!treasuryGithubData) {
console.log(
`GitHub user was not found for username ${process.env.PERMIT_TREASURY_GITHUB_USERNAME}, skipping permit fee generation`
`GitHub user was not found for username ${env.PERMIT_TREASURY_GITHUB_USERNAME}, skipping permit fee generation`
);
return result;
}
Expand All @@ -185,7 +190,7 @@ export class PermitGenerationModule implements Module {
// - user.total
// - user.task.reward
// - user.comments[].reward
const feeRateDecimal = new Decimal(100).minus(process.env.PERMIT_FEE_RATE).div(100);
const feeRateDecimal = new Decimal(100).minus(env.PERMIT_FEE_RATE).div(100);
let permitFeeAmountDecimal = new Decimal(0);
for (const [_, rewardResult] of Object.entries(result)) {
// accumulate total permit fee amount
Expand All @@ -206,7 +211,7 @@ export class PermitGenerationModule implements Module {
}

// Add a new result item for treasury
result[process.env.PERMIT_TREASURY_GITHUB_USERNAME] = {
result[env.PERMIT_TREASURY_GITHUB_USERNAME] = {
total: +permitFeeAmountDecimal.toFixed(2),
userId: treasuryGithubData.id,
};
Expand Down Expand Up @@ -306,10 +311,11 @@ export class PermitGenerationModule implements Module {
async _isPrivateKeyAllowed(
privateKeyEncrypted: string,
githubContextOwnerId: number,
githubContextRepositoryId: number
githubContextRepositoryId: number,
env: EnvConfigType
): Promise<boolean> {
// decrypt private key
const privateKeyDecrypted = await decrypt(privateKeyEncrypted, process.env.X25519_PRIVATE_KEY);
const privateKeyDecrypted = await decrypt(privateKeyEncrypted, env.X25519_PRIVATE_KEY);

// parse decrypted private key
const privateKeyParsed = parseDecryptedPrivateKey(privateKeyDecrypted);
Expand Down
3 changes: 3 additions & 0 deletions src/types/env-type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Static, Type } from "@sinclair/typebox";
import { StandardValidator } from "typebox-validators";

const envConfigSchema = Type.Object({
SUPABASE_URL: Type.String(),
Expand All @@ -15,4 +16,6 @@ const envConfigSchema = Type.Object({

export type EnvConfigType = Static<typeof envConfigSchema>;

export const envValidator = new StandardValidator(envConfigSchema);

export default envConfigSchema;
61 changes: 33 additions & 28 deletions tests/parser/permit-generation-module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,23 @@ describe("permit-generation-module.ts", () => {
process.env.PERMIT_FEE_RATE = "";
const permitGenerationModule = new PermitGenerationModule();
const spyConsoleLog = jest.spyOn(console, "log");
await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS);
await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS, process.env);
expect(spyConsoleLog).toHaveBeenCalledWith("PERMIT_FEE_RATE is not set, skipping permit fee generation");
});

it("Should not apply fees if PERMIT_FEE_RATE is 0", async () => {
process.env.PERMIT_FEE_RATE = "0";
const permitGenerationModule = new PermitGenerationModule();
const spyConsoleLog = jest.spyOn(console, "log");
await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS);
await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS, process.env);
expect(spyConsoleLog).toHaveBeenCalledWith("PERMIT_FEE_RATE is not set, skipping permit fee generation");
});

it("Should not apply fees if PERMIT_TREASURY_GITHUB_USERNAME is empty", async () => {
process.env.PERMIT_TREASURY_GITHUB_USERNAME = "";
const permitGenerationModule = new PermitGenerationModule();
const spyConsoleLog = jest.spyOn(console, "log");
await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS);
await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS, process.env);
expect(spyConsoleLog).toHaveBeenCalledWith(
"PERMIT_TREASURY_GITHUB_USERNAME is not set, skipping permit fee generation"
);
Expand All @@ -141,15 +141,15 @@ describe("permit-generation-module.ts", () => {
it("Should not apply fees if ERC20 reward token is included in PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST", async () => {
const permitGenerationModule = new PermitGenerationModule();
const spyConsoleLog = jest.spyOn(console, "log");
await permitGenerationModule._applyFees(resultOriginal, DOLLAR_ADDRESS);
await permitGenerationModule._applyFees(resultOriginal, DOLLAR_ADDRESS, process.env);
expect(spyConsoleLog).toHaveBeenCalledWith(
`Token address ${DOLLAR_ADDRESS} is whitelisted to be fee free, skipping permit fee generation`
);
});

it("Should apply fees", async () => {
const permitGenerationModule = new PermitGenerationModule();
const resultAfterFees = await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS);
const resultAfterFees = await permitGenerationModule._applyFees(resultOriginal, WXDAI_ADDRESS, process.env);

// check that 10% fee is subtracted from rewards
expect(resultAfterFees["user1"].total).toEqual(90);
Expand Down Expand Up @@ -180,13 +180,14 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 1;
const githubContextRepositoryId = 2;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(false);
expect(isAllowed).toEqual(false);
expect(spyConsoleLog).toHaveBeenCalledWith("Private key could not be decrypted");
});

Expand All @@ -201,21 +202,21 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 99;
const githubContextRepositoryId = 2;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(false);
expect(isAllowed).toEqual(false);
expect(spyConsoleLog).toHaveBeenCalledWith(
"Current organization/user id 99 is not allowed to use this private key"
);
});

it("Should return true if private key is used in allowed organization", async () => {
const permitGenerationModule = new PermitGenerationModule();
const spyConsoleLog = jest.spyOn(console, "log");

// format: "PRIVATE_KEY:GITHUB_ORGANIZATION_ID"
// encrypted value: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80:1"
Expand All @@ -224,13 +225,14 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 1;
const githubContextRepositoryId = 2;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(true);
expect(isAllowed).toEqual(true);
});

it("Should return false if private key is used in unallowed organization and allowed repository", async () => {
Expand All @@ -244,13 +246,14 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 99;
const githubContextRepositoryId = 2;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(false);
expect(isAllowed).toEqual(false);
expect(spyConsoleLog).toHaveBeenCalledWith(
"Current organization/user id 99 and repository id 2 are not allowed to use this private key"
);
Expand All @@ -267,21 +270,21 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 1;
const githubContextRepositoryId = 99;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(false);
expect(isAllowed).toEqual(false);
expect(spyConsoleLog).toHaveBeenCalledWith(
"Current organization/user id 1 and repository id 99 are not allowed to use this private key"
);
});

it("Should return true if private key is used in allowed organization and repository", async () => {
const permitGenerationModule = new PermitGenerationModule();
const spyConsoleLog = jest.spyOn(console, "log");

// format: "PRIVATE_KEY:GITHUB_ORGANIZATION_ID:GITHUB_REPOSITORY_ID"
// encrypted value: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80:1:2"
Expand All @@ -290,13 +293,14 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 1;
const githubContextRepositoryId = 2;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(true);
expect(isAllowed).toEqual(true);
});

it("Should return false if private key format is invalid", async () => {
Expand All @@ -310,13 +314,14 @@ describe("permit-generation-module.ts", () => {
const githubContextOrganizationId = 1;
const githubContextRepositoryId = 2;

const result = await permitGenerationModule._isPrivateKeyAllowed(
const isAllowed = await permitGenerationModule._isPrivateKeyAllowed(
privateKeyEncrypted,
githubContextOrganizationId,
githubContextRepositoryId
githubContextRepositoryId,
process.env
);

expect(result).toEqual(false);
expect(isAllowed).toEqual(false);
expect(spyConsoleLog).toHaveBeenCalledWith("Invalid private key format");
});
});
Expand Down
Loading