diff --git a/packages/hardhat/contracts/Market.sol b/packages/hardhat/contracts/Market.sol index 7ffe6ff..bcef620 100644 --- a/packages/hardhat/contracts/Market.sol +++ b/packages/hardhat/contracts/Market.sol @@ -29,7 +29,7 @@ error Market__UpkeepNotNeeded(); contract Market is VRFConsumerBaseV2, AutomationCompatibleInterface, Ownable { enum RoundState { OPEN, - CLOSING, + // CLOSING, CALCULATING, CLOSED } @@ -211,27 +211,27 @@ contract Market is VRFConsumerBaseV2, AutomationCompatibleInterface, Ownable { * @notice caller's GLD balance must be fully allocated in vaults */ - function startClosing() external { - require( - isPlayer(msg.sender), - "Only active players can start the closing process" - ); - require( - currentRound.state == RoundState.OPEN, - "Current round must be open to start the closing process" - ); - require( - token.balanceOf(msg.sender) == 0, - "Your GLD must be fully allocated in order to start the closing process" - ); - - currentRound.state = RoundState.CLOSING; - emit RoundClosing( - currentRound.number, - currentContest.number, - block.timestamp - ); - } + // function startClosing() external { + // require( + // isPlayer(msg.sender), + // "Only active players can start the closing process" + // ); + // require( + // currentRound.state == RoundState.OPEN, + // "Current round must be open to start the closing process" + // ); + // require( + // token.balanceOf(msg.sender) == 0, + // "Your GLD must be fully allocated in order to start the closing process" + // ); + + // currentRound.state = RoundState.CLOSING; + // emit RoundClosing( + // currentRound.number, + // currentContest.number, + // block.timestamp + // ); + // } /** Owner of contract can pause/unpause the game * @dev prevents upkeep from triggering indefinitely @@ -265,13 +265,11 @@ contract Market is VRFConsumerBaseV2, AutomationCompatibleInterface, Ownable { } bool contestOpen = currentContest.state == ContestState.OPEN; - bool roundClosing = currentRound.state == RoundState.CLOSING; + // bool roundClosing = currentRound.state == RoundState.CLOSING; bool hasPlayers = players.length > 0; bool maxTimePassed = (block.timestamp - lastTimestamp) > roundInterval; - upkeepNeeded = (contestOpen && - hasPlayers && - (roundClosing || maxTimePassed)); + upkeepNeeded = (contestOpen && hasPlayers && maxTimePassed); return (upkeepNeeded, "0x0"); } @@ -305,7 +303,7 @@ contract Market is VRFConsumerBaseV2, AutomationCompatibleInterface, Ownable { ); } - function emergencyRequestRandomWords() external onlyOwner { + function manuallyRequestRandomWords() external onlyOwner { vrfCoordinator.requestRandomWords( keyHash, subscriptionId, @@ -313,6 +311,12 @@ contract Market is VRFConsumerBaseV2, AutomationCompatibleInterface, Ownable { callbackGasLimit, numWords ); + + emit RoundCalculating( + currentRound.number, + currentContest.number, + block.timestamp + ); } /** Triggered by the VRF Coordinator that gets triggered by performUpkeep @@ -355,19 +359,19 @@ contract Market is VRFConsumerBaseV2, AutomationCompatibleInterface, Ownable { } // emit event with each players updated total assets - // for (uint i = 0; i < players.length; i++) { - // address player = players[i]; - // uint256 totalAssets = lowRiskVault.maxWithdraw(player) + - // mediumRiskVault.maxWithdraw(player) + - // highRiskVault.maxWithdraw(player) + - // token.balanceOf(player); - // emit PlayerTotalAssetUpdate( - // currentContest.number, - // currentRound.number, - // player, - // totalAssets - // ); - // } + for (uint i = 0; i < players.length; i++) { + address player = players[i]; + uint256 totalAssets = lowRiskVault.maxWithdraw(player) + + mediumRiskVault.maxWithdraw(player) + + highRiskVault.maxWithdraw(player) + + token.balanceOf(player); + emit PlayerTotalAssetUpdate( + currentContest.number, + currentRound.number, + player, + totalAssets + ); + } } /** Uses random value to distribute/take tokens to/from the vaults diff --git a/packages/nextjs/components/vaults-of-fortune/Leaderboard.tsx b/packages/nextjs/components/vaults-of-fortune/Leaderboard.tsx index 1c9daaf..70edf82 100644 --- a/packages/nextjs/components/vaults-of-fortune/Leaderboard.tsx +++ b/packages/nextjs/components/vaults-of-fortune/Leaderboard.tsx @@ -1,47 +1,45 @@ +// import { useFetchPlayersAssets } from "~~/hooks/vaults-of-fortune"; +import { useEffect, useState } from "react"; import { formatEther } from "viem"; import { Address } from "~~/components/scaffold-eth"; -import { useFetchPlayersAssets } from "~~/hooks/vaults-of-fortune"; - -// import { -// useScaffoldContractRead, -// useScaffoldEventHistory, -// useScaffoldEventSubscriber, -// } from "~~/hooks/scaffold-eth"; - -// interface IPlayerScores { -// contestNumber: number | undefined; -// roundNumber: number | undefined; -// player: string | undefined; -// totalAssets: number; -// blockNumber: bigint; -// } +import { useScaffoldContractRead, useScaffoldEventHistory, useScaffoldEventSubscriber } from "~~/hooks/scaffold-eth"; -export const Leaderboard = () => { - const { playersAssets } = useFetchPlayersAssets(); - - // const [playersScores, setPlayersScores] = useState([]); - - // const { data: currentContestNumber } = useScaffoldContractRead({ - // contractName: "Market", - // functionName: "getCurrentContestNumber", - // }); - - // const { data: currentRoundNumber } = useScaffoldContractRead({ - // contractName: "Market", - // functionName: "getCurrentRoundNumber", - // }); +interface IPlayerScores { + contestNumber: number | undefined; + roundNumber: number | undefined; + player: string | undefined; + totalAssets: number; + blockNumber: bigint; +} - // const { data: events, isLoading: isLoadingEvents } = useScaffoldEventHistory({ - // contractName: "Market", - // eventName: "PlayerTotalAssetUpdate", - // fromBlock: 0n, - // fromBlock: 43030910n, - // // Apply filters to the event based on parameter names and values { [parameterName]: value }, - // filters: { - // contestNumber: currentContestNumber, - // roundNumber: currentRoundNumber, - // }, - // }); +export const Leaderboard = () => { + // const { playersAssets } = useFetchPlayersAssets(); + + const [playersScores, setPlayersScores] = useState([]); + + console.log("playersScores", playersScores); + + const { data: currentContestNumber } = useScaffoldContractRead({ + contractName: "Market", + functionName: "getCurrentContestNumber", + }); + + const { data: currentRoundNumber } = useScaffoldContractRead({ + contractName: "Market", + functionName: "getCurrentRoundNumber", + }); + + const { data: events, isLoading: isLoadingEvents } = useScaffoldEventHistory({ + contractName: "Market", + eventName: "PlayerTotalAssetUpdate", + // fromBlock: 0n, + fromBlock: 43030910n, + // Apply filters to the event based on parameter names and values { [parameterName]: value }, + filters: { + contestNumber: currentContestNumber, + roundNumber: currentRoundNumber, + }, + }); // useScaffoldEventSubscriber({ // contractName: "Market", @@ -55,87 +53,87 @@ export const Leaderboard = () => { // }, // }); - // useScaffoldEventSubscriber({ - // contractName: "Market", - // eventName: "PlayerTotalAssetUpdate", - - // listener: logs => { - // logs.forEach(log => { - // const { contestNumber, roundNumber, player, totalAssets } = log.args; - // const { blockNumber } = log; - - // setPlayersScores(prevScores => { - // // Find index of existing entry for the same player - // const existingIndex = prevScores.findIndex(score => score.player === player); - - // // Prepare the new score entry - // const newScore = { - // contestNumber: Number(contestNumber), - // roundNumber: Number(roundNumber), - // player: player, - // totalAssets: Number(formatEther(totalAssets || 0n)), - // blockNumber: blockNumber, - // }; - - // // If the player already exists in the array - // if (existingIndex !== -1) { - // // Compare block numbers and update only if the new event is more recent - // if (prevScores[existingIndex].blockNumber < blockNumber) { - // // Replace the existing entry with the new one - // const updatedScores = [...prevScores]; - // updatedScores[existingIndex] = newScore; - // updatedScores.sort((a, b) => b.totalAssets - a.totalAssets); - // return updatedScores; - // } else { - // // Keep the array as it is if the existing event is more recent - // return prevScores; - // } - // } else { - // // Add the new score if the player is not already in the array - // return [...prevScores, newScore].sort((a, b) => b.totalAssets - a.totalAssets); - // } - // }); - // }); - // }, - // }); - - // // console.log("playersScores", playersScores); - - // useEffect(() => { - // if (!playersScores?.length && !!events?.length && !isLoadingEvents) { - // const rawScores: IPlayerScores[] = events.map(event => { - // const { args, log } = event; - // // console.log("event", event); - // return { - // contestNumber: Number(args.contestNumber), - // roundNumber: Number(args.roundNumber), - // player: args.player, - // totalAssets: Number(formatEther(args.totalAssets || 0n)), - // blockNumber: log.blockNumber, - // }; - // }); - - // const uniquePlayerScores = rawScores.reduce>((acc, item) => { - // // Check if this player already exists in the accumulator - - // if (item.player) { - // if (!acc[item.player] || acc[item.player].blockNumber < item.blockNumber) { - // // If not present, or if the current item's blockNumber is greater, store it - // acc[item.player] = item; - // } - // } - - // return acc; - // }, {}); - - // const playerScores = Object.values(uniquePlayerScores); - - // const sortedScores = playerScores.sort((a, b) => { - // return b.totalAssets - a.totalAssets; - // }); - // setPlayersScores(sortedScores); - // } - // }, [playersScores.length, events, isLoadingEvents]); + useScaffoldEventSubscriber({ + contractName: "Market", + eventName: "PlayerTotalAssetUpdate", + + listener: logs => { + logs.forEach(log => { + const { contestNumber, roundNumber, player, totalAssets } = log.args; + const { blockNumber } = log; + + setPlayersScores(prevScores => { + // Find index of existing entry for the same player + const existingIndex = prevScores.findIndex(score => score.player === player); + + // Prepare the new score entry + const newScore = { + contestNumber: Number(contestNumber), + roundNumber: Number(roundNumber), + player: player, + totalAssets: Number(formatEther(totalAssets || 0n)), + blockNumber: blockNumber, + }; + + // If the player already exists in the array + if (existingIndex !== -1) { + // Compare block numbers and update only if the new event is more recent + if (prevScores[existingIndex].blockNumber < blockNumber) { + // Replace the existing entry with the new one + const updatedScores = [...prevScores]; + updatedScores[existingIndex] = newScore; + updatedScores.sort((a, b) => b.totalAssets - a.totalAssets); + return updatedScores; + } else { + // Keep the array as it is if the existing event is more recent + return prevScores; + } + } else { + // Add the new score if the player is not already in the array + return [...prevScores, newScore].sort((a, b) => b.totalAssets - a.totalAssets); + } + }); + }); + }, + }); + + // console.log("playersScores", playersScores); + + useEffect(() => { + if (!playersScores?.length && !!events?.length && !isLoadingEvents) { + const rawScores: IPlayerScores[] = events.map(event => { + const { args, log } = event; + // console.log("event", event); + return { + contestNumber: Number(args.contestNumber), + roundNumber: Number(args.roundNumber), + player: args.player, + totalAssets: Number(formatEther(args.totalAssets || 0n)), + blockNumber: log.blockNumber, + }; + }); + + const uniquePlayerScores = rawScores.reduce>((acc, item) => { + // Check if this player already exists in the accumulator + + if (item.player) { + if (!acc[item.player] || acc[item.player].blockNumber < item.blockNumber) { + // If not present, or if the current item's blockNumber is greater, store it + acc[item.player] = item; + } + } + + return acc; + }, {}); + + const playerScores = Object.values(uniquePlayerScores); + + const sortedScores = playerScores.sort((a, b) => { + return b.totalAssets - a.totalAssets; + }); + setPlayersScores(sortedScores); + } + }, [playersScores.length, events, isLoadingEvents]); return (
@@ -146,18 +144,18 @@ export const Leaderboard = () => { Pos Player - Total Assets + Total Godl - {playersAssets.map((score, idx) => { + {playersScores.map((score, idx) => { return ( {idx + 1}
- {Number(formatEther(score.totalAssets)).toFixed(0)} + {score.totalAssets.toFixed(0)} ); })} diff --git a/packages/nextjs/components/vaults-of-fortune/Portfolio.tsx b/packages/nextjs/components/vaults-of-fortune/Portfolio.tsx index c57a841..ee2662b 100644 --- a/packages/nextjs/components/vaults-of-fortune/Portfolio.tsx +++ b/packages/nextjs/components/vaults-of-fortune/Portfolio.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +// import { useEffect, useState } from "react"; import { ArcElement, Chart as ChartJS, Tooltip } from "chart.js"; import { Doughnut } from "react-chartjs-2"; import { formatEther } from "viem"; @@ -15,19 +15,19 @@ ChartJS.register(ArcElement, Tooltip); // }; export const Portfolio = () => { - const [isPlayer, setIsPlayer] = useState(false); + // const [isPlayer, setIsPlayer] = useState(false); const account = useAccount(); - const { data: players } = useScaffoldContractRead({ - contractName: "Market", - functionName: "getPlayers", - }); + // const { data: players } = useScaffoldContractRead({ + // contractName: "Market", + // functionName: "getPlayers", + // }); - useEffect(() => { - if (players !== undefined && account.address !== undefined) { - setIsPlayer(players.includes(account.address)); - } - }, [players, account.address]); + // useEffect(() => { + // if (players !== undefined && account.address !== undefined) { + // setIsPlayer(players.includes(account.address)); + // } + // }, [players, account.address]); const { writeAsync: enterContest, @@ -38,14 +38,14 @@ export const Portfolio = () => { functionName: "enterContest", }); - const { - writeAsync: startClosing, - // isLoading, - // isMining, - } = useScaffoldContractWrite({ - contractName: "Market", - functionName: "startClosing", - }); + // const { + // writeAsync: startClosing, + // // isLoading, + // // isMining, + // } = useScaffoldContractWrite({ + // contractName: "Market", + // functionName: "startClosing", + // }); const { data: userGoldBalance } = useScaffoldContractRead({ contractName: "GoldToken", @@ -71,10 +71,10 @@ export const Portfolio = () => { args: [account.address], }); - const { data: currentRoundState } = useScaffoldContractRead({ - contractName: "Market", - functionName: "getCurrentRoundState", - }); + // const { data: currentRoundState } = useScaffoldContractRead({ + // contractName: "Market", + // functionName: "getCurrentRoundState", + // }); const formattedLowRisk = +formatEther(lowRiskAssets || 0n); const formattedMediumRisk = +formatEther(mediumRiskAssets || 0n); @@ -120,14 +120,15 @@ export const Portfolio = () => { return ( <> - {isPlayer ? ( + {totalAssets > 0 ? ( <>

Portfolio

- {currentRoundState === 0 && userGoldBalance === 0n && ( + + {/* {currentRoundState === 0 && userGoldBalance === 0n && ( - )} + )} */}
-
{formatEther(userGoldBalance || 0n)} GLD tokens
+
{formatEther(userGoldBalance || 0n)} GODL
) : (
diff --git a/packages/nextjs/components/vaults-of-fortune/Round.tsx b/packages/nextjs/components/vaults-of-fortune/Round.tsx index 8ca15b2..f03a5b2 100644 --- a/packages/nextjs/components/vaults-of-fortune/Round.tsx +++ b/packages/nextjs/components/vaults-of-fortune/Round.tsx @@ -117,8 +117,6 @@ const RoiTable = () => { functionName: "getCurrentContestNumber", }); - console.log("currentContestNumber", currentContestNumber); - const { data: events, isLoading: isLoadingEvents } = useScaffoldEventHistory({ contractName: "Market", eventName: "RoundROIResults", @@ -194,6 +192,19 @@ const RoiTable = () => { } }; + const generatePlaceholderData = () => ({ + contestNumber: 0, + roundNumber: 0, + lowRiskVaultROI: 0, + mediumRiskVaultROI: 0, + highRiskVaultROI: 0, + }); + + const tableRows = + roundResults.length >= 3 + ? roundResults + : [...roundResults, ...Array(3 - roundResults.length).fill(generatePlaceholderData())]; + return (
@@ -206,12 +217,18 @@ const RoiTable = () => { - {roundResults.map(result => ( - - - - - + {tableRows.map((result, index) => ( + + + + + ))} diff --git a/packages/nextjs/components/vaults-of-fortune/Vaults.tsx b/packages/nextjs/components/vaults-of-fortune/Vaults.tsx index f62c9a7..e2eb005 100644 --- a/packages/nextjs/components/vaults-of-fortune/Vaults.tsx +++ b/packages/nextjs/components/vaults-of-fortune/Vaults.tsx @@ -231,7 +231,13 @@ export const Vaults = () => { disabled={!((userGoldBalance ?? 0) > 0) || vaultDeposit[vault.key].amount === "0"} onClick={async () => { setMaxDeposit((userGoldBalance || 0n) - parseEther(vaultDeposit[vault.key].amount)); - await vault.deposit(); + + try { + await vault.deposit(); + } catch { + setMaxDeposit(maxDeposit + parseEther(vaultDeposit[vault.key].amount)); + } + setVaultDeposit(prevState => ({ ...prevState, [vault.key]: { diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index debd156..4c60cc6 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -2577,7 +2577,7 @@ const deployedContracts = { }, 80001: { GoldToken: { - address: "0x88CA96372D2196aAcaa9093fca361C576b2d171c", + address: "0x03910EA285e741225a8eBd0046b8B12E1690C9a1", abi: [ { inputs: [], @@ -2857,7 +2857,7 @@ const deployedContracts = { ], }, HighRiskVault: { - address: "0x0fBf225a33b81942aAC95a69514990ce9583cb54", + address: "0x913CC2b11Cc3FC7237FB4751C0FF95e6B405191F", abi: [ { inputs: [ @@ -3649,7 +3649,7 @@ const deployedContracts = { ], }, LowRiskVault: { - address: "0xCb53D95C67c12D1db7A73bfCB36d7eE18CA6Af24", + address: "0x5402d699E9FA1dA201E846097ee02eef0FE4B85f", abi: [ { inputs: [ @@ -4441,7 +4441,7 @@ const deployedContracts = { ], }, Market: { - address: "0xc60D0494C120Fd0D233Fe35bbAE2b4e9812Ae937", + address: "0xB1D926d3543c1860bE69fee7f1D15A0C3EFB801D", abi: [ { inputs: [ @@ -4825,13 +4825,6 @@ const deployedContracts = { stateMutability: "view", type: "function", }, - { - inputs: [], - name: "emergencyRequestRandomWords", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, { inputs: [], name: "enterContest", @@ -5012,6 +5005,13 @@ const deployedContracts = { stateMutability: "nonpayable", type: "function", }, + { + inputs: [], + name: "manuallyRequestRandomWords", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, { inputs: [], name: "mediumRiskVault", @@ -5141,13 +5141,6 @@ const deployedContracts = { stateMutability: "view", type: "function", }, - { - inputs: [], - name: "startClosing", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, { inputs: [], name: "subscriptionId", @@ -5210,7 +5203,7 @@ const deployedContracts = { ], }, MediumRiskVault: { - address: "0xDd3325B8209bfD7eFB2781aDe9b953D053324b10", + address: "0x68A2600c7c2fF387B677Ce73fFC9a76302AB5Ec1", abi: [ { inputs: [ diff --git a/packages/nextjs/hooks/vaults-of-fortune/useFetchPlayersAssets.tsx b/packages/nextjs/hooks/vaults-of-fortune/useFetchPlayersAssets.tsx index 38bfbbd..65c69c6 100644 --- a/packages/nextjs/hooks/vaults-of-fortune/useFetchPlayersAssets.tsx +++ b/packages/nextjs/hooks/vaults-of-fortune/useFetchPlayersAssets.tsx @@ -35,7 +35,6 @@ export function useFetchPlayersAssets() { // const contracts = (deployedContracts as GenericContractsDeclaration)[chainId] const contracts = deployedContracts[80001]; - console.log("contract AVI", contracts.Market.abi); const players = await publicClient.readContract({ address: contracts.Market.address, abi: contracts.Market.abi, diff --git a/packages/nextjs/pages/contest.tsx b/packages/nextjs/pages/contest.tsx index 5d2d980..e9cbd39 100644 --- a/packages/nextjs/pages/contest.tsx +++ b/packages/nextjs/pages/contest.tsx @@ -157,7 +157,7 @@ const CurrentContest: NextPage = () => {
{result.roundNumber}{result.lowRiskVaultROI}%{result.mediumRiskVaultROI}%{result.highRiskVaultROI}%
{index + 1} + {result.lowRiskVaultROI ? `${result.lowRiskVaultROI}%` : "-"} + + {result.mediumRiskVaultROI ? `${result.mediumRiskVaultROI}%` : "-"} + + {result.highRiskVaultROI ? `${result.highRiskVaultROI}%` : "-"} +
Medium Risk 0 ? "text-green-500" : "text-red-500" + roundResults.mediumRiskVaultROI > 0 ? "text-green-500" : "text-red-500" }`} > {roundResults.mediumRiskVaultROI}% diff --git a/packages/nextjs/utils/formatWithCommas.tsx b/packages/nextjs/utils/formatWithCommas.tsx new file mode 100644 index 0000000..a72eae7 --- /dev/null +++ b/packages/nextjs/utils/formatWithCommas.tsx @@ -0,0 +1,6 @@ +export function formatWithCommas(value: number | string): string { + const stringValue = value.toString(); + + // Use a regular expression to format the string with commas + return stringValue.replace(/\B(?=(\d{3})+(?!\d))/g, ","); +}