Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

September 2023 Update Adjustments #818

Open
wants to merge 9 commits into
base: development
Choose a base branch
from
16 changes: 0 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,22 +224,6 @@ Bounty bot is built using the [probot](https://probot.github.io/) framework so i
├── <a href="https://github.com/ubiquity/ubiquibot/tree/development/src/utils">utils</a> A set of utility functions
</pre>

## Default Config Notes (`ubiquibot-config-default.ts`)

We can't use a `jsonc` file due to limitations with Netlify. Here is a snippet of some values with notes next to them.

```jsonc
{
"payment-permit-max-price": 9007199254740991, // Number.MAX_SAFE_INTEGER
"max-concurrent-assigns": 9007199254740991, // Number.MAX_SAFE_INTEGER
"comment-element-pricing": {
/* https://github.com/syntax-tree/mdast#nodes */
"strong": 0 // Also includes italics, unfortunately https://github.com/syntax-tree/mdast#strong
/* https://github.com/syntax-tree/mdast#gfm */
}
}
```

## Supabase Cron Job (`logs-cleaner`)

##### Dashboard > Project > Database > Extensions
Expand Down
4 changes: 1 addition & 3 deletions src/bindings/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ export const loadConfig = async (context: Context): Promise<BotConfig> => {
permitBaseUrl: process.env.PERMIT_BASE_URL || permitBaseUrl,
},
unassign: {
timeRangeForMaxIssue: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE
? Number(process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE)
: timeRangeForMaxIssue,
timeRangeForMaxIssue: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE ? Number(process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE) : timeRangeForMaxIssue,
timeRangeForMaxIssueEnabled: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED
? process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED == "true"
: timeRangeForMaxIssueEnabled,
Expand Down
30 changes: 27 additions & 3 deletions src/configs/ubiquibot-config-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export const DefaultConfig: MergedConfig = {
evmNetworkId: 100,
priceMultiplier: 1,
issueCreatorMultiplier: 2,
paymentPermitMaxPrice: 9007199254740991,
maxConcurrentAssigns: 9007199254740991,
paymentPermitMaxPrice: Number.MAX_SAFE_INTEGER,
maxConcurrentAssigns: Number.MAX_SAFE_INTEGER,
assistivePricing: false,
disableAnalytics: false,
commentIncentives: false,
Expand Down Expand Up @@ -87,7 +87,31 @@ export const DefaultConfig: MergedConfig = {
],
incentives: {
comment: {
elements: {},
elements: {
h1: 0,
h2: 0,
h3: 0,
h4: 0,
h5: 0,
h6: 0,
a: 0,
ul: 0,
li: 0,
p: 0,
img: 0,
code: 0,
table: 0,
td: 0,
tr: 0,
br: 0,
blockquote: 0,
em: 0,
strong: 0,
hr: 0,
del: 0,
pre: 0,
ol: 0,
},
totals: {
word: 0,
},
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/comment/handlers/ask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const ask = async (body: string) => {
}

if (!issue) {
return `This command can only be used on issues`;
return `This command can only be used on issues or pull requests`;
}

const chatHistory: CreateChatCompletionRequestMessage[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/comment/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export const userCommands = (): UserCommands[] => {
},
{
id: IssueCommentCommands.ASK,
description: `Ask a technical question to the Ubiquity AI. \n example usage: "/ask How do I do X?"`,
description: `Ask a technical question to UbiquiBot. \n example usage: "/ask How do I do X?"`,
handler: ask,
callback: commandCallback,
},
Expand Down
7 changes: 5 additions & 2 deletions src/handlers/comment/handlers/payout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getBotContext, getLogger } from "../../../bindings";
import { getBotConfig, getBotContext, getLogger } from "../../../bindings";
import { Payload } from "../../../types";
import { IssueCommentCommands } from "../commands";
import {
Expand All @@ -14,6 +14,9 @@ import { GLOBAL_STRINGS } from "../../../configs";

export const payout = async (body: string) => {
const { payload: _payload } = getBotContext();
const {
payout: { permitBaseUrl },
} = getBotConfig();
const logger = getLogger();
if (body != IssueCommentCommands.PAYOUT && body.replace(/`/g, "") != IssueCommentCommands.PAYOUT) {
logger.info(`Skipping to payout. body: ${body}`);
Expand All @@ -39,7 +42,7 @@ export const payout = async (body: string) => {
return `Permit generation failed due to internal GitHub Error`;
}

const hasPosted = IssueComments.find((e) => e.user.type === "Bot" && e.body.includes("https://pay.ubq.fi?claim"));
const hasPosted = IssueComments.find((e) => e.user.type === "Bot" && e.body.includes(permitBaseUrl));
if (hasPosted) {
logger.info(`Permit already generated for ${payload.issue?.number}`);
return;
Expand Down
64 changes: 46 additions & 18 deletions src/handlers/comment/handlers/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,51 @@ export const tableComment = ({
days?: number;
}) => {
return `
<code>
<table>
${
isBountyStale
? `<tr><td>Warning!</td> <td>This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.</td></tr>`
: ``
}
<tr>
<td>Deadline</td>
<td>${deadline}</td>
</tr>
<tr>
<td>Registered Wallet</td>
<td>${wallet}</td>
</tr>
${multiplier ? `<tr><td>Payment Multiplier</td><td>${multiplier}</td></tr>` : ``}
${reason ? `<tr><td>Multiplier Reason</td><td>${reason}</td></tr>` : ``}
${bounty ? `<tr><td>Total Bounty</td><td>${bounty}</td></tr>` : ``}
</table></code>`;
${
isBountyStale
? `
<tr>
<td>Warning!</td>
<td>This task was created over ${days} days ago. Please confirm that this issue specification is accurate before starting.</td>
</tr>`
: ``
}
<tr>
<td>Deadline</td>
<td>${deadline}</td>
</tr>
<tr>
<td>Registered Wallet</td>
<td>${wallet}</td>
</tr>
${
multiplier
? `
<tr>
<td>Payment Multiplier</td>
<td>${multiplier}</td>
</tr>`
: ``
}
${
reason
? `
<tr>
<td>Multiplier Reason</td>
<td>${reason}</td>
</tr>`
: ``
}
${
bounty
? `
<tr>
<td>Total Bounty</td>
<td>${bounty}</td>
</tr>`
: ``
}
</table>
`;
};
4 changes: 2 additions & 2 deletions src/handlers/payout/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ export const incentivesCalculation = async (): Promise<IncentivesCalculationResu
const comments = await getAllIssueComments(issue.number);

const wasReopened = await wasIssueReopened(issue.number);
const claimUrlRegex = new RegExp(`\\((${permitBaseUrl}\\?claim=\\S+)\\)`);
const permitCommentIdx = comments.findIndex((e) => e.user.type === UserType.Bot && e.body.match(claimUrlRegex));
const claimUrlRegex = new RegExp(`\\((${permitBaseUrl}\\S+)\\)`);
const permitCommentIdx = comments.findIndex((e) => e.user.type === UserType.Bot && e.body.match(claimUrlRegex) && e.body.includes("Task Assignee Reward"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it makes sense to call this Assignee Reward because task is implicit

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't change it because it will break backwards compatibility. We need to implement comment metadata first and then we can change format


if (wasReopened && permitCommentIdx !== -1) {
const permitComment = comments[permitCommentIdx];
Expand Down
28 changes: 14 additions & 14 deletions src/handlers/payout/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { BigNumber } from "ethers";
export interface CreatorCommentResult {
title: string;
account?: string | undefined;
amountInETH?: Decimal | undefined;
rewardInTokens?: Decimal | undefined;
userId?: string | undefined;
tokenSymbol?: string | undefined;
node_id?: string | undefined;
Expand Down Expand Up @@ -120,14 +120,14 @@ export const calculateIssueCreatorReward = async (incentivesCalculation: Incenti

const description = await getIssueDescription(incentivesCalculation.issue.number, "html");
if (!description) {
logger.info(`Skipping to generate a permit url because issue description is empty. description: ${description}`);
return { error: `Skipping to generate a permit url because issue description is empty. description: ${description}` };
logger.info(`Skipping issue creator reward because issue description is empty. description: ${description}`);
return { error: `Skipping issue creator reward because issue description is empty. description: ${description}` };
}
logger.info(`Getting the issue description done. description: ${description}`);
const creator = incentivesCalculation.issue.user;
if (creator.type === UserType.Bot || creator.login === incentivesCalculation.issue.assignee) {
logger.info("Issue creator assigneed himself or Bot created this issue.");
return { error: "Issue creator assigneed himself or Bot created this issue." };
if (creator.type === UserType.Bot) {
logger.info("Skipping issue creator reward because Bot created this issue.");
return { error: "Skipping issue creator reward because Bot created this issue." };
}

const result = await generatePermitForComments(
Expand All @@ -138,8 +138,8 @@ export const calculateIssueCreatorReward = async (incentivesCalculation: Incenti
incentivesCalculation.paymentPermitMaxPrice
);

if (!result || !result.account || !result.amountInETH) {
throw new Error("Failed to generate permit for issue creator because of missing account or amountInETH");
if (!result || !result.account || !result.rewardInTokens) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's as expressive as inBigNumber etc it implies 1e18 format

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not in 1e18 format, but with decimals like 5.235

throw new Error("Failed to generate permit for issue creator because of missing account or rewardInTokens");
}

return {
Expand All @@ -149,7 +149,7 @@ export const calculateIssueCreatorReward = async (incentivesCalculation: Incenti
username: creator.login,
reward: [
{
priceInEth: result?.amountInETH ?? new Decimal(0),
priceInEth: result?.rewardInTokens ?? new Decimal(0),
account: result?.account,
userId: creator.id,
user: "",
Expand Down Expand Up @@ -269,7 +269,7 @@ const generatePermitForComments = async (
multiplier: number,
incentives: Incentives,
paymentPermitMaxPrice: number
): Promise<undefined | { account: string; amountInETH: Decimal }> => {
): Promise<undefined | { account: string; rewardInTokens: Decimal }> => {
const logger = getLogger();
const commentsByNode = await parseComments(comments, ItemsToExclude);
const rewardValue = calculateRewardValue(commentsByNode, incentives);
Expand All @@ -279,15 +279,15 @@ const generatePermitForComments = async (
}
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue.sum}`);
const account = await getWalletAddress(user);
const amountInETH = rewardValue.sum.mul(multiplier);
if (amountInETH.gt(paymentPermitMaxPrice)) {
const rewardInTokens = rewardValue.sum.mul(multiplier);
if (rewardInTokens.gt(paymentPermitMaxPrice)) {
logger.info(`Skipping issue creator reward for user ${user} because reward is higher than payment permit max price`);
return;
}
if (account) {
return { account, amountInETH };
return { account, rewardInTokens };
} else {
return { account: "0x", amountInETH: new Decimal(0) };
return { account: "0x", rewardInTokens: new Decimal(0) };
}
};
/**
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/push/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getBotContext, getLogger } from "../../bindings";
import { createCommitComment, getFileContent } from "../../helpers";
import { CommitsPayload, PushPayload, WideConfigSchema } from "../../types";
import { CommitsPayload, PushPayload, ConfigSchema } from "../../types";
whilefoo marked this conversation as resolved.
Show resolved Hide resolved
import { parseYAML } from "../../utils/private";
import { updateBaseRate } from "./update-base";
import { validate } from "../../utils/ajv";
Expand Down Expand Up @@ -87,7 +87,7 @@ export const validateConfigChange = async () => {
if (configFileContent) {
const decodedConfig = Buffer.from(configFileContent, "base64").toString();
const config = parseYAML(decodedConfig);
const { valid, error } = validate(WideConfigSchema, config);
const { valid, error } = validate(ConfigSchema, config);
if (!valid) {
await createCommitComment(`@${payload.sender.login} Config validation failed! ${error}`, commitSha, BASE_RATE_FILE);
}
Expand Down
10 changes: 5 additions & 5 deletions src/helpers/gpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import OpenAI from "openai";
import { CreateChatCompletionRequestMessage } from "openai/resources/chat";
import { ErrorDiff } from "../utils/helpers";

export const sysMsg = `You are the UbiquityAI, designed to provide accurate technical answers. \n
export const sysMsg = `You are the UbiquiBot, designed to provide accurate technical answers. \n
Whenever appropriate, format your response using GitHub Flavored Markdown. Utilize tables, lists, and code blocks for clear and organized answers. \n
Do not make up answers. If you are unsure, say so. \n
Original Context exists only to provide you with additional information to the current question, use it to formulate answers. \n
Expand All @@ -14,7 +14,7 @@ All replies MUST end with "\n\n <!--- { 'UbiquityAI': 'answer' } ---> ".\n
`;

export const gptContextTemplate = `
You are the UbiquityAI, designed to review and analyze pull requests.
You are the UbiquiBot, designed to review and analyze pull requests.
You have been provided with the spec of the issue and all linked issues or pull requests.
Using this full context, Reply in pure JSON format, with the following structure omitting irrelvant information pertaining to the specification.
You MUST provide the following structure, but you may add additional information if you deem it relevant.
Expand Down Expand Up @@ -118,17 +118,17 @@ export const decideContextGPT = async (
{
role: "system",
content: "This issue/Pr context: \n" + JSON.stringify(streamlined),
name: "UbiquityAI",
name: "UbiquiBot",
} as CreateChatCompletionRequestMessage,
{
role: "system",
content: "Linked issue(s) context: \n" + JSON.stringify(linkedIssueStreamlined),
name: "UbiquityAI",
name: "UbiquiBot",
} as CreateChatCompletionRequestMessage,
{
role: "system",
content: "Linked Pr(s) context: \n" + JSON.stringify(linkedPRStreamlined),
name: "UbiquityAI",
name: "UbiquiBot",
} as CreateChatCompletionRequestMessage
);

Expand Down
21 changes: 14 additions & 7 deletions src/helpers/permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { keccak256, toUtf8Bytes } from "ethers/lib/utils";
import Decimal from "decimal.js";
import { Payload } from "../types";
import { savePermit } from "../adapters/supabase";
import { ERC20ABI } from "../configs";

const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on all networks

Expand Down Expand Up @@ -45,16 +46,16 @@ type TxData = {
};

/**
* Generates permit2 signature data with `spender` and `amountInETH`
* Generates permit2 signature data with `spender` and `amountInTokens`
*
* @param spender The recipient address we're going to send tokens
* @param amountInETH The token amount in ETH
* @param amountInTokens The token amount
*
* @returns Permit2 url including base64 encoded data
*/
export const generatePermit2Signature = async (
spender: string,
amountInEth: Decimal,
amountInTokens: Decimal,
identifier: string,
userId = ""
): Promise<{ txData: TxData; payoutUrl: string }> => {
Expand All @@ -65,12 +66,15 @@ export const generatePermit2Signature = async (
const provider = new ethers.providers.JsonRpcProvider(rpc);
const adminWallet = new ethers.Wallet(privateKey, provider);

const tokenContract = new ethers.Contract(paymentToken, ERC20ABI, provider);
const decimals = await tokenContract.decimals();

const permitTransferFromData: PermitTransferFrom = {
permitted: {
// token we are permitting to be transferred
token: paymentToken,
// amount we are permitting to be transferred
amount: ethers.utils.parseUnits(amountInEth.toString(), 18),
amount: ethers.utils.parseUnits(amountInTokens.toString(), decimals),
},
// who can transfer the tokens
spender: spender,
Expand Down Expand Up @@ -101,9 +105,12 @@ export const generatePermit2Signature = async (

const base64encodedTxData = Buffer.from(JSON.stringify(txData)).toString("base64");

const payoutUrl = `${permitBaseUrl}?claim=${base64encodedTxData}&network=${networkId}`;
logger.info(`Generated permit2 url: ${payoutUrl}`);
return { txData, payoutUrl };
const payoutUrl = new URL(permitBaseUrl);
payoutUrl.searchParams.append("claim", base64encodedTxData);
payoutUrl.searchParams.append("network", networkId.toString());
logger.info(`Generated permit2 of amount ${amountInTokens} for user ${spender}, url: ${payoutUrl}`);

return { txData, payoutUrl: payoutUrl.toString() };
};

export const savePermitToDB = async (bountyHunterId: number, txData: TxData): Promise<Permit> => {
Expand Down
Loading