diff --git a/packages/contracts/test/contracts/token/SaleMaxTargetReached.d.sol b/packages/contracts/test/contracts/token/SaleMaxTargetReached.d.sol index 74d2b59f..6ef3f229 100644 --- a/packages/contracts/test/contracts/token/SaleMaxTargetReached.d.sol +++ b/packages/contracts/test/contracts/token/SaleMaxTargetReached.d.sol @@ -57,45 +57,45 @@ contract SaleMaxTargetReachedTest is Test { start = endRegistration + 1 * day + 1000; end = start + 1 * day; - paymentToken = new MockERC20("usdc", "usdc", 18); + paymentToken = new MockERC20("usdc", "usdc", 6); sale = new Sale( address(paymentToken), - 0.2 ether, + 0.2 * 1e6, start, end, 2500000 ether, - 500000 ether, - 1000000 ether, + 500000 * 1e6, + 1000000 * 1e6, startRegistration, endRegistration ); sale.setMerkleRoot(merkleRoot); - sale.setMinContribution(sale.paymentTokenToToken(100 ether)); + sale.setMinContribution(sale.paymentTokenToToken(100 * 1e6)); - paymentToken.mint(alice, 2000000 ether); - paymentToken.mint(bob, 2000000 ether); + paymentToken.mint(alice, 2000000 * 1e6); + paymentToken.mint(bob, 2000000 * 1e6); vm.stopPrank(); } function test_BuyRevertsWhenMaxTargetReached() public { vm.startPrank(owner); - sale.setMaxTarget(1 ether); - sale.setMinContribution(1 ether); + sale.setMaxTarget(1 * 1e6); + sale.setMinContribution(1 * 1e6); vm.stopPrank(); vm.warp(sale.start()); vm.startPrank(alice); - paymentToken.approve(address(sale), 2 ether); - sale.buy(sale.paymentTokenToToken(2 ether), merkleProofs[alice]); + paymentToken.approve(address(sale), 2 * 1e6); + sale.buy(sale.paymentTokenToToken(2 * 1e6), merkleProofs[alice]); vm.stopPrank(); vm.startPrank(bob); - paymentToken.approve(address(sale), 1 ether); + paymentToken.approve(address(sale), 1 * 1e6); - uint256 amount = sale.paymentTokenToToken(1 ether); + uint256 amount = sale.paymentTokenToToken(1 * 1e6); vm.expectRevert(Sale.MaxContributorsReached.selector); sale.buy(amount, merkleProofs[bob]); @@ -104,16 +104,20 @@ contract SaleMaxTargetReachedTest is Test { } function test_AllocationAfterMaxTargetReached() public { + vm.startPrank(owner); + sale.setMaxTarget(2000000 * 1e6); + sale.setMinContribution(1000000 * 1e6); + vm.warp(sale.start()); vm.startPrank(alice); - paymentToken.approve(address(sale), 2000000 ether); - sale.buy(sale.paymentTokenToToken(2000000 ether), merkleProofs[alice]); + paymentToken.approve(address(sale), 2000000 * 1e6); + sale.buy(sale.paymentTokenToToken(2000000 * 1e6), merkleProofs[alice]); vm.stopPrank(); vm.startPrank(bob); - paymentToken.approve(address(sale), 2000000 ether); - sale.buy(sale.paymentTokenToToken(2000000 ether), merkleProofs[bob]); + paymentToken.approve(address(sale), 2000000 * 1e6); + sale.buy(sale.paymentTokenToToken(2000000 * 1e6), merkleProofs[bob]); vm.stopPrank(); vm.warp(sale.end() + 1000); diff --git a/packages/contracts/test/contracts/token/SaleMinTargetNotReached.d.sol b/packages/contracts/test/contracts/token/SaleMinTargetNotReached.d.sol index c596ffcb..38c5174c 100644 --- a/packages/contracts/test/contracts/token/SaleMinTargetNotReached.d.sol +++ b/packages/contracts/test/contracts/token/SaleMinTargetNotReached.d.sol @@ -59,24 +59,24 @@ contract SaleMinTargetNotReachedTest is Test { start = endRegistration + 1 * DAY + 1000; end = start + 1 * DAY; - paymentToken = new MockERC20("USDC", "USDC", 18); + paymentToken = new MockERC20("USDC", "USDC", 6); sale = new Sale( address(paymentToken), - 0.2 ether, + 0.2 * 1e6, start, end, 2500000 ether, - 500000 ether, - 1000000 ether, + 500000 * 1e6, + 1000000 * 1e6, startRegistration, endRegistration ); sale.setMerkleRoot(merkleRoot); - sale.setMinContribution(sale.paymentTokenToToken(100 ether)); + sale.setMinContribution(100 * 1e6); - paymentToken.mint(alice, 300000 ether); - paymentToken.mint(bob, 300000 ether); + paymentToken.mint(alice, 300000 * 1e6); + paymentToken.mint(bob, 300000 * 1e6); vm.stopPrank(); } @@ -85,35 +85,35 @@ contract SaleMinTargetNotReachedTest is Test { vm.warp(sale.start()); vm.startPrank(alice); - paymentToken.approve(address(sale), 300000 ether); - sale.buy(sale.paymentTokenToToken(300000 ether), merkleProofs[alice]); + paymentToken.approve(address(sale), 300000 * 1e6); + sale.buy(sale.paymentTokenToToken(300000 * 1e6), merkleProofs[alice]); vm.stopPrank(); vm.startPrank(bob); - paymentToken.approve(address(sale), 200000 ether); - sale.buy(sale.paymentTokenToToken(200000 ether), merkleProofs[bob]); + paymentToken.approve(address(sale), 200000 * 1e6); + sale.buy(sale.paymentTokenToToken(200000 * 1e6), merkleProofs[bob]); vm.stopPrank(); - require(paymentToken.balanceOf(address(sale)) == 500000 ether); + require(paymentToken.balanceOf(address(sale)) == 500000 * 1e6); vm.startPrank(bob); - paymentToken.approve(address(sale), 100000 ether); - sale.buy(sale.paymentTokenToToken(100000 ether), merkleProofs[bob]); + paymentToken.approve(address(sale), 100000 * 1e6); + sale.buy(sale.paymentTokenToToken(100000 * 1e6), merkleProofs[bob]); vm.stopPrank(); vm.warp(sale.end() + 1000); require( sale.totalUncappedAllocations() == - sale.paymentTokenToToken(600000 ether) + sale.paymentTokenToToken(600000 * 1e6) ); require( sale.allocation(address(alice)) == - ((300000 ether / sale.currentTokenPrice()) * 1 ether) + (((300000 * 1e6) / sale.currentTokenPrice()) * 1 ether) ); require( sale.allocation(address(bob)) == - ((300000 ether / sale.currentTokenPrice()) * 1 ether) + (((300000 * 1e6) / sale.currentTokenPrice()) * 1 ether) ); } @@ -123,44 +123,47 @@ contract SaleMinTargetNotReachedTest is Test { require(bytes32(sale.merkleRoot()) == bytes32(merkleRoot)); vm.startPrank(alice); - paymentToken.approve(address(sale), 200 ether); - sale.buy(sale.paymentTokenToToken(200 ether), merkleProofs[alice]); + paymentToken.approve(address(sale), 200 * 1e6); + sale.buy(sale.paymentTokenToToken(200 * 1e6), merkleProofs[alice]); vm.stopPrank(); - require(paymentToken.balanceOf(address(sale)) == 200 ether); + require(paymentToken.balanceOf(address(sale)) == 200 * 1e6); vm.startPrank(bob); - paymentToken.approve(address(sale), 100 ether); - sale.buy(sale.paymentTokenToToken(100 ether), merkleProofs[bob]); + paymentToken.approve(address(sale), 100 * 1e6); + sale.buy(sale.paymentTokenToToken(100 * 1e6), merkleProofs[bob]); vm.stopPrank(); require( sale.totalUncappedAllocations() == - sale.paymentTokenToToken(300 ether) + sale.paymentTokenToToken(300 * 1e6) + ); + require( + sale.totalUncappedAllocations() < + sale.paymentTokenToToken(sale.minTarget()) ); - require(sale.totalUncappedAllocations() < sale.minTarget()); vm.warp(sale.end() + 1000); - require(paymentToken.balanceOf(address(sale)) == 300 ether); + require(paymentToken.balanceOf(address(sale)) == 300 * 1e6); vm.prank(owner); sale.setIndividualCap(1000 ether); require(sale.allocation(alice) == 0); - require(sale.refundAmount(alice) == 200 ether); + require(sale.refundAmount(alice) == 200 * 1e6); vm.prank(alice); sale.refund(alice); - require(paymentToken.balanceOf(address(sale)) == 100 ether); + require(paymentToken.balanceOf(address(sale)) == 100 * 1e6); require(sale.allocation(bob) == 0); - require(sale.refundAmount(bob) == 100 ether); + require(sale.refundAmount(bob) == 100 * 1e6); vm.prank(bob); sale.refund(bob); - require(paymentToken.balanceOf(address(sale)) == 0 ether); + require(paymentToken.balanceOf(address(sale)) == 0 * 1e6); } } diff --git a/packages/web-app/app/_lib/hooks.tsx b/packages/web-app/app/_lib/hooks.tsx index 3a3be0b7..76449903 100644 --- a/packages/web-app/app/_lib/hooks.tsx +++ b/packages/web-app/app/_lib/hooks.tsx @@ -21,7 +21,7 @@ import { useReadCtzndSaleStart, useReadCtzndSaleEnd, } from '@/wagmi.generated'; -import { formatEther, parseEther } from 'viem'; +import { formatEther, formatUnits, parseUnits } from 'viem'; import { sepolia } from 'viem/chains'; export const useKycCredential = () => { @@ -209,7 +209,7 @@ export const useCtzndPaymentTokenAllowance = (userAddress: `0x${string}`) => { export const useContributeToCtznd = () => { const [amount, setAmount] = useState(0); - const amountInWei = useMemo(() => parseEther(amount.toString()), [amount]); + const amountInWei = useMemo(() => parseUnits(amount.toString(), 6), [amount]); const { formattedValue: maxAmount } = usePaymentTokenBalance(); const { data: tokensToBuyInWei, error: tokenError } = useReadCtzndSalePaymentTokenToToken({ @@ -272,8 +272,8 @@ export const useCtzndSaleCapStatus = () => { const { data: minTarget } = useReadCtzndSaleMinTarget(); const investedValue = Number(totalInvested); - const maxValue = maxTarget ? Number(formatEther(maxTarget)) : undefined; - const minValue = minTarget ? Number(formatEther(minTarget)) : undefined; + const maxValue = maxTarget ? Number(formatUnits(maxTarget, 6)) : undefined; + const minValue = minTarget ? Number(formatUnits(minTarget, 6)) : undefined; if (maxValue === undefined || minValue === undefined) { return 'loading'; diff --git a/packages/web-app/app/_lib/queries.tsx b/packages/web-app/app/_lib/queries.tsx index 34a65669..c85bdf7f 100644 --- a/packages/web-app/app/_lib/queries.tsx +++ b/packages/web-app/app/_lib/queries.tsx @@ -20,7 +20,7 @@ import { useReadCtzndSaleTotalUncappedAllocations, useReadCtzndSaleUncappedAllocation, } from '@/wagmi.generated'; -import { formatEther, formatUnits } from 'viem'; +import { formatEther, formatUnits, parseEther } from 'viem'; import { computeRisingTideCap } from '../_server/risingTide/risingtide'; import { appSignal } from '../app-signal'; import { useEffect } from 'react'; @@ -289,7 +289,7 @@ export const useTotalInvestedUsdcCtznd = () => { }); const usdcValue = - ctzndTokensSold && tokensInvested ? formatEther(tokensInvested) : '0'; + ctzndTokensSold && tokensInvested ? formatUnits(tokensInvested, 6) : '0'; return usdcValue; }; @@ -302,7 +302,7 @@ export const useUserTotalInvestedUsdcCtznd = (address: `0x${string}`) => { }, }); const { data: tokensInvested } = useReadCtzndSaleTokenToPaymentToken({ - args: [ctzndTokensSold || 0n], + args: [parseEther(formatUnits(ctzndTokensSold || 0n, 6)) || 0n], }); const usdcValue = @@ -315,10 +315,10 @@ export const useCtzndMinContributionUsdc = () => { const { data: min } = useReadCtzndSaleMinContribution(); const { data: minUsdc } = useReadCtzndSaleTokenToPaymentToken({ - args: [min || 0n], + args: [parseEther(formatUnits(min || 0n, 6)) || 0n], }); - const usdcValue = min && minUsdc ? formatEther(minUsdc) : '0'; + const usdcValue = min && minUsdc ? formatUnits(minUsdc, 6) : '0'; return usdcValue; }; diff --git a/packages/web-app/app/_server/sales.ts b/packages/web-app/app/_server/sales.ts index 47db4966..0ff98571 100644 --- a/packages/web-app/app/_server/sales.ts +++ b/packages/web-app/app/_server/sales.ts @@ -4,6 +4,7 @@ import { ctzndSaleAbi, ctzndSaleAddress } from '@/wagmi.generated'; import { createWalletClient, formatEther, + formatUnits, getContract, http, publicActions, @@ -83,8 +84,8 @@ export const saleDetails = async (): Promise< maxTarget: contractResults[3], start: contractResults[4] * 1000n, end: contractResults[5] * 1000n, - minContribution: formatEther(contractResults[6]), - maxContribution: formatEther(contractResults[7]), + minContribution: formatUnits(contractResults[6], 6), + maxContribution: formatUnits(contractResults[7], 6), totalTokensForSale: formatEther(contractResults[8]), startRegistration: contractResults[9] * 1000n, endRegistration: contractResults[10] * 1000n, diff --git a/packages/web-app/app/_ui/components/dialogs/contribute-dialog/index.tsx b/packages/web-app/app/_ui/components/dialogs/contribute-dialog/index.tsx index e9efaf40..d5eabb30 100644 --- a/packages/web-app/app/_ui/components/dialogs/contribute-dialog/index.tsx +++ b/packages/web-app/app/_ui/components/dialogs/contribute-dialog/index.tsx @@ -2,7 +2,7 @@ import { useCtzndPaymentTokenAllowance, useEffectSafe } from '@/app/_lib/hooks'; import { Dialog } from '@headlessui/react'; -import { formatEther } from 'viem'; +import { formatEther, formatUnits } from 'viem'; import { useBuyCtzndTokens, useSetPaymentTokenAllowance, @@ -218,7 +218,7 @@ export function ContributeDialog({
Current allowed value:
- {allowance ? formatEther(allowance) : 0} USDC + {allowance ? formatUnits(allowance, 6) : 0} USDC
diff --git a/packages/web-app/app/_ui/my-projects/index.tsx b/packages/web-app/app/_ui/my-projects/index.tsx index 312d26a6..e987202b 100644 --- a/packages/web-app/app/_ui/my-projects/index.tsx +++ b/packages/web-app/app/_ui/my-projects/index.tsx @@ -11,7 +11,7 @@ import { MyProjectSkeleton } from './my-project'; import { useReadCtzndSaleInvestorCount } from '@/wagmi.generated'; import { useAccount } from 'wagmi'; import { useUserTotalInvestedUsdcCtznd } from '@/app/_lib/queries'; -import { formatEther } from 'viem'; +import { formatUnits } from 'viem'; const ProjectRow = ({ logo, @@ -26,8 +26,8 @@ const ProjectRow = ({ const totalContributions = contributions ? contributions.toString() : 0; const totalUsdc = useUserTotalInvestedUsdcCtznd(address!); const targetedRaise = usdRange( - BigInt(formatEther(minTarget)), - BigInt(formatEther(maxTarget)), + BigInt(formatUnits(minTarget, 6)), + BigInt(formatUnits(maxTarget, 6)), ); return ( {

Rising Tide Mechanism

{saleCapStatus === 'above' - ? 'ON (max. target reached' + ? 'ON (max. target reached)' : 'OFF (total contributed below max.)'}
diff --git a/packages/web-app/app/_ui/project/contribution/DataFields.tsx b/packages/web-app/app/_ui/project/contribution/DataFields.tsx index 8ce86a09..35eae28a 100644 --- a/packages/web-app/app/_ui/project/contribution/DataFields.tsx +++ b/packages/web-app/app/_ui/project/contribution/DataFields.tsx @@ -2,7 +2,7 @@ import { useReadCtzndSaleMaxTarget, useReadCtzndSaleTotalTokensForSale, } from '@/wagmi.generated'; -import { formatEther } from 'viem'; +import { formatEther, formatUnits } from 'viem'; import { number } from '../../utils/intl-formaters/number'; import { useCtzndMinContributionUsdc } from '@/app/_lib/queries'; import { useTotalInvestedUsdcCtznd } from '@/app/_lib/queries'; @@ -14,7 +14,7 @@ const useMaxParticipants = () => { const { data: maxTarget, isLoading: targetLoading } = useReadCtzndSaleMaxTarget(); const min = useCtzndMinContributionUsdc(); - const targetValue = maxTarget ? Number(formatEther(maxTarget)) : undefined; + const targetValue = maxTarget ? Number(formatUnits(maxTarget, 6)) : undefined; const minValue = min ? Number(min) : undefined; return { diff --git a/packages/web-app/app/_ui/project/sale-status.tsx b/packages/web-app/app/_ui/project/sale-status.tsx index 999ff97e..c35aa41c 100644 --- a/packages/web-app/app/_ui/project/sale-status.tsx +++ b/packages/web-app/app/_ui/project/sale-status.tsx @@ -3,7 +3,7 @@ import { useReadCtzndSaleMaxTarget, } from '@/wagmi.generated'; import { usdValue } from '../utils/intl-formaters/usd-value'; -import { formatEther } from 'viem'; +import { formatEther, formatUnits } from 'viem'; import clsx from 'clsx'; import { number } from '../utils/intl-formaters/number'; import { useTotalInvestedUsdcCtznd } from '@/app/_lib/queries'; @@ -186,7 +186,7 @@ const Info = () => { export const SaleStatus = ({ hasGrant }: { hasGrant: boolean }) => { const { data: maxTarget } = useReadCtzndSaleMaxTarget(); const totalCommitted = useTotalInvestedUsdcCtznd(); - const maxValue = maxTarget ? Number(formatEther(maxTarget)) : 0; + const maxValue = maxTarget ? Number(formatUnits(maxTarget, 6)) : 0; const { data: investorCount } = useReadCtzndSaleInvestorCount({ query: { refetchInterval: 1000 * 10, // 10 seconds diff --git a/packages/web-app/app/_ui/projects/project-card.tsx b/packages/web-app/app/_ui/projects/project-card.tsx index 91f50582..e58b0150 100644 --- a/packages/web-app/app/_ui/projects/project-card.tsx +++ b/packages/web-app/app/_ui/projects/project-card.tsx @@ -10,7 +10,7 @@ import { Status } from './status'; import { usdValue } from '../utils/intl-formaters/usd-value'; import { usdRange } from '../utils/intl-formaters/usd-range'; import { shortDateRange } from '../utils/intl-formaters/date-range'; -import { formatEther } from 'viem/utils'; +import { formatUnits } from 'viem/utils'; import { useCtzndMinContributionUsdc, useTotalInvestedUsdcCtznd, @@ -25,8 +25,8 @@ const Upcoming = ({ start, }: TProjectSaleDetails) => { const targetedRaise = usdRange( - BigInt(formatEther(minTarget)), - BigInt(formatEther(maxTarget)), + BigInt(formatUnits(minTarget, 6)), + BigInt(formatUnits(maxTarget, 6)), ); const registerPeriod = shortDateRange(startRegistration, endRegistration); const { days, hours, minutes, seconds } = useCountdown(start); @@ -60,8 +60,8 @@ const FullData = ({ totalTokensForSale, }: TProjectSaleDetails) => { const targetedRaise = usdRange( - BigInt(formatEther(minTarget)), - BigInt(formatEther(maxTarget)), + BigInt(formatUnits(minTarget, 6)), + BigInt(formatUnits(maxTarget, 6)), ); const minPrice = useCtzndMinContributionUsdc(); const totalTokens = new Intl.NumberFormat('default').format( diff --git a/packages/web-app/app/_ui/utils/calculateTokenPrice.ts b/packages/web-app/app/_ui/utils/calculateTokenPrice.ts index fa5becf1..fe87cefe 100644 --- a/packages/web-app/app/_ui/utils/calculateTokenPrice.ts +++ b/packages/web-app/app/_ui/utils/calculateTokenPrice.ts @@ -7,8 +7,10 @@ export const calculateTokenPrice = (totalContributed: number): number => { return 0.4; } - return ( - 0.2 + - (0.4 - (0.2 * (Number(totalContributed) - 500000)) / (1_000_000 - 500_000)) + return Number( + ( + 0.2 + + (0.2 * (Number(totalContributed) - 500000)) / (1_000_000 - 500_000) + ).toFixed(2), ); };