From e42794512c1c5fd69ee1faa563507c558674687f Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 14 Oct 2025 10:24:36 -0300 Subject: [PATCH 1/6] fix default values --- scripts/deploy-multichain.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/deploy-multichain.js b/scripts/deploy-multichain.js index 4470187cc3..7d84ac0844 100644 --- a/scripts/deploy-multichain.js +++ b/scripts/deploy-multichain.js @@ -260,9 +260,9 @@ async function main(option = 0) { isDefault: false, color: "#29b6af", lockAmountForNetworkCreation: DEPLOY_LOCK_AMOUNT_FOR_NETWORK_CREATION, - networkCreationFeePercentage: DEPLOY_LOCK_FEE_PERCENTAGE || DEFAULT_LOCK_FEE_PERCENTAGE / DIVISOR, - closeFeePercentage: DEPLOY_CLOSE_BOUNTY_FEE || DEFAULT_LOCK_FEE_PERCENTAGE / DIVISOR, - cancelFeePercentage: DEPLOY_CANCEL_BOUNTY_FEE || DEFAULT_LOCK_FEE_PERCENTAGE / DIVISOR, + networkCreationFeePercentage: (+DEPLOY_LOCK_FEE_PERCENTAGE || DEFAULT_LOCK_FEE_PERCENTAGE) / DIVISOR, + closeFeePercentage: (+DEPLOY_CLOSE_BOUNTY_FEE || DEFAULT_CLOSE_BOUNTY_FEE) / DIVISOR, + cancelFeePercentage: (+DEPLOY_CANCEL_BOUNTY_FEE || DEFAULT_CANCEL_BOUNTY_FEE) / DIVISOR, icon: "QmZ8dSeJp9pZn2TFy2gp7McfMj9HapqnPW3mwnnrDLKtZs", } }); From 30398c4803328948e784b36f710ef8f7ce4fa6e3 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 14 Oct 2025 10:24:40 -0300 Subject: [PATCH 2/6] ignore logs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e866c60bc9..584c479389 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ yarn-debug.log* yarn-error.log* .vscode/ .editorconfig +logs From e86332b9322ff900d324d41643e889c8312e6a06 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 14 Oct 2025 10:24:51 -0300 Subject: [PATCH 3/6] add missing event type --- helpers/analytic-events.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers/analytic-events.ts b/helpers/analytic-events.ts index e42ea71c43..1609ef7dd4 100644 --- a/helpers/analytic-events.ts +++ b/helpers/analytic-events.ts @@ -11,4 +11,5 @@ export const analyticEvents: AnalyticEvents = { create_task_approve_amount: [analytic("ga4")], create_pre_task: [analytic("ga4")], created_task: [analytic("ga4")], + user_logged_in: [analytic("ga4")], } \ No newline at end of file From cb3528100ed21bc169c045c0a83f60e4b8c16cc6 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 14 Oct 2025 13:43:28 -0300 Subject: [PATCH 4/6] adjust value to be divisible by 100 --- .../create-bounty/token-amount/controller.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/components/bounty/create-bounty/token-amount/controller.tsx b/components/bounty/create-bounty/token-amount/controller.tsx index 1c83fc1642..c886963638 100644 --- a/components/bounty/create-bounty/token-amount/controller.tsx +++ b/components/bounty/create-bounty/token-amount/controller.tsx @@ -1,6 +1,7 @@ import {useEffect, useState} from "react"; import {NumberFormatValues} from "react-number-format"; +import { fromSmartContractDecimals, toSmartContractDecimals } from "@taikai/dappkit"; import BigNumber from "bignumber.js"; import {useDebouncedCallback} from "use-debounce"; @@ -139,15 +140,22 @@ export default function CreateBountyTokenAmount({ return; } - const amountOfType = + let totalAmount = BigNumber(type === "reward" ? _calculateTotalAmountFromGivenReward(value) : value); + + const contractTotalAmount = +toSmartContractDecimals(totalAmount.toString(), decimals); + + if (contractTotalAmount % 100 !== 0) { + const adjustedContractTotalAmount = Math.ceil(contractTotalAmount / 100) * 100; + totalAmount = BigNumber(fromSmartContractDecimals(adjustedContractTotalAmount, decimals)); + } const initialDistributions = calculateDistributedAmounts( chain.closeFeePercentage, mergeCreatorFeeShare, proposerFeeShare, - amountOfType, + totalAmount, [ { recipient: currentUser?.walletAddress, @@ -166,16 +174,21 @@ export default function CreateBountyTokenAmount({ const _distributions = { totalServiceFees, ...initialDistributions} if(type === 'reward'){ - const total = BigNumber(_calculateTotalAmountFromGivenReward(value)); - updateIssueAmount(handleNumberFormat(total)) - if (amountIsGtBalance(total.toNumber(), tokenBalance) && !isFunding) { + updateIssueAmount(handleNumberFormat(totalAmount)) + const rewardValue = BigNumber(calculateRewardAmountGivenTotalAmount(totalAmount.toNumber())); + + if (rewardValue !== value) { + setPreviewAmount(handleNumberFormat(rewardValue)); + } + + if (amountIsGtBalance(totalAmount.toNumber(), tokenBalance) && !isFunding) { setInputError("bounty:errors.exceeds-allowance"); sethasAmountError(true); } } if(type === 'total'){ - const rewardValue = BigNumber(calculateRewardAmountGivenTotalAmount(value)); + const rewardValue = BigNumber(calculateRewardAmountGivenTotalAmount(totalAmount.toNumber())); setPreviewAmount(handleNumberFormat(rewardValue)); if (rewardValue.isLessThan(BigNumber(currentToken?.minimum))) { From 72adb2b27ddb3c3c1c3586f50442e7445d5ff72e Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 14 Oct 2025 15:48:15 -0300 Subject: [PATCH 5/6] clear inputs to ensure recalculation --- components/pages/task/create-task/controller.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/pages/task/create-task/controller.tsx b/components/pages/task/create-task/controller.tsx index 91a845196c..8e2bd57164 100644 --- a/components/pages/task/create-task/controller.tsx +++ b/components/pages/task/create-task/controller.tsx @@ -466,7 +466,11 @@ export default function CreateTaskPage({ function handleUpdateToken(e: Token, type: 'transactional' | 'reward') { const ERC20 = type === 'transactional' ? transactionalERC20 : rewardERC20 const setToken = type === 'transactional' ? setTransactionalToken : setRewardToken - setToken(e) + setToken(e); + setIssueAmount(ZeroNumberFormatValues); + setRewardAmount(ZeroNumberFormatValues); + setPreviewAmount(ZeroNumberFormatValues); + setDistributions(undefined); ERC20.setAddress(e.address) } From ed10df07c8c774f9de197c6bce5c502aa187524d Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Mon, 20 Oct 2025 15:41:05 -0300 Subject: [PATCH 6/6] add tests and fix --- .../token-amount.controller.test.tsx | 145 ++++++++++++++++++ .../create-bounty/token-amount/controller.tsx | 15 +- 2 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 __tests__/components/bounty/create-bounty/token-amount/token-amount.controller.test.tsx diff --git a/__tests__/components/bounty/create-bounty/token-amount/token-amount.controller.test.tsx b/__tests__/components/bounty/create-bounty/token-amount/token-amount.controller.test.tsx new file mode 100644 index 0000000000..0befff60e7 --- /dev/null +++ b/__tests__/components/bounty/create-bounty/token-amount/token-amount.controller.test.tsx @@ -0,0 +1,145 @@ +import { NumberFormatValues } from "react-number-format"; + +import { toSmartContractDecimals } from "@taikai/dappkit/dist/src/utils/numbers"; +import { fireEvent } from "@testing-library/dom"; +import BigNumber from "bignumber.js"; + +import CreateBountyTokenAmount, { + ZeroNumberFormatValues, +} from "components/bounty/create-bounty/token-amount/controller"; + +import { Network } from "interfaces/network"; +import { DistributionsProps } from "interfaces/proposal"; +import { Token } from "interfaces/token"; + +import { render } from "__tests__/utils/custom-render"; + +jest.mock("x-hooks/use-bepro", () => () => ({})); + +const mockCurrentToken: Token = { + address: "0x1234567890123456789012345678901234567890", + name: "Mock Token", + symbol: "MOCK", +}; + +const mockCurrentNetwork: Network = { + id: 1, + name: "Mock Network", + updatedAt: new Date(), + createdAt: new Date(), + description: "Mock Description", + networkAddress: "0x1234567890123456789012345678901234567890", + creatorAddress: "0x1234567890123456789012345678901234567890", + openBounties: 0, + totalBounties: 0, + allowCustomTokens: false, + councilMembers: [], + banned_domains: [], + closeTaskAllowList: [], + allow_list: [], + mergeCreatorFeeShare: 0.05, + proposerFeeShare: 0.5, + chain: { + chainId: 1, + chainRpc: "https://mock-rpc.com", + name: "Mock Chain", + chainName: "Mock Chain", + chainShortName: "MOCK", + chainCurrencySymbol: "MOCK", + chainCurrencyDecimals: "18", + chainCurrencyName: "Mock Token", + blockScanner: "https://mock-scanner.com", + registryAddress: "0x1234567890123456789012345678901234567890", + eventsApi: "https://mock-events.com", + isDefault: true, + closeFeePercentage: 10, + cancelFeePercentage: 1.0, + networkCreationFeePercentage: 0.5, + }, +}; + +describe("TokenAmountController", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("fuzzes total input and ensures internal adjusted total is divisible by 100 in contract units", () => { + let executions = 0; + + let issueAmount = ZeroNumberFormatValues; + let previewAmount = ZeroNumberFormatValues; + + const setPreviewAmount = jest.fn((value: NumberFormatValues) => { + previewAmount = value; + }); + const updateIssueAmount = jest.fn((value: NumberFormatValues) => { + issueAmount = value; + }); + + while (executions < 100) { + const decimals = Math.floor(Math.random() * 13) + 6; + const randomValue = parseFloat((Math.random() * 499999 + 1).toFixed(decimals)); + + const result = render(); + + const totalAmountInput = result.getAllByTestId("total-amount-input")[0]; + + const valueString = randomValue.toString(); + + fireEvent.change(totalAmountInput, { target: { value: valueString } }); + + result.rerender(); + + const newValueContract = toSmartContractDecimals(issueAmount.value, decimals); + + expect(Number(BigInt(newValueContract) % BigInt(100))).toBe(0); + + jest.clearAllMocks(); + result.unmount(); + + executions += 1; + } + }); +}); diff --git a/components/bounty/create-bounty/token-amount/controller.tsx b/components/bounty/create-bounty/token-amount/controller.tsx index c886963638..d19c2bfd1e 100644 --- a/components/bounty/create-bounty/token-amount/controller.tsx +++ b/components/bounty/create-bounty/token-amount/controller.tsx @@ -1,7 +1,7 @@ import {useEffect, useState} from "react"; import {NumberFormatValues} from "react-number-format"; -import { fromSmartContractDecimals, toSmartContractDecimals } from "@taikai/dappkit"; +import { fromSmartContractDecimals, toSmartContractDecimals } from "@taikai/dappkit/dist/src/utils/numbers"; import BigNumber from "bignumber.js"; import {useDebouncedCallback} from "use-debounce"; @@ -15,7 +15,7 @@ import {useUserStore} from "x-hooks/stores/user/user.store"; import CreateBountyTokenAmountView from "./view"; -const ZeroNumberFormatValues = { +export const ZeroNumberFormatValues = { value: "", formattedValue: "", floatValue: 0, @@ -102,8 +102,8 @@ export default function CreateBountyTokenAmount({ } const handleNumberFormat = (v: BigNumber) => ({ - value: v.decimalPlaces(5, 0).toFixed(), - floatValue: v.toNumber(), + value: v.decimalPlaces(Math.min(10, decimals), 0).toFixed(), + floatValue: +v.decimalPlaces(Math.min(10, decimals), 0).toFixed(), formattedValue: v.decimalPlaces(Math.min(10, decimals), 0).toFixed() }); @@ -191,12 +191,15 @@ export default function CreateBountyTokenAmount({ const rewardValue = BigNumber(calculateRewardAmountGivenTotalAmount(totalAmount.toNumber())); setPreviewAmount(handleNumberFormat(rewardValue)); + if (totalAmount.toFixed() !== value.toString()) { + updateIssueAmount(handleNumberFormat(totalAmount)); + } + if (rewardValue.isLessThan(BigNumber(currentToken?.minimum))) { setInputError("bounty:errors.exceeds-minimum-amount"); sethasAmountError(true); } } - setDistributions(_distributions); } @@ -236,8 +239,8 @@ export default function CreateBountyTokenAmount({ setInputError(""); sethasAmountError(false); } - debouncedDistributionsUpdater(values.value, type); setType(handleNumberFormat(BigNumber(values.value))); + debouncedDistributionsUpdater(values.value, type); } }