Skip to content

Commit

Permalink
feat: retry plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
whilefoo committed Jul 23, 2024
1 parent 6d8bd23 commit 6bdd49f
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 133 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dependencies": {
"@actions/core": "1.10.1",
"@actions/github": "6.0.0",
"@octokit/plugin-retry": "6.0.1",
"@octokit/rest": "20.1.0",
"@octokit/webhooks": "13.2.7",
"@sinclair/typebox": "0.32.23",
Expand All @@ -41,7 +42,6 @@
"lodash": "4.17.21",
"markdown-it": "14.1.0",
"openai": "4.29.1",
"ts-retry": "4.2.5",
"tsx": "4.7.1",
"typebox-validators": "0.3.5",
"yaml": "2.4.1"
Expand Down
32 changes: 4 additions & 28 deletions src/issue-activity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { retryAsyncUntilDefinedDecorator } from "ts-retry";
import { CommentType } from "./configuration/comment-types";
import configuration from "./configuration/config-reader";
import { DataCollectionConfiguration } from "./configuration/data-collection-config";
Expand All @@ -11,8 +10,6 @@ import {
GitHubPullRequestReviewComment,
GitHubPullRequestReviewState,
} from "./github-types";
import githubCommentModuleInstance from "./helpers/github-comment-module-instance";
import logger from "./helpers/logger";
import {
getIssue,
getIssueComments,
Expand All @@ -35,32 +32,11 @@ export class IssueActivity {
linkedReviews: Review[] = [];

async init() {
function fn<T>(func: () => Promise<T>) {
return func();
}
const decoratedFn = retryAsyncUntilDefinedDecorator(fn, {
delay: this._configuration.delayMs,
maxTry: this._configuration.maxAttempts,
async onError(error) {
try {
const content = "Failed to retrieve activity. Retrying...";
const message = logger.error(content, { error });
await githubCommentModuleInstance.postComment(message?.logMessage.diff || content);
} catch (e) {
logger.error(`${e}`);
}
},
async onMaxRetryFunc(error) {
logger.error("Failed to retrieve activity after 10 attempts. See logs for more details.", {
error,
});
},
});
[this.self, this.events, this.comments, this.linkedReviews] = await Promise.all([
decoratedFn(() => getIssue(this._issueParams)),
decoratedFn(() => getIssueEvents(this._issueParams)),
decoratedFn(() => getIssueComments(this._issueParams)),
decoratedFn(() => this._getLinkedReviews()),
getIssue(this._issueParams),
getIssueEvents(this._issueParams),
getIssueComments(this._issueParams),
this._getLinkedReviews(),
]);
}

Expand Down
5 changes: 4 additions & 1 deletion src/get-authentication-token.ts → src/octokit.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Octokit } from "@octokit/rest";
import { retry } from "@octokit/plugin-retry";
import program from "./parser/command-line";

const customOctokit = Octokit.plugin(retry);

let octokitInstance: Octokit | null = null;

function getOctokitInstance(): Octokit {
if (!octokitInstance) {
octokitInstance = new Octokit({ auth: program.authToken });
octokitInstance = new customOctokit({ auth: program.authToken });
}
return octokitInstance;
}
Expand Down
2 changes: 1 addition & 1 deletion src/parser/github-comment-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { stringify } from "yaml";
import { CommentType } from "../configuration/comment-types";
import configuration from "../configuration/config-reader";
import { GithubCommentConfiguration, githubCommentConfigurationType } from "../configuration/github-comment-config";
import { getOctokitInstance } from "../get-authentication-token";
import { getOctokitInstance } from "../octokit";
import { getGithubWorkflowRunUrl } from "../helpers/github-comment-module-instance";
import logger from "../helpers/logger";
import { getERC20TokenSymbol } from "../helpers/web3";
Expand Down
19 changes: 12 additions & 7 deletions src/parser/permit-generation-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
PermitGenerationConfiguration,
permitGenerationConfigurationType,
} from "../configuration/permit-generation-configuration";
import { getOctokitInstance } from "../get-authentication-token";
import { getOctokitInstance } from "../octokit";
import { IssueActivity } from "../issue-activity";
import { getRepo, parseGitHubUrl } from "../start";
import envConfigSchema, { EnvConfigType } from "../types/env-type";
Expand Down Expand Up @@ -158,12 +158,16 @@ export class PermitGenerationModule implements Module {
return result;
}
}

// Get treasury github user id
const octokit = getOctokitInstance();
const { data: treasuryGithubData } = await octokit.users.getByUsername({ username: process.env.PERMIT_TREASURY_GITHUB_USERNAME });
const { data: treasuryGithubData } = await octokit.users.getByUsername({
username: process.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`);
console.log(
`GitHub user was not found for username ${process.env.PERMIT_TREASURY_GITHUB_USERNAME}, skipping permit fee generation`
);
return result;
}

Expand All @@ -175,14 +179,15 @@ export class PermitGenerationModule implements Module {
let permitFeeAmountDecimal = new Decimal(0);
for (const [_, rewardResult] of Object.entries(result)) {
// accumulate total permit fee amount
const totalAfterFee = +(new Decimal(rewardResult.total).mul(feeRateDecimal));
const totalAfterFee = +new Decimal(rewardResult.total).mul(feeRateDecimal);
permitFeeAmountDecimal = permitFeeAmountDecimal.add(new Decimal(rewardResult.total).minus(totalAfterFee));
// subtract fees
rewardResult.total = +totalAfterFee.toFixed(2);
if (rewardResult.task) rewardResult.task.reward = +(new Decimal(rewardResult.task.reward).mul(feeRateDecimal).toFixed(2));
if (rewardResult.task)
rewardResult.task.reward = +new Decimal(rewardResult.task.reward).mul(feeRateDecimal).toFixed(2);
if (rewardResult.comments) {
for (let comment of rewardResult.comments) {
if (comment.score) comment.score.reward = +(new Decimal(comment.score.reward).mul(feeRateDecimal).toFixed(2));
if (comment.score) comment.score.reward = +new Decimal(comment.score.reward).mul(feeRateDecimal).toFixed(2);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/start.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getOctokitInstance } from "./get-authentication-token";
import { getOctokitInstance } from "./octokit";
import {
GitHubIssue,
GitHubIssueComment,
Expand Down
122 changes: 63 additions & 59 deletions tests/parser/permit-generation-module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { CommentType } from "../../src/configuration/comment-types";
import { PermitGenerationModule } from "../../src/parser/permit-generation-module";
import { Result } from "../../src/parser/processor";

const DOLLAR_ADDRESS = '0xb6919Ef2ee4aFC163BC954C5678e2BB570c2D103';
const WXDAI_ADDRESS = '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d';
const DOLLAR_ADDRESS = "0xb6919Ef2ee4aFC163BC954C5678e2BB570c2D103";
const WXDAI_ADDRESS = "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d";

jest.mock("../../src/parser/command-line", () => {
const cfg = require("../__mocks__/results/valid-configuration.json");
Expand Down Expand Up @@ -31,16 +31,16 @@ jest.mock("../../src/parser/command-line", () => {
};
});

jest.mock("../../src/get-authentication-token", () => ({
jest.mock("../../src/octokit", () => ({
getOctokitInstance: () => ({
users: {
getByUsername: () => ({
data: {
id: 3
}
})
}
})
id: 3,
},
}),
},
}),
}));

jest.mock("@supabase/supabase-js", () => {
Expand All @@ -51,41 +51,41 @@ jest.mock("@supabase/supabase-js", () => {

// original rewards object before fees are applied
const resultOriginal: Result = {
"user1": {
"total": 100,
"task": {
"reward": 90,
"multiplier": 1,
user1: {
total: 100,
task: {
reward: 90,
multiplier: 1,
},
"userId": 1,
"comments": [
userId: 1,
comments: [
{
"content": "comment 3",
"url": "https://github.com/user-org/test-repo/issues/57#issuecomment-2172704421",
"type": CommentType.COMMENTED,
"score": {
"reward": 10
}
}
]
content: "comment 3",
url: "https://github.com/user-org/test-repo/issues/57#issuecomment-2172704421",
type: CommentType.COMMENTED,
score: {
reward: 10,
},
},
],
},
"user2": {
"total": 11.11,
"task": {
"reward": 9.99,
"multiplier": 1,
user2: {
total: 11.11,
task: {
reward: 9.99,
multiplier: 1,
},
"userId": 1,
"comments": [
userId: 1,
comments: [
{
"content": "comment 3",
"url": "https://github.com/user-org/test-repo/issues/57#issuecomment-2172704421",
"type": CommentType.COMMENTED,
"score": {
"reward": 1.12
}
}
]
content: "comment 3",
url: "https://github.com/user-org/test-repo/issues/57#issuecomment-2172704421",
type: CommentType.COMMENTED,
score: {
reward: 1.12,
},
},
],
},
};

Expand All @@ -94,11 +94,11 @@ describe("permit-generation-module.ts", () => {
beforeEach(() => {
// set fee related env variables
// treasury fee applied to the final permits, ex: 100 = 100%, 0.1 = 0.1%
process.env.PERMIT_FEE_RATE='10';
process.env.PERMIT_FEE_RATE = "10";
// github account associated with EVM treasury address allowed to claim permit fees, ex: "ubiquibot-treasury"
process.env.PERMIT_TREASURY_GITHUB_USERNAME="ubiquibot-treasury"
process.env.PERMIT_TREASURY_GITHUB_USERNAME = "ubiquibot-treasury";
// comma separated list of token addresses which should not incur any fees, ex: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d, 0x4ECaBa5870353805a9F068101A40E0f32ed605C6"
process.env.PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST=`${DOLLAR_ADDRESS}`
process.env.PERMIT_ERC20_TOKENS_NO_FEE_WHITELIST = `${DOLLAR_ADDRESS}`;
});

afterEach(() => {
Expand All @@ -107,50 +107,54 @@ describe("permit-generation-module.ts", () => {
});

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

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');
const spyConsoleLog = jest.spyOn(console, "log");
await permitGenerationModule._applyFees(resultOriginal, DOLLAR_ADDRESS);
expect(spyConsoleLog).toHaveBeenCalledWith(`Token address ${DOLLAR_ADDRESS} is whitelisted to be fee free, skipping permit fee generation`);
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);

// check that 10% fee is subtracted from rewards
expect(resultAfterFees['user1'].total).toEqual(90);
expect(resultAfterFees['user1'].task?.reward).toEqual(81);
expect(resultAfterFees['user1'].comments?.[0].score?.reward).toEqual(9);
expect(resultAfterFees['user2'].total).toEqual(10);
expect(resultAfterFees['user2'].task?.reward).toEqual(8.99);
expect(resultAfterFees['user2'].comments?.[0].score?.reward).toEqual(1.01);
expect(resultAfterFees["user1"].total).toEqual(90);
expect(resultAfterFees["user1"].task?.reward).toEqual(81);
expect(resultAfterFees["user1"].comments?.[0].score?.reward).toEqual(9);
expect(resultAfterFees["user2"].total).toEqual(10);
expect(resultAfterFees["user2"].task?.reward).toEqual(8.99);
expect(resultAfterFees["user2"].comments?.[0].score?.reward).toEqual(1.01);

// check that treasury item is added
expect(resultAfterFees['ubiquibot-treasury'].total).toEqual(11.11);
expect(resultAfterFees["ubiquibot-treasury"].total).toEqual(11.11);
});
});
});
Loading

0 comments on commit 6bdd49f

Please sign in to comment.