Skip to content

Commit

Permalink
Merge pull request #131 from gentlementlegen/fix/check-env-schema
Browse files Browse the repository at this point in the history
fix: check env schema
  • Loading branch information
gentlementlegen authored Oct 4, 2024
2 parents 3febd8e + 099a9e1 commit aeacb16
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 48 deletions.
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

0 comments on commit aeacb16

Please sign in to comment.