From 8d5466db80b447a0e5de32ca1999e6249648a657 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 16:46:41 -0700 Subject: [PATCH 01/44] first commit working on de-prize --- ui/components/layout/Sidebar/Navigation.ts | 8 +- ui/components/nance/DePrize.tsx | 389 +++++++++++++++++++++ ui/const/abis/Competitor.json | 1 + ui/const/config.ts | 11 + ui/lib/tokens/hooks/useTokenBalances.tsx | 26 ++ ui/lib/utils/voting.ts | 6 +- ui/pages/_app.tsx | 2 + ui/pages/deprize.tsx | 66 ++++ 8 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 ui/components/nance/DePrize.tsx create mode 100644 ui/const/abis/Competitor.json create mode 100644 ui/lib/tokens/hooks/useTokenBalances.tsx create mode 100644 ui/pages/deprize.tsx diff --git a/ui/components/layout/Sidebar/Navigation.ts b/ui/components/layout/Sidebar/Navigation.ts index b06f7dd0..92b7120d 100644 --- a/ui/components/layout/Sidebar/Navigation.ts +++ b/ui/components/layout/Sidebar/Navigation.ts @@ -6,6 +6,7 @@ import { RocketLaunchIcon, Squares2X2Icon, UserGroupIcon, + MoonIcon, } from '@heroicons/react/24/outline' import IconOrg from '../../assets/IconOrg' @@ -19,6 +20,11 @@ export const navigation = [ { name: 'Create a Team', href: '/team' }, ], }, + { + name: 'De-Prize', + href: '/deprize', + icon: MoonIcon, + }, { name: 'Network', href: '/network', @@ -37,7 +43,7 @@ export const navigation = [ }, { name: 'Events', href: '/events' }, { name: 'Analytics', href: '/analytics' }, - { name: 'Current Projects', href: '/current-projects'} + { name: 'Current Projects', href: '/current-projects' }, ], }, { diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx new file mode 100644 index 00000000..d0f42a4d --- /dev/null +++ b/ui/components/nance/DePrize.tsx @@ -0,0 +1,389 @@ +import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' +import { useAddress, useContract } from '@thirdweb-dev/react' +import ERC20 from 'const/abis/ERC20.json' +import { + DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, + SNAPSHOT_RETROACTIVE_REWARDS_ID, + PRIZE_TOKEN_ADDRESSES, + PRIZE_DECIMALS, +} from 'const/config' +import _ from 'lodash' +import { useState, useEffect } from 'react' +import toast from 'react-hot-toast' +import { useCitizens } from '@/lib/citizen/useCitizen' +import { useAssets } from '@/lib/dashboard/hooks' +import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' +import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' +import { useVotingPowers } from '@/lib/snapshot' +import useWindowSize from '@/lib/team/use-window-size' +import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' +import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance' +import { getBudget, getPayouts } from '@/lib/utils/rewards' +import { runQuadraticVoting } from '@/lib/utils/voting' +import Asset from '@/components/dashboard/treasury/balance/Asset' +import Container from '@/components/layout/Container' +import ContentLayout from '@/components/layout/ContentLayout' +import Head from '@/components/layout/Head' +import { NoticeFooter } from '@/components/layout/NoticeFooter' +import StandardButton from '../layout/StandardButton' + +export type Competitor = { + id: string + deprize: number + title: string + treasury: string +} +export type Distribution = { + deprize: number + year: number + quarter: number + address: string + distribution: { [key: string]: number } +} + +export type DePrizeProps = { + competitors: Competitor[] + distributions: Distribution[] + refreshRewards: () => void +} + +export function DePrize({ + competitors, + distributions, + refreshRewards, +}: DePrizeProps) { + console.log('competitors', competitors) + const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + const { isMobile } = useWindowSize() + + const userAddress = useAddress() + const year = new Date().getFullYear() + const quarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 + const deprize = 1 + + const [edit, setEdit] = useState(false) + const [distribution, setDistribution] = useState<{ [key: string]: number }>( + {} + ) + // Check if the user already has a distribution for the current quarter + useEffect(() => { + if (distributions && userAddress) { + for (const d of distributions) { + if ( + d.year === year && + d.quarter === quarter && + d.address.toLowerCase() === userAddress.toLowerCase() + ) { + setDistribution(d.distribution) + setEdit(true) + break + } + } + } + }, [userAddress, distributions]) + const handleDistributionChange = (competitorId: string, value: number) => { + setDistribution((prev) => ({ + ...prev, + [competitorId]: Math.min(100, Math.max(1, value)), + })) + } + + const addresses = distributions ? distributions.map((d) => d.address) : [] + + const { data: _vps } = useVotingPowers( + addresses, + SNAPSHOT_SPACE_NAME, + SNAPSHOT_RETROACTIVE_REWARDS_ID + ) + const { contract: prizeContract } = useContract( + PRIZE_TOKEN_ADDRESSES[chain.slug], + ERC20.abi + ) + const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) + console.log('prizeBalance', prizeBalance) + const tokenBalances = useTokenBalances( + prizeContract, + PRIZE_DECIMALS, + addresses + ) + console.log('tokenBalances', tokenBalances) + const addressToQuadraticVotingPower = Object.fromEntries( + addresses.map((address, i) => [address, Math.sqrt(tokenBalances[i])]) + ) + console.log('userAddress', userAddress) + console.log('addressToQuadraticVotingPower', addressToQuadraticVotingPower) + const votingPowerSumIsNonZero = + _.sum(Object.values(addressToQuadraticVotingPower)) > 0 + const userHasVotingPower = + userAddress && + (userAddress.toLowerCase() in addressToQuadraticVotingPower || + userAddress in addressToQuadraticVotingPower) && + addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 + console.log('userHasVotingPower', userHasVotingPower) + + // All competitors need at least one citizen distribution to do iterative normalization + const isCitizens = useCitizens(chain, addresses) + const citizenDistributions = distributions?.filter((_, i) => isCitizens[i]) + const nonCitizenDistributions = distributions?.filter( + (_, i) => !isCitizens[i] + ) + const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => + citizenDistributions.some(({ distribution }) => id in distribution) + ) + console.log( + 'allCompetitorsHaveCitizenDistribution', + allCompetitorsHaveCitizenDistribution + ) + //const readyToRunVoting = + //allCompetitorsHaveCitizenDistribution && votingPowerSumIsNonZero + const readyToRunVoting = votingPowerSumIsNonZero + + console.log('distributions', distributions) + const budgetPercent = 100 + const competitorIdToEstimatedPercentage: { [key: string]: number } = + runQuadraticVoting( + distributions, + addressToQuadraticVotingPower, + budgetPercent + ) + + const { contract: distributionTableContract } = useContract( + DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug] + ) + const { tokens } = useAssets() + const { ethBudget, usdBudget, mooneyBudget, ethPrice } = getBudget( + tokens, + year, + quarter + ) + const prizeBudget = 2_500_000 + const prizePrice = 1 + const competitorIdToPrizePayout = {} + for (const competitor of competitors) { + competitorIdToPrizePayout[competitor.id] = + (prizeBudget * competitorIdToEstimatedPercentage[competitor.id]) / 100 + } + console.log('competitorIdToPrizePayout', competitorIdToPrizePayout) + + const handleSubmit = async () => { + const totalPercentage = Object.values(distribution).reduce( + (sum, value) => sum + value, + 0 + ) + if (totalPercentage !== 100) { + toast.error('Total distribution must equal 100%', { + style: toastStyle, + }) + return + } + try { + if (edit) { + await distributionTableContract?.call('updateTableCol', [ + deprize, + quarter, + year, + JSON.stringify(distribution), + ]) + toast.success('Distribution edited successfully!', { + style: toastStyle, + }) + setTimeout(() => { + refreshRewards() + }, 5000) + } else { + await distributionTableContract?.call('insertIntoTable', [ + deprize, + quarter, + year, + JSON.stringify(distribution), + ]) + toast.success('Distribution submitted successfully!', { + style: toastStyle, + }) + setTimeout(() => { + refreshRewards() + }, 5000) + } + } catch (error) { + console.error('Error submitting distribution:', error) + toast.error('Error submitting distribution. Please try again.', { + style: toastStyle, + }) + } + } + const handleDelete = async () => { + try { + await distributionTableContract?.call('deleteFromTable', [ + deprize, + quarter, + year, + ]) + toast.success('Distribution deleted successfully!', { + style: toastStyle, + }) + setTimeout(() => { + refreshRewards() + }, 5000) + } catch (error) { + console.error('Error deleting distribution:', error) + toast.error('Error deleting distribution. Please try again.', { + style: toastStyle, + }) + } + } + + return ( +
+ + + } + mainPadding + mode="compact" + popOverEffect={false} + isProfile + > +
+
+

+ Total Q{quarter} Rewards +

+ +
+ {userAddress && ( +
+

+ Voting Power +

+ +
+ )} +
+
+
+

+ Distribute +

+ {readyToRunVoting && ( +

+ Estimated Rewards +

+ )} +
+
+ {competitors && + competitors.map((competitor, i: number) => ( +
+
+ + handleDistributionChange( + competitor.id, + parseInt(e.target.value) + ) + } + className="border rounded px-2 py-1 w-20" + min="1" + max="100" + disabled={!userAddress || !userHasVotingPower} + /> + % +
+      +
+ + Competitor {competitor.id}: + + {competitor.name} +
+ {readyToRunVoting && tokens && tokens[0] && ( + <> +
+ {competitorIdToEstimatedPercentage[ + competitor.id + ].toFixed(2)} + % +
+
+ {Number( + competitorIdToPrizePayout[ + competitor.id + ].toPrecision(3) + ).toLocaleString()}{' '} + PRIZE +
+ + )} +
+ ))} +
+ {competitors && userHasVotingPower ? ( + + + {edit ? 'Edit Distribution' : 'Submit Distribution'} + + {edit && ( + + Delete Distribution + + )} + + ) : ( + + + Get Voting Power + + + )} +
+
+
+
+ ) +} diff --git a/ui/const/abis/Competitor.json b/ui/const/abis/Competitor.json new file mode 100644 index 00000000..a8ee1306 --- /dev/null +++ b/ui/const/abis/Competitor.json @@ -0,0 +1 @@ +[{"type":"constructor","inputs":[{"name":"_table_prefix","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"type":"function","name":"currId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getPolicy","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getPolicy","inputs":[{"name":"caller","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getTableId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTableName","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"insertIntoTable","inputs":[{"name":"name","type":"string","internalType":"string"},{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"treasury","type":"string","internalType":"string"},{"name":"metadata","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setAccessControl","inputs":[{"name":"controller","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"ChainNotSupported","inputs":[{"name":"chainid","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]}] diff --git a/ui/const/config.ts b/ui/const/config.ts index c1eb5468..7c7d2725 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -94,10 +94,21 @@ export const PROJECT_TABLE_ADDRESSES: Index = { arbitrum: '0x4c2d6567D81A34117E894b6fDC97C9824f80D961', 'arbitrum-sepolia': '0xF0A3DB6161D1Ee7B99197CDeD4EdFc462EAE80e0', } +export const COMPETITOR_TABLE_ADDRESSES: Index = { + sepolia: '0xb5D65d5867eBF99556a0F79fF6eB5A1F5680bFBc', +} export const DISTRIBUTION_TABLE_ADDRESSES: Index = { arbitrum: '0xabD8D3693439A72393220d87aee159952261Ad1f', 'arbitrum-sepolia': '0xd1D57F18252D06a6b28DE96B6cbF7F4283A4F205', } +export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { + sepolia: '0xA441f20115c868dc66bC1977E1c17D4B9A0189c7', +} +// TODO don't hard code, pull from d-prize contract +export const PRIZE_TOKEN_ADDRESSES: Index = { + sepolia: '0xf2a29F67fb5e6d7B9682591c0fD100d357dA85A7', +} +export const PRIZE_DECIMALS = 18 export const CITIZEN_WHITELIST_ADDRESSES: Index = { arbitrum: '0xd594DBF360D666c94615Fb186AF3cB1018Be1616', diff --git a/ui/lib/tokens/hooks/useTokenBalances.tsx b/ui/lib/tokens/hooks/useTokenBalances.tsx new file mode 100644 index 00000000..744fe3a6 --- /dev/null +++ b/ui/lib/tokens/hooks/useTokenBalances.tsx @@ -0,0 +1,26 @@ +import { useContext, useEffect, useState } from 'react' +import PrivyWalletContext from '../privy/privy-wallet-context' +import { useWallets } from './useWallets' + +export default function useTokenBalances( + tokenContract: any, + decimals: number, + addresses: string[] +) { + const [tokenBalances, setTokenBalances] = useState([]) + + useEffect(() => { + async function getBalances() { + const balances = await Promise.all( + addresses.map(async (address) => { + const balance = await tokenContract.call('balanceOf', [address]) + return +balance.toString() / 10 ** decimals + }) + ) + setTokenBalances(balances) + } + + getBalances() + }, []) + return tokenBalances +} diff --git a/ui/lib/utils/voting.ts b/ui/lib/utils/voting.ts index f65aff4b..8f09c191 100644 --- a/ui/lib/utils/voting.ts +++ b/ui/lib/utils/voting.ts @@ -174,9 +174,10 @@ export function runIterativeNormalization(distributions: any, projects: any) { return return_tuple } -function runQuadraticVoting( +export function runQuadraticVoting( distributions: Distribution[], - addressToQuadraticVotingPower: any + addressToQuadraticVotingPower: any, + budgetPercentMinusCommunityFund = 90 ) { const projectIdToEstimatedPercentage: { [key: string]: number } = {} const projectIdToListOfPercentage: { [key: string]: number[] } = {} @@ -202,7 +203,6 @@ function runQuadraticVoting( ) ) / votingPowerSum } - const budgetPercentMinusCommunityFund = 90 // normalize projectIdToEstimatedPercentage const sum = _.sum(Object.values(projectIdToEstimatedPercentage)) for (const [projectId, percentage] of Object.entries( diff --git a/ui/pages/_app.tsx b/ui/pages/_app.tsx index 8c54d9bf..626e7cc4 100644 --- a/ui/pages/_app.tsx +++ b/ui/pages/_app.tsx @@ -14,6 +14,8 @@ function App({ Component, pageProps: { session, ...pageProps } }: any) { const [selectedChain, setSelectedChain]: any = useState( process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia ) + console.log('selectedChain') + console.log(selectedChain) const [lightMode, setLightMode] = useLightMode() diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx new file mode 100644 index 00000000..9e523bf7 --- /dev/null +++ b/ui/pages/deprize.tsx @@ -0,0 +1,66 @@ +import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' +import CompetitorABI from 'const/abis/Competitor.json' +import { + COMPETITOR_TABLE_ADDRESSES, + DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, + TABLELAND_ENDPOINT, +} from 'const/config' +import { useRouter } from 'next/router' +import { initSDK } from '@/lib/thirdweb/thirdweb' +import { DePrize, DePrizeProps } from '../components/nance/DePrize' + +export default function Rewards({ competitors, distributions }: DePrizeProps) { + const router = useRouter() + return ( + router.reload()} + /> + ) +} + +export async function getStaticProps() { + const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + const sdk = initSDK(chain) + + const competitorTableContract = await sdk.getContract( + COMPETITOR_TABLE_ADDRESSES[chain.slug], + CompetitorABI + ) + + const distributionTableContract = await sdk.getContract( + DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug] + ) + + const competitorBoardTableName = await competitorTableContract.call( + 'getTableName' + ) + const distributionTableName = await distributionTableContract.call( + 'getTableName' + ) + + // TODO don't hardcode + const dePrizeId = 1 + const currentYear = new Date().getFullYear() + const currentQuarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 + const competitorStatement = `SELECT * FROM ${competitorBoardTableName} WHERE deprize = ${dePrizeId}` + const competitorsRes = await fetch( + `${TABLELAND_ENDPOINT}?statement=${competitorStatement}` + ) + const competitors = await competitorsRes.json() + + const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId} AND year = ${currentYear} AND quarter = ${currentQuarter}` + const distributionsRes = await fetch( + `${TABLELAND_ENDPOINT}?statement=${distributionStatement}` + ) + const distributions = await distributionsRes.json() + + return { + props: { + competitors, + distributions, + }, + revalidate: 60, + } +} From 64c2e0f80dfc4da668c0e3ce65fe079f1d9626dd Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 16:58:42 -0700 Subject: [PATCH 02/44] fix type errors --- ui/components/nance/DePrize.tsx | 31 +++++++++++-------------------- ui/pages/deprize.tsx | 2 +- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index d0f42a4d..3e40c945 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -27,11 +27,15 @@ import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' import StandardButton from '../layout/StandardButton' +export type Metadata = { + social: string +} export type Competitor = { id: string deprize: number title: string treasury: string + metadata: Metadata } export type Distribution = { deprize: number @@ -52,7 +56,6 @@ export function DePrize({ distributions, refreshRewards, }: DePrizeProps) { - console.log('competitors', competitors) const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const { isMobile } = useWindowSize() @@ -100,18 +103,14 @@ export function DePrize({ ERC20.abi ) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) - console.log('prizeBalance', prizeBalance) const tokenBalances = useTokenBalances( prizeContract, PRIZE_DECIMALS, addresses ) - console.log('tokenBalances', tokenBalances) const addressToQuadraticVotingPower = Object.fromEntries( addresses.map((address, i) => [address, Math.sqrt(tokenBalances[i])]) ) - console.log('userAddress', userAddress) - console.log('addressToQuadraticVotingPower', addressToQuadraticVotingPower) const votingPowerSumIsNonZero = _.sum(Object.values(addressToQuadraticVotingPower)) > 0 const userHasVotingPower = @@ -119,7 +118,6 @@ export function DePrize({ (userAddress.toLowerCase() in addressToQuadraticVotingPower || userAddress in addressToQuadraticVotingPower) && addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 - console.log('userHasVotingPower', userHasVotingPower) // All competitors need at least one citizen distribution to do iterative normalization const isCitizens = useCitizens(chain, addresses) @@ -130,15 +128,8 @@ export function DePrize({ const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => citizenDistributions.some(({ distribution }) => id in distribution) ) - console.log( - 'allCompetitorsHaveCitizenDistribution', - allCompetitorsHaveCitizenDistribution - ) - //const readyToRunVoting = - //allCompetitorsHaveCitizenDistribution && votingPowerSumIsNonZero const readyToRunVoting = votingPowerSumIsNonZero - console.log('distributions', distributions) const budgetPercent = 100 const competitorIdToEstimatedPercentage: { [key: string]: number } = runQuadraticVoting( @@ -158,12 +149,12 @@ export function DePrize({ ) const prizeBudget = 2_500_000 const prizePrice = 1 - const competitorIdToPrizePayout = {} - for (const competitor of competitors) { - competitorIdToPrizePayout[competitor.id] = - (prizeBudget * competitorIdToEstimatedPercentage[competitor.id]) / 100 - } - console.log('competitorIdToPrizePayout', competitorIdToPrizePayout) + const competitorIdToPrizePayout = Object.fromEntries( + competitors.map(({ id }) => [ + id, + (prizeBudget * competitorIdToEstimatedPercentage[id]) / 100, + ]) + ) const handleSubmit = async () => { const totalPercentage = Object.values(distribution).reduce( @@ -264,7 +255,7 @@ export function DePrize({ name="PRIZE" amount={Number(prizeBudget.toPrecision(3)).toLocaleString()} usd={Number( - prizeBudget.toPrecision(3) * prizePrice + (prizeBudget * prizePrice).toPrecision(3) ).toLocaleString()} /> diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 9e523bf7..5203ba24 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -53,7 +53,7 @@ export async function getStaticProps() { const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId} AND year = ${currentYear} AND quarter = ${currentQuarter}` const distributionsRes = await fetch( `${TABLELAND_ENDPOINT}?statement=${distributionStatement}` - ) + j const distributions = await distributionsRes.json() return { From 111213484e87a61328b49d724bb9d56180c0784c Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 17:13:29 -0700 Subject: [PATCH 03/44] some fixes --- ui/lib/tokens/hooks/useTokenBalances.tsx | 3 ++- ui/pages/deprize.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/lib/tokens/hooks/useTokenBalances.tsx b/ui/lib/tokens/hooks/useTokenBalances.tsx index 744fe3a6..72e26b8c 100644 --- a/ui/lib/tokens/hooks/useTokenBalances.tsx +++ b/ui/lib/tokens/hooks/useTokenBalances.tsx @@ -11,6 +11,7 @@ export default function useTokenBalances( useEffect(() => { async function getBalances() { + if (!tokenContract) return const balances = await Promise.all( addresses.map(async (address) => { const balance = await tokenContract.call('balanceOf', [address]) @@ -21,6 +22,6 @@ export default function useTokenBalances( } getBalances() - }, []) + }, [tokenContract, addresses]) return tokenBalances } diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 5203ba24..9e523bf7 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -53,7 +53,7 @@ export async function getStaticProps() { const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId} AND year = ${currentYear} AND quarter = ${currentQuarter}` const distributionsRes = await fetch( `${TABLELAND_ENDPOINT}?statement=${distributionStatement}` - j + ) const distributions = await distributionsRes.json() return { From 808d88d9996219ee634ac1fdec428c340803e3df Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 17:17:18 -0700 Subject: [PATCH 04/44] fixes types --- ui/components/nance/DePrize.tsx | 1 + ui/lib/tokens/hooks/useTokenBalances.tsx | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 3e40c945..118305da 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -32,6 +32,7 @@ export type Metadata = { } export type Competitor = { id: string + name: string deprize: number title: string treasury: string diff --git a/ui/lib/tokens/hooks/useTokenBalances.tsx b/ui/lib/tokens/hooks/useTokenBalances.tsx index 72e26b8c..4e8f29a4 100644 --- a/ui/lib/tokens/hooks/useTokenBalances.tsx +++ b/ui/lib/tokens/hooks/useTokenBalances.tsx @@ -1,6 +1,4 @@ import { useContext, useEffect, useState } from 'react' -import PrivyWalletContext from '../privy/privy-wallet-context' -import { useWallets } from './useWallets' export default function useTokenBalances( tokenContract: any, From ec48c26a5363277eb177392a7f3cac2318df7551 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 17:22:31 -0700 Subject: [PATCH 05/44] hard code sepolia' --- ui/pages/deprize.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 9e523bf7..7ca3d083 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -21,7 +21,9 @@ export default function Rewards({ competitors, distributions }: DePrizeProps) { } export async function getStaticProps() { - const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + // TODO uncomment + //const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + const chain = process.env.NEXT_PUBLIC_CHAIN === Sepolia const sdk = initSDK(chain) const competitorTableContract = await sdk.getContract( From 71a894a13aac97748006ece575d8b4f73cf663b8 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 17:25:14 -0700 Subject: [PATCH 06/44] fix typo --- ui/pages/deprize.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 7ca3d083..f896c4de 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -23,7 +23,7 @@ export default function Rewards({ competitors, distributions }: DePrizeProps) { export async function getStaticProps() { // TODO uncomment //const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia - const chain = process.env.NEXT_PUBLIC_CHAIN === Sepolia + const chain = Sepolia const sdk = initSDK(chain) const competitorTableContract = await sdk.getContract( From d82f852942901ac646e4e3f54cd2617844669c20 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 30 Oct 2024 17:30:26 -0700 Subject: [PATCH 07/44] fix error --- ui/components/nance/DePrize.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 118305da..5da29c29 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -150,12 +150,14 @@ export function DePrize({ ) const prizeBudget = 2_500_000 const prizePrice = 1 - const competitorIdToPrizePayout = Object.fromEntries( - competitors.map(({ id }) => [ - id, - (prizeBudget * competitorIdToEstimatedPercentage[id]) / 100, - ]) - ) + const competitorIdToPrizePayout = competitors + ? Object.fromEntries( + competitors.map(({ id }) => [ + id, + (prizeBudget * competitorIdToEstimatedPercentage[id]) / 100, + ]) + ) + : {} const handleSubmit = async () => { const totalPercentage = Object.values(distribution).reduce( From 215693fbc6efd0e9c1bd61e96cfe1d84285a22b6 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Thu, 7 Nov 2024 11:14:23 -0800 Subject: [PATCH 08/44] wip --- contracts/README.md | 5 +- contracts/curve.py | 671 ++++++++++++++++++ contracts/foundry.toml | 10 +- contracts/scripts/dev-deploy.ts | 60 +- contracts/scripts/helpers.ts | 19 +- .../src/governance/SmartWalletChecker.sol | 63 ++ contracts/src/governance/VotingEscrow.vy | 4 +- .../src/governance/VotingEscrowDepositor.sol | 25 + contracts/src/sweepstakes/mooney_mumbai.sol | 35 - contracts/src/sweepstakes/tts2_mumbai.sol | 250 ------- contracts/src/test/MerkleDistributorV2.t.sol | 112 --- .../src/test/VotingEscrowDepositor.t.sol | 76 ++ ui/components/nance/DePrize.tsx | 81 ++- ui/const/abis/REVDeployer.json | 1 + ui/const/config.ts | 10 + ui/lib/revnet/hooks/useIsOperator.tsx | 21 + ui/lib/tokens/hooks/useTokenBalances.tsx | 2 +- ui/lib/tokens/hooks/useTokenSupply.tsx | 16 + ui/pages/_app.tsx | 2 - ui/public/sitemap-0.xml | 57 +- 20 files changed, 1046 insertions(+), 474 deletions(-) create mode 100644 contracts/curve.py create mode 100644 contracts/src/governance/SmartWalletChecker.sol create mode 100644 contracts/src/governance/VotingEscrowDepositor.sol delete mode 100644 contracts/src/sweepstakes/mooney_mumbai.sol delete mode 100644 contracts/src/sweepstakes/tts2_mumbai.sol delete mode 100644 contracts/src/test/MerkleDistributorV2.t.sol create mode 100644 contracts/src/test/VotingEscrowDepositor.t.sol create mode 100644 ui/const/abis/REVDeployer.json create mode 100644 ui/lib/revnet/hooks/useIsOperator.tsx create mode 100644 ui/lib/tokens/hooks/useTokenSupply.tsx diff --git a/contracts/README.md b/contracts/README.md index 84426e88..52d3dbbc 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -33,7 +33,10 @@ yarn install cp .env.sample .env # Install Vyper -pip install vyper==0.2.4 +pip install vyper==0.3.1 + +# Add vyper to path +export PATH="$PATH:$HOME/Library/Python/3.9/bin" # Install Git submodules forge install diff --git a/contracts/curve.py b/contracts/curve.py new file mode 100644 index 00000000..d92c57af --- /dev/null +++ b/contracts/curve.py @@ -0,0 +1,671 @@ +# @version 0.3.0 +""" +@title Voting Escrow +@author Curve Finance +@license MIT +@notice Votes have a weight depending on time, so that users are + committed to the future of (whatever they are voting for) +@dev Vote weight decays linearly over time. Lock time cannot be + more than `MAXTIME` (4 years). +""" + +# Voting escrow to have time-weighted votes +# Votes have a weight depending on time, so that users are committed +# to the future of (whatever they are voting for). +# The weight in this implementation is linear, and lock cannot be more than maxtime: +# w ^ +# 1 + / +# | / +# | / +# | / +# |/ +# 0 +--------+------> time +# maxtime (4 years?) + +struct Point: + bias: int128 + slope: int128 # - dweight / dt + ts: uint256 + blk: uint256 # block +# We cannot really do block numbers per se b/c slope is per time, not per block +# and per block could be fairly bad b/c Ethereum changes blocktimes. +# What we can do is to extrapolate ***At functions + +struct LockedBalance: + amount: int128 + end: uint256 + + +interface ERC20: + def decimals() -> uint256: view + def name() -> String[64]: view + def symbol() -> String[32]: view + def transfer(to: address, amount: uint256) -> bool: nonpayable + def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable + + +# Interface for checking whether address belongs to a whitelisted +# type of a smart wallet. +# When new types are added - the whole contract is changed +# The check() method is modifying to be able to use caching +# for individual wallet addresses +interface SmartWalletChecker: + def check(addr: address) -> bool: nonpayable + +DEPOSIT_FOR_TYPE: constant(int128) = 0 +CREATE_LOCK_TYPE: constant(int128) = 1 +INCREASE_LOCK_AMOUNT: constant(int128) = 2 +INCREASE_UNLOCK_TIME: constant(int128) = 3 + + +event CommitOwnership: + admin: address + +event ApplyOwnership: + admin: address + +event Deposit: + provider: indexed(address) + value: uint256 + locktime: indexed(uint256) + type: int128 + ts: uint256 + +event Withdraw: + provider: indexed(address) + value: uint256 + ts: uint256 + +event Supply: + prevSupply: uint256 + supply: uint256 + + +WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week +MAXTIME: constant(uint256) = 4 * 365 * 86400 # 4 years +MULTIPLIER: constant(uint256) = 10 ** 18 + +token: public(address) +supply: public(uint256) + +locked: public(HashMap[address, LockedBalance]) + +epoch: public(uint256) +point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point +user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] +user_point_epoch: public(HashMap[address, uint256]) +slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change + +# Aragon's view methods for compatibility +controller: public(address) +transfersEnabled: public(bool) + +name: public(String[64]) +symbol: public(String[32]) +version: public(String[32]) +decimals: public(uint256) + +# Checker for whitelisted (smart contract) wallets which are allowed to deposit +# The goal is to prevent tokenizing the escrow +future_smart_wallet_checker: public(address) +smart_wallet_checker: public(address) + +admin: public(address) # Can and will be a smart contract +future_admin: public(address) + + +@external +def __init__(token_addr: address, _name: String[64], _symbol: String[32], _version: String[32]): + """ + @notice Contract constructor + @param token_addr `ERC20CRV` token address + @param _name Token name + @param _symbol Token symbol + @param _version Contract version - required for Aragon compatibility + """ + self.admin = msg.sender + self.token = token_addr + self.point_history[0].blk = block.number + self.point_history[0].ts = block.timestamp + self.controller = msg.sender + self.transfersEnabled = True + + _decimals: uint256 = ERC20(token_addr).decimals() + assert _decimals <= 255 + self.decimals = _decimals + + self.name = _name + self.symbol = _symbol + self.version = _version + + +@external +def commit_transfer_ownership(addr: address): + """ + @notice Transfer ownership of VotingEscrow contract to `addr` + @param addr Address to have ownership transferred to + """ + assert msg.sender == self.admin # dev: admin only + self.future_admin = addr + log CommitOwnership(addr) + + +@external +def apply_transfer_ownership(): + """ + @notice Apply ownership transfer + """ + assert msg.sender == self.admin # dev: admin only + _admin: address = self.future_admin + assert _admin != ZERO_ADDRESS # dev: admin not set + self.admin = _admin + log ApplyOwnership(_admin) + + +@external +def commit_smart_wallet_checker(addr: address): + """ + @notice Set an external contract to check for approved smart contract wallets + @param addr Address of Smart contract checker + """ + assert msg.sender == self.admin + self.future_smart_wallet_checker = addr + + +@external +def apply_smart_wallet_checker(): + """ + @notice Apply setting external contract to check approved smart contract wallets + """ + assert msg.sender == self.admin + self.smart_wallet_checker = self.future_smart_wallet_checker + + +@internal +def assert_not_contract(addr: address): + """ + @notice Check if the call is from a whitelisted smart contract, revert if not + @param addr Address to be checked + """ + if addr != tx.origin: + checker: address = self.smart_wallet_checker + if checker != ZERO_ADDRESS: + if SmartWalletChecker(checker).check(addr): + return + raise "Smart contract depositors not allowed" + + +@external +@view +def get_last_user_slope(addr: address) -> int128: + """ + @notice Get the most recently recorded rate of voting power decrease for `addr` + @param addr Address of the user wallet + @return Value of the slope + """ + uepoch: uint256 = self.user_point_epoch[addr] + return self.user_point_history[addr][uepoch].slope + + +@external +@view +def user_point_history__ts(_addr: address, _idx: uint256) -> uint256: + """ + @notice Get the timestamp for checkpoint `_idx` for `_addr` + @param _addr User wallet address + @param _idx User epoch number + @return Epoch time of the checkpoint + """ + return self.user_point_history[_addr][_idx].ts + + +@external +@view +def locked__end(_addr: address) -> uint256: + """ + @notice Get timestamp when `_addr`'s lock finishes + @param _addr User wallet + @return Epoch time of the lock end + """ + return self.locked[_addr].end + + +@internal +def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance): + """ + @notice Record global and per-user data to checkpoint + @param addr User's wallet address. No user checkpoint if 0x0 + @param old_locked Pevious locked amount / end lock time for the user + @param new_locked New locked amount / end lock time for the user + """ + u_old: Point = empty(Point) + u_new: Point = empty(Point) + old_dslope: int128 = 0 + new_dslope: int128 = 0 + _epoch: uint256 = self.epoch + + if addr != ZERO_ADDRESS: + # Calculate slopes and biases + # Kept at zero when they have to + if old_locked.end > block.timestamp and old_locked.amount > 0: + u_old.slope = old_locked.amount / MAXTIME + u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128) + if new_locked.end > block.timestamp and new_locked.amount > 0: + u_new.slope = new_locked.amount / MAXTIME + u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128) + + # Read values of scheduled changes in the slope + # old_locked.end can be in the past and in the future + # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros + old_dslope = self.slope_changes[old_locked.end] + if new_locked.end != 0: + if new_locked.end == old_locked.end: + new_dslope = old_dslope + else: + new_dslope = self.slope_changes[new_locked.end] + + last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number}) + if _epoch > 0: + last_point = self.point_history[_epoch] + last_checkpoint: uint256 = last_point.ts + # initial_last_point is used for extrapolation to calculate block number + # (approximately, for *At methods) and save them + # as we cannot figure that out exactly from inside the contract + initial_last_point: Point = last_point + block_slope: uint256 = 0 # dblock/dt + if block.timestamp > last_point.ts: + block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts) + # If last point is already recorded in this block, slope=0 + # But that's ok b/c we know the block in such case + + # Go over weeks to fill history and calculate what the current point is + t_i: uint256 = (last_checkpoint / WEEK) * WEEK + for i in range(255): + # Hopefully it won't happen that this won't get used in 5 years! + # If it does, users will be able to withdraw but vote weight will be broken + t_i += WEEK + d_slope: int128 = 0 + if t_i > block.timestamp: + t_i = block.timestamp + else: + d_slope = self.slope_changes[t_i] + last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128) + last_point.slope += d_slope + if last_point.bias < 0: # This can happen + last_point.bias = 0 + if last_point.slope < 0: # This cannot happen - just in case + last_point.slope = 0 + last_checkpoint = t_i + last_point.ts = t_i + last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER + _epoch += 1 + if t_i == block.timestamp: + last_point.blk = block.number + break + else: + self.point_history[_epoch] = last_point + + self.epoch = _epoch + # Now point_history is filled until t=now + + if addr != ZERO_ADDRESS: + # If last point was in this block, the slope change has been applied already + # But in such case we have 0 slope(s) + last_point.slope += (u_new.slope - u_old.slope) + last_point.bias += (u_new.bias - u_old.bias) + if last_point.slope < 0: + last_point.slope = 0 + if last_point.bias < 0: + last_point.bias = 0 + + # Record the changed point into history + self.point_history[_epoch] = last_point + + if addr != ZERO_ADDRESS: + # Schedule the slope changes (slope is going down) + # We subtract new_user_slope from [new_locked.end] + # and add old_user_slope to [old_locked.end] + if old_locked.end > block.timestamp: + # old_dslope was - u_old.slope, so we cancel that + old_dslope += u_old.slope + if new_locked.end == old_locked.end: + old_dslope -= u_new.slope # It was a new deposit, not extension + self.slope_changes[old_locked.end] = old_dslope + + if new_locked.end > block.timestamp: + if new_locked.end > old_locked.end: + new_dslope -= u_new.slope # old slope disappeared at this point + self.slope_changes[new_locked.end] = new_dslope + # else: we recorded it already in old_dslope + + # Now handle user history + user_epoch: uint256 = self.user_point_epoch[addr] + 1 + + self.user_point_epoch[addr] = user_epoch + u_new.ts = block.timestamp + u_new.blk = block.number + self.user_point_history[addr][user_epoch] = u_new + + +@internal +def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128): + """ + @notice Deposit and lock tokens for a user + @param _addr User's wallet address + @param _value Amount to deposit + @param unlock_time New time when to unlock the tokens, or 0 if unchanged + @param locked_balance Previous locked amount / timestamp + """ + _locked: LockedBalance = locked_balance + supply_before: uint256 = self.supply + + self.supply = supply_before + _value + old_locked: LockedBalance = _locked + # Adding to existing lock, or if a lock is expired - creating a new one + _locked.amount += convert(_value, int128) + if unlock_time != 0: + _locked.end = unlock_time + self.locked[_addr] = _locked + + # Possibilities: + # Both old_locked.end could be current or expired (>/< block.timestamp) + # value == 0 (extend lock) or value > 0 (add to lock or extend lock) + # _locked.end > block.timestamp (always) + self._checkpoint(_addr, old_locked, _locked) + + if _value != 0: + assert ERC20(self.token).transferFrom(_addr, self, _value) + + log Deposit(_addr, _value, _locked.end, type, block.timestamp) + log Supply(supply_before, supply_before + _value) + + +@external +def checkpoint(): + """ + @notice Record global data to checkpoint + """ + self._checkpoint(ZERO_ADDRESS, empty(LockedBalance), empty(LockedBalance)) + + +@external +@nonreentrant('lock') +def deposit_for(_addr: address, _value: uint256): + """ + @notice Deposit `_value` tokens for `_addr` and add to the lock + @dev Anyone (even a smart contract) can deposit for someone else, but + cannot extend their locktime and deposit for a brand new user + @param _addr User's wallet address + @param _value Amount to add to user's lock + """ + _locked: LockedBalance = self.locked[_addr] + + assert _value > 0 # dev: need non-zero value + assert _locked.amount > 0, "No existing lock found" + assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" + + self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE) + + +@external +@nonreentrant('lock') +def create_lock(_value: uint256, _unlock_time: uint256): + """ + @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time` + @param _value Amount to deposit + @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks + """ + self.assert_not_contract(msg.sender) + unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks + _locked: LockedBalance = self.locked[msg.sender] + + assert _value > 0 # dev: need non-zero value + assert _locked.amount == 0, "Withdraw old tokens first" + assert unlock_time > block.timestamp, "Can only lock until time in the future" + assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max" + + self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE) + + +@external +@nonreentrant('lock') +def increase_amount(_value: uint256): + """ + @notice Deposit `_value` additional tokens for `msg.sender` + without modifying the unlock time + @param _value Amount of tokens to deposit and add to the lock + """ + self.assert_not_contract(msg.sender) + _locked: LockedBalance = self.locked[msg.sender] + + assert _value > 0 # dev: need non-zero value + assert _locked.amount > 0, "No existing lock found" + assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" + + self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT) + + +@external +@nonreentrant('lock') +def increase_unlock_time(_unlock_time: uint256): + """ + @notice Extend the unlock time for `msg.sender` to `_unlock_time` + @param _unlock_time New epoch time for unlocking + """ + self.assert_not_contract(msg.sender) + _locked: LockedBalance = self.locked[msg.sender] + unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks + + assert _locked.end > block.timestamp, "Lock expired" + assert _locked.amount > 0, "Nothing is locked" + assert unlock_time > _locked.end, "Can only increase lock duration" + assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max" + + self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME) + + +@external +@nonreentrant('lock') +def withdraw(): + """ + @notice Withdraw all tokens for `msg.sender` + @dev Only possible if the lock has expired + """ + _locked: LockedBalance = self.locked[msg.sender] + assert block.timestamp >= _locked.end, "The lock didn't expire" + value: uint256 = convert(_locked.amount, uint256) + + old_locked: LockedBalance = _locked + _locked.end = 0 + _locked.amount = 0 + self.locked[msg.sender] = _locked + supply_before: uint256 = self.supply + self.supply = supply_before - value + + # old_locked can have either expired <= timestamp or zero end + # _locked has only 0 end + # Both can have >= 0 amount + self._checkpoint(msg.sender, old_locked, _locked) + + assert ERC20(self.token).transfer(msg.sender, value) + + log Withdraw(msg.sender, value, block.timestamp) + log Supply(supply_before, supply_before - value) + + +# The following ERC20/minime-compatible methods are not real balanceOf and supply! +# They measure the weights for the purpose of voting, so they don't represent +# real coins. + +@internal +@view +def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256: + """ + @notice Binary search to estimate timestamp for block number + @param _block Block to find + @param max_epoch Don't go beyond this epoch + @return Approximate timestamp for block + """ + # Binary search + _min: uint256 = 0 + _max: uint256 = max_epoch + for i in range(128): # Will be always enough for 128-bit numbers + if _min >= _max: + break + _mid: uint256 = (_min + _max + 1) / 2 + if self.point_history[_mid].blk <= _block: + _min = _mid + else: + _max = _mid - 1 + return _min + + +@external +@view +def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256: + """ + @notice Get the current voting power for `msg.sender` + @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility + @param addr User wallet address + @param _t Epoch time to return voting power at + @return User voting power + """ + _epoch: uint256 = self.user_point_epoch[addr] + if _epoch == 0: + return 0 + else: + last_point: Point = self.user_point_history[addr][_epoch] + last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128) + if last_point.bias < 0: + last_point.bias = 0 + return convert(last_point.bias, uint256) + + +@external +@view +def balanceOfAt(addr: address, _block: uint256) -> uint256: + """ + @notice Measure voting power of `addr` at block height `_block` + @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime + @param addr User's wallet address + @param _block Block to calculate the voting power at + @return Voting power + """ + # Copying and pasting totalSupply code because Vyper cannot pass by + # reference yet + assert _block <= block.number + + # Binary search + _min: uint256 = 0 + _max: uint256 = self.user_point_epoch[addr] + for i in range(128): # Will be always enough for 128-bit numbers + if _min >= _max: + break + _mid: uint256 = (_min + _max + 1) / 2 + if self.user_point_history[addr][_mid].blk <= _block: + _min = _mid + else: + _max = _mid - 1 + + upoint: Point = self.user_point_history[addr][_min] + + max_epoch: uint256 = self.epoch + _epoch: uint256 = self.find_block_epoch(_block, max_epoch) + point_0: Point = self.point_history[_epoch] + d_block: uint256 = 0 + d_t: uint256 = 0 + if _epoch < max_epoch: + point_1: Point = self.point_history[_epoch + 1] + d_block = point_1.blk - point_0.blk + d_t = point_1.ts - point_0.ts + else: + d_block = block.number - point_0.blk + d_t = block.timestamp - point_0.ts + block_time: uint256 = point_0.ts + if d_block != 0: + block_time += d_t * (_block - point_0.blk) / d_block + + upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128) + if upoint.bias >= 0: + return convert(upoint.bias, uint256) + else: + return 0 + + +@internal +@view +def supply_at(point: Point, t: uint256) -> uint256: + """ + @notice Calculate total voting power at some point in the past + @param point The point (bias/slope) to start search from + @param t Time to calculate the total voting power at + @return Total voting power at that time + """ + last_point: Point = point + t_i: uint256 = (last_point.ts / WEEK) * WEEK + for i in range(255): + t_i += WEEK + d_slope: int128 = 0 + if t_i > t: + t_i = t + else: + d_slope = self.slope_changes[t_i] + last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128) + if t_i == t: + break + last_point.slope += d_slope + last_point.ts = t_i + + if last_point.bias < 0: + last_point.bias = 0 + return convert(last_point.bias, uint256) + + +@external +@view +def totalSupply(t: uint256 = block.timestamp) -> uint256: + """ + @notice Calculate total voting power + @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility + @return Total voting power + """ + _epoch: uint256 = self.epoch + last_point: Point = self.point_history[_epoch] + return self.supply_at(last_point, t) + + +@external +@view +def totalSupplyAt(_block: uint256) -> uint256: + """ + @notice Calculate total voting power at some point in the past + @param _block Block to calculate the total voting power at + @return Total voting power at `_block` + """ + assert _block <= block.number + _epoch: uint256 = self.epoch + target_epoch: uint256 = self.find_block_epoch(_block, _epoch) + + point: Point = self.point_history[target_epoch] + dt: uint256 = 0 + if target_epoch < _epoch: + point_next: Point = self.point_history[target_epoch + 1] + if point.blk != point_next.blk: + dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk) + else: + if point.blk != block.number: + dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk) + # Now dt contains info on how far are we beyond point + + return self.supply_at(point, point.ts + dt) + + +# Dummy methods for compatibility with Aragon + +@external +def changeController(_newController: address): + """ + @dev Dummy method required for Aragon compatibility + """ + assert msg.sender == self.controller + self.controller = _newController diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 8d2dd106..2fe11be0 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -1,4 +1,12 @@ -[default] +[profile.default] src = "src" out = "out" libs = ["lib"] +evm_version = "berlin" + +#[profile.default.compiler] +#version = "0.8.17" + +# Vyper compiler settings +[profile.default.vyper] +vyper_version = "0.3.1" diff --git a/contracts/scripts/dev-deploy.ts b/contracts/scripts/dev-deploy.ts index 971d79fb..b3ef3ee6 100644 --- a/contracts/scripts/dev-deploy.ts +++ b/contracts/scripts/dev-deploy.ts @@ -1,22 +1,33 @@ import { wallet, dec, save } from "./helpers"; import { ethers, BigNumber, Contract } from "ethers"; -import { formatUnits } from "@ethersproject/units" +import { formatUnits } from "@ethersproject/units"; -import MyToken from '../out/MyToken.sol/MyToken.json'; -import VotingEscrow from '../out/VotingEscrow.vy/VotingEscrow.json'; -import MerkleDistributorV2 from '../out/MerkleDistributorV2.sol/MerkleDistributorV2.json'; +import MyToken from "../out/MyToken.sol/MyToken.json"; +import VotingEscrow from "../out/VotingEscrow.vy/VotingEscrow.json"; const getContractFactory = (artifact: any) => { return new ethers.ContractFactory(artifact.abi, artifact.bytecode.object, wallet); -} - -const deployContract = async ({ name, deployer, factory, args }: { name: string, deployer: ethers.Wallet, factory: ethers.ContractFactory, args: Array}) => { - console.log(`Deploying ${name}..`) - const contract = await factory.connect(deployer).deploy(...args); +}; + +const deployContract = async ({ + name, + deployer, + factory, + args, +}: { + name: string; + deployer: ethers.Wallet; + factory: ethers.ContractFactory; + args: Array; +}) => { + console.log(`Deploying ${name}..`); + const contract = await factory.connect(deployer).deploy(...args, { + gasLimit: BigNumber.from(8000000), + }); await contract.deployed(); - console.log(`Deployed ${name} to: ${contract.address}`) + console.log(`Deployed ${name} to: ${contract.address}`); return contract; -} +}; const deployMOONEY = async () => { const supply: BigNumber = BigNumber.from(process.env.MOONEY_SUPPLY ?? dec(42069, 18)); @@ -26,14 +37,14 @@ const deployMOONEY = async () => { name: "MOONEY", deployer: wallet, factory: Factory, - args: [] + args: ["Mooney", "MOONEY"], }); - await MOONEYToken.connect(wallet).mint(wallet.address, supply); - console.log(`Minted ${formatUnits(supply, 18)} tokens to deployer address`) + //await MOONEYToken.connect(wallet).mint(wallet.address, supply); + console.log(`Minted ${formatUnits(supply, 18)} tokens to deployer address`); return MOONEYToken; -} +}; const deployVMOONEY = async (MOONEYToken: Contract) => { const Factory = getContractFactory(VotingEscrow); @@ -42,11 +53,11 @@ const deployVMOONEY = async (MOONEYToken: Contract) => { name: "vMOONEY", deployer: wallet, factory: Factory, - args: [MOONEYToken.address, "Vote-escrowed MOONEY", "vMOONEY", "vMOONEY_1.0.0"] - }) + args: [MOONEYToken.address, "Vote-escrowed MOONEY", "vMOONEY", "vMOONEY_1.0.0"], + }); return vMOONEY; -} +}; // const deployAirdropDistributor = async (MOONEYToken: Contract, root: string) => { // const Factory = getContractFactory(MerkleDistributorV2); @@ -119,7 +130,7 @@ const deployVMOONEY = async (MOONEYToken: Contract) => { // factory: passportIssuerFactory, // args: [] // }) - + // await passportToken.connect(wallet).transferControl(passportIssuer.address); // // TODO: Set renderer @@ -139,16 +150,15 @@ const main = async () => { const vMOONEY = await deployVMOONEY(MOONEY); const deployment = { - "MOONEYToken": MOONEY.address, - "vMOONEYToken": vMOONEY.address, - } + MOONEYToken: MOONEY.address, + vMOONEYToken: vMOONEY.address, + }; const manifestFile = "./deployments/local.json"; save(deployment, manifestFile); - console.log(`Deployment manifest saved to ${manifestFile}`) -} - + console.log(`Deployment manifest saved to ${manifestFile}`); +}; main().catch((error) => { console.error(error); diff --git a/contracts/scripts/helpers.ts b/contracts/scripts/helpers.ts index dbbe5c4e..7cec616e 100644 --- a/contracts/scripts/helpers.ts +++ b/contracts/scripts/helpers.ts @@ -1,7 +1,7 @@ import fs from "fs"; import { ethers } from "ethers"; import dotenv from "dotenv"; -dotenv.config() +dotenv.config(); let validConfig = true; if (process.env.RPC_URL === undefined) { @@ -14,19 +14,18 @@ if (process.env.DEPLOYER_PRIVATE_KEY === undefined) { } if (!validConfig) process.exit(1); -export const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL) -export const wallet = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY ?? "", provider) +export const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); +export const wallet = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY ?? "", provider); // Deployment helpers export const dec = (val: number, scale: number) => { const zerosCount = scale; const strVal = val.toString(); - const strZeros = ('0').repeat(zerosCount); + const strZeros = "0".repeat(zerosCount); return strVal.concat(strZeros); - } - +}; export const save = (info: object, path: string) => { const content = JSON.stringify(info, null, 1); @@ -37,7 +36,7 @@ export const save = (info: object, path: string) => { fs.mkdirSync(dir, { recursive: true }); } - return fs.writeFile(path, content, { encoding: "utf-8"}, (err) => { if(err) console.log(err); }) -} - - + return fs.writeFile(path, content, { encoding: "utf-8" }, (err) => { + if (err) console.log(err); + }); +}; diff --git a/contracts/src/governance/SmartWalletChecker.sol b/contracts/src/governance/SmartWalletChecker.sol new file mode 100644 index 00000000..5b9be392 --- /dev/null +++ b/contracts/src/governance/SmartWalletChecker.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.6; + +//import "@openzeppelin/contracts-4.2.0/access/Ownable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract SmartWalletChecker is Ownable { + bool public isWhitelistEnabled; + mapping(address => bool) public wallets; + address public checker; + address public future_checker; + + event ApproveWallet(address); + event RevokeWallet(address); + event WhitelistEnabled(bool); + + constructor(bool _isWhitelistEnabled) public Ownable() { + // Set state variables + setIsWhitelistEnabled(_isWhitelistEnabled); + } + + function commitSetChecker(address _checker) external onlyOwner { + future_checker = _checker; + } + + function applySetChecker() external onlyOwner { + checker = future_checker; + } + + function approveWallet(address _wallet) external onlyOwner { + wallets[_wallet] = true; + + emit ApproveWallet(_wallet); + } + + function revokeWallet(address _wallet) external onlyOwner { + wallets[_wallet] = false; + + emit RevokeWallet(_wallet); + } + + function setIsWhitelistEnabled(bool _isWhitelistEnabled) public onlyOwner { + isWhitelistEnabled = _isWhitelistEnabled; + + emit WhitelistEnabled(_isWhitelistEnabled); + } + + function check(address _wallet) external view returns (bool) { + if (!isWhitelistEnabled) { + return true; + } + + bool _check = wallets[_wallet]; + if (_check) { + return _check; + } else { + if (checker != address(0)) { + return SmartWalletChecker(checker).check(_wallet); + } + } + return false; + } +} diff --git a/contracts/src/governance/VotingEscrow.vy b/contracts/src/governance/VotingEscrow.vy index bd09ec9b..c61a69a0 100644 --- a/contracts/src/governance/VotingEscrow.vy +++ b/contracts/src/governance/VotingEscrow.vy @@ -1,4 +1,4 @@ -# @version 0.3.0 +# @version 0.3.1 """ @title Voting Escrow @author Curve Finance @@ -668,4 +668,4 @@ def changeController(_newController: address): @dev Dummy method required for Aragon compatibility """ assert msg.sender == self.controller - self.controller = _newController \ No newline at end of file + self.controller = _newController diff --git a/contracts/src/governance/VotingEscrowDepositor.sol b/contracts/src/governance/VotingEscrowDepositor.sol new file mode 100644 index 00000000..98409859 --- /dev/null +++ b/contracts/src/governance/VotingEscrowDepositor.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IERC20 { + function transfer(address recipient, uint256 amount) external returns (bool); +} + +interface IVotingEscrow { + function deposit_for(address _addr, uint256 _value) external; +} + +contract VotingEscrowDepositor { + IERC20 public token; + IVotingEscrow public escrowToken; + + constructor(address _tokenAddress, address _escrowTokenAddress) { + token = IERC20(_tokenAddress); + escrowToken = IVotingEscrow(_escrowTokenAddress); + } + + function transfer_and_deposit_for(address addr, uint256 value) external { + require(token.transfer(addr, value), "Token transfer failed"); + escrowToken.deposit_for(addr, value); + } +} diff --git a/contracts/src/sweepstakes/mooney_mumbai.sol b/contracts/src/sweepstakes/mooney_mumbai.sol deleted file mode 100644 index b0773447..00000000 --- a/contracts/src/sweepstakes/mooney_mumbai.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.6; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; -import "@paulrberg/contracts/token/erc20/Erc20Permit.sol"; - -import "./ITickets.sol"; - -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract MumbaiMooney is ERC20, ERC20Permit, Ownable, ITickets { - constructor(string memory _name, string memory _symbol) - ERC20(_name, _symbol) - ERC20Permit(_name) - { - _mint(msg.sender, 1000000 * 10 ** 18); - } - - function print(address _account, uint256 _amount) - external - override - onlyOwner - { - return _mint(_account, _amount); - } - - function redeem(address _account, uint256 _amount) - external - override - onlyOwner - { - return _burn(_account, _amount); - } -} \ No newline at end of file diff --git a/contracts/src/sweepstakes/tts2_mumbai.sol b/contracts/src/sweepstakes/tts2_mumbai.sol deleted file mode 100644 index bc05bb2e..00000000 --- a/contracts/src/sweepstakes/tts2_mumbai.sol +++ /dev/null @@ -1,250 +0,0 @@ -/* - NAME: TICKET-TO-SPACE-2 - CHAIN: MAINNET -*/ - -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "@thirdweb-dev/contracts/eip/ERC721A.sol"; -import "@thirdweb-dev/contracts/extension/Ownable.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol"; -import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; -import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; -import "@openzeppelin/contracts/utils/Base64.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -contract TicketToSpace2 is ERC721A, Ownable, VRFConsumerBaseV2 { - VRFCoordinatorV2Interface COORDINATOR; - LinkTokenInterface immutable LINKTOKEN = - LinkTokenInterface(0x326C977E6efc84E512bB9C30f76E30c160eD06FB); //https://vrf.chain.link/mainnet - bytes32 immutable keyHash = - 0x4b09e658ed251bcafeebbc69400383d49f344ace09b9576fe248bb02c003fe9f; //200gwei mainnet - address immutable vrfCoordinator_ = 0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed; - - struct RequestStatus { - bool paid; // paid? - bool fulfilled; // whether the request has been successfully fulfilled - uint256[] randomWords; - } - - mapping(uint256 => RequestStatus) - public s_requests; /* requestId --> requestStatus */ - - mapping(uint256 => address) - public winners; - - uint256 public winnersCount = 5; - - string private _nftName = "Ticket to Space NFT 2"; - string private _image = "ipfs://Qmba3umb3db7DqCA19iRSSbtzv9nYUmP8Cibo5QMkLpgpP"; - - IERC20 public mooneyToken = IERC20(0x3818f3273D1f46259b737342Ad30e576A7A74f09); - uint256 public mintPrice = 100 * 10 ** 18; - - bytes32 public previousEntrantsMerkleRoot; - - //VRF subscription ID. - uint64 s_subscriptionId; - - uint256[] public requestIds; - uint256 public lastRequestId; - - uint16 immutable requestConfirmations = 6; - uint32 immutable numWords = 1; - uint256 public immutable maxTokens = 2**256 - 1; - uint256 public immutable maxWalletMints = 50; - - bool public allowTransfers = false; - bool public allowMinting = false; - - bool internal locked; //re-entry lock - - //EVENTS - event RequestSent(uint256 requestId, uint32 numWords); - event RequestFulfilled(uint256 requestId, uint256[] randomWords); - event WinnerSelected(uint256 winnerNum, address winner); - - constructor() VRFConsumerBaseV2(0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed) ERC721A("Ticket to Space 2", "TTS2") { - COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator_); - _setupOwner(msg.sender); - } - - //MODIFIERS - modifier reEntrancyGuard(){ - require(!locked, "No re-entrancy"); - locked = true; - _; - locked = false; - } - - //FUNCTIONS - function _canSetOwner() internal view virtual override returns (bool) { - return msg.sender == owner(); - } - - function setImage(string memory image) public onlyOwner { - _image = image; - } - - function setSubscript(uint64 subscriptionId_) external onlyOwner { - s_subscriptionId = subscriptionId_; - } - - function setAllowTransfers(bool allowTransfers_) external onlyOwner { - allowTransfers = allowTransfers_; - } - - function setAllowMinting(bool allowMinting_) external onlyOwner { - allowMinting = allowMinting_; - } - - function setMooneyToken(IERC20 mooneyToken_) external onlyOwner { - mooneyToken = mooneyToken_; - } - - function setMintPrice(uint256 mintPrice_) external onlyOwner { - mintPrice = mintPrice_; - } - - function addMerkleRoot(bytes32 _merkleRoot) external onlyOwner { - previousEntrantsMerkleRoot = _merkleRoot; - } - - function toString(uint256 value) internal pure returns (string memory) { - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); - } - - function tokenURI(uint256 tokenId) public view virtual override returns (string memory){ - require(_exists(tokenId), "URI query for nonexistent token"); - string memory json = Base64.encode( - bytes( - string( - abi.encodePacked( - '{"name": "', - _nftName, - " #", - toString(tokenId), - '", "image": "', - _image, - '"}' - ) - ) - ) - ); - return string(abi.encodePacked("data:application/json;base64,", json)); - } - - function getSupply() public view returns (uint256) { - return _currentIndex; - } - - function canClaimFree(bytes32[] calldata merkleProof, address receiver) - public - view - returns (bool) - { - - return - MerkleProof.verify( - merkleProof, - previousEntrantsMerkleRoot, - keccak256(abi.encodePacked(receiver)) - ) && balanceOf(receiver) == 0; - } - - function claimFree(bytes32[] calldata merkleProof) public { - require(allowMinting, "Minting is not currently open"); - require(_currentIndex < maxTokens, "error:10003 NFT mint limit reached"); - require(balanceOf(msg.sender) < maxWalletMints, "Address has already minted the maximum amount of NFTs"); - - address claimAddress = msg.sender; - require(canClaimFree(merkleProof, claimAddress), "Address cannot claim for free, or has already claimed"); - _safeMint(claimAddress, 1); - } - - function mint(uint256 count) public payable { - require(allowMinting, "Minting is not currently open"); - require(_currentIndex + count < maxTokens, "Tickets already minted"); - require(count + balanceOf(msg.sender) <= maxWalletMints, "Mint amount is more than allowed"); - - mooneyToken.transferFrom(msg.sender, address(0x000000000000000000000000000000000000dEaD), mintPrice*count); - _safeMint(msg.sender, count); - } - - function _beforeTokenTransfers(address from, address to, uint256 tokenId, uint256 batchSize) - internal - override - { - super._beforeTokenTransfers(from, to, tokenId, batchSize); - //non-transferable after mint until allowTransfers = true - if(from != address(0x0) && !allowTransfers) revert("Transfers disabled"); - } - - function chooseWinner(uint32 limit) external onlyOwner returns(uint256 requestId) { - uint32 callbackGasLimit = limit; - requestId = COORDINATOR.requestRandomWords( - keyHash, - s_subscriptionId, - requestConfirmations, - callbackGasLimit, - numWords - ); - s_requests[requestId] = RequestStatus({ - randomWords: new uint256[](0), - paid: true, - fulfilled: false - }); - requestIds.push(requestId); - lastRequestId = requestId; - emit RequestSent(requestId, numWords); - return requestId; - } - - function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) - internal - override - { - require(s_requests[_requestId].paid, "request not found"); - s_requests[_requestId].fulfilled = true; - s_requests[_requestId].randomWords = _randomWords; - addWinner(this.ownerOf(_randomWords[0] % _currentIndex)); - emit RequestFulfilled(_requestId, _randomWords); - } - - function getRequestStatus( - uint256 _requestId - ) - external - view - returns (bool paid, bool fulfilled, uint256[] memory randomWords) - { - RequestStatus memory request = s_requests[_requestId]; - return (request.paid, request.fulfilled, request.randomWords); - } - - function addWinner(address winner) internal { - require(winnersCount > 0, "All winners already chosen"); - winners[winnersCount] = winner; - winnersCount -= 1; - emit WinnerSelected(winnersCount, winner); - } -} diff --git a/contracts/src/test/MerkleDistributorV2.t.sol b/contracts/src/test/MerkleDistributorV2.t.sol deleted file mode 100644 index 34cad461..00000000 --- a/contracts/src/test/MerkleDistributorV2.t.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.10; - -import {DSTest} from "./utils/test.sol"; -import {Hevm} from "./utils/Hevm.sol"; -import {Signatures as sig} from "./utils/Signatures.sol"; -import {MockERC20} from "./utils/mocks/MockERC20.sol"; -import {MerkleDistributorV2} from "../distributors/MerkleDistributorV2.sol"; - -contract MerkleDistributorTest is DSTest { - Hevm evm = Hevm(HEVM_ADDRESS); - - MockERC20 token; - MerkleDistributorV2 distributor; - - uint256 public constant tokenSupply = 42069 * 1e21; - uint256 public constant airdropAllowance = 1337 * 1e21; - bytes32 public constant merkleRoot = 0x5cdea970c9f23ca3ad7c3d706650a7d1a1cf0269632e0059c9dcfcb544d3a5c8; - - function setUp() public { - token = new MockERC20("Nation3 Network Token", "NATION", tokenSupply); - distributor = new MerkleDistributorV2(); - distributor.setUp(address(this), token, merkleRoot); - // Set allowance for the airdrop - token.approve(address(distributor), airdropAllowance); - } - - function getValidClaimer() - public - pure - returns ( - uint256 index, - address account, - uint256 amount, - bytes32[] memory proofs - ) - { - index = 19; - account = 0xBC61c73CFc191321DA837def848784c002279a01; - amount = 5; - - proofs = new bytes32[](3); - proofs[0] = 0xebe77a4d8819f67553d4563538abf6fd6417c99b9b85486d47458fb8e42fbdd6; - proofs[1] = 0xc1e42302dfdf0b0d2c52bc4f0fdbb35f8b4d02b1a276fe895d77b4116d639f05; - proofs[2] = 0x5221b4a135c004944c72c1a4aa7cd3f203283a3a0822fabd5dc8efbab6689980; - } - - function testInit() public { - assertEq(distributor.sender(), address(this)); - assertEq(address(distributor.token()), address(token)); - assertEq(distributor.merkleRoot(), merkleRoot); - } - - function testClaim() public { - (uint256 index, address account, uint256 amount, bytes32[] memory proofs) = getValidClaimer(); - - distributor.claim(index, account, amount, proofs); - - // Check that the tokens has been sent from sender and the allowance adjust properly - uint256 diffSenderBalance = tokenSupply - token.balanceOf(address(this)); - uint256 diffDistributorAllowance = airdropAllowance - token.allowance(address(this), address(distributor)); - assertEq(token.balanceOf(account), amount); - assertEq(diffSenderBalance, amount); - assertEq(diffDistributorAllowance, amount); - - // Trying to claim again must revert - evm.expectRevert(sig.selector("AlreadyClaimed()")); - distributor.claim(index, account, amount, proofs); - } - - function testClaimInvalidProofs() public { - (uint256 index, address account, uint256 amount, bytes32[] memory proofs) = getValidClaimer(); - - // Invalid proofs - bytes32[] memory badProofs = new bytes32[](2); - badProofs[0] = proofs[1]; - badProofs[1] = proofs[0]; - - evm.expectRevert(sig.selector("InvalidProof()")); - distributor.claim(index, account, amount, badProofs); - } - - function testClaimInvalidAccount() public { - (uint256 index, , uint256 amount, bytes32[] memory proofs) = getValidClaimer(); - - // Random account - address badAccount = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; - - evm.expectRevert(sig.selector("InvalidProof()")); - distributor.claim(index, badAccount, amount, proofs); - } - - function testClaimInvalidAmount() public { - (uint256 index, address account, , bytes32[] memory proofs) = getValidClaimer(); - - // Random amount - uint256 badAmount = 20; - - evm.expectRevert(sig.selector("InvalidProof()")); - distributor.claim(index, account, badAmount, proofs); - } - - function testDropReset() public { - (uint256 index, address account, uint256 amount, bytes32[] memory proofs) = getValidClaimer(); - - distributor.claim(index, account, amount, proofs); - - distributor.setUp(address(this), token, merkleRoot); - - distributor.claim(index, account, amount, proofs); - } -} diff --git a/contracts/src/test/VotingEscrowDepositor.t.sol b/contracts/src/test/VotingEscrowDepositor.t.sol new file mode 100644 index 00000000..9a9374b1 --- /dev/null +++ b/contracts/src/test/VotingEscrowDepositor.t.sol @@ -0,0 +1,76 @@ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "../src/VotingEscrowDepositor.sol"; + +//Using deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +//Deploying MOONEY.. +//Deployed MOONEY to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +//Minted 0.0000000001 tokens to deployer address +//Deploying vMOONEY.. +//Deployed vMOONEY to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +//Deployment manifest saved to ./deployments/local.json + +address MOONEY = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512; +address vMOONEY = 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0; +contract MockToken is IERC20 { + mapping(address => uint256) public balanceOf; + + function transfer(address recipient, uint256 amount) external override returns (bool) { + require(balanceOf[msg.sender] >= amount, "Insufficient balance"); + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + return true; + } + + function mint(address to, uint256 amount) external { + balanceOf[to] += amount; + } +} + +contract MockVotingEscrow is IVotingEscrow { + mapping(address => uint256) public deposits; + + function deposit_for(address _addr, uint256 _value) external override { + deposits[_addr] += _value; + } +} + +contract VotingEscrowDepositorTest is Test { + VotingEscrowDepositor public depositor; + MockToken public token; + MockVotingEscrow public escrowToken; + + address public user = address(0x123); + uint256 public initialBalance = 1000; + + function setUp() public { + token = new MockToken(); + escrowToken = new MockVotingEscrow(); + depositor = new VotingEscrowDepositor(address(MOONEY), address(vMOONEY)); + + // Mint tokens for the depositor contract to transfer + token.mint(address(depositor), initialBalance); + } + + function testTransferAndDepositFor() public { + uint256 depositAmount = 500; + + // Verify initial balances and deposits + assertEq(token.balanceOf(address(depositor)), initialBalance); + assertEq(token.balanceOf(user), 0); + assertEq(escrowToken.deposits(user), 0); + + // Call transfer_and_deposit_for + depositor.transfer_and_deposit_for(user, depositAmount); + + // Verify token transfer + assertEq(token.balanceOf(address(depositor)), initialBalance - depositAmount); + assertEq(token.balanceOf(user), depositAmount); + + // Verify escrow deposit + assertEq(escrowToken.deposits(user), depositAmount); + } +} diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 5da29c29..4fe415a9 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -1,12 +1,17 @@ import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import { useAddress, useContract } from '@thirdweb-dev/react' import ERC20 from 'const/abis/ERC20.json' +import REVDeployer from 'const/abis/REVDeployer.json' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, SNAPSHOT_RETROACTIVE_REWARDS_ID, PRIZE_TOKEN_ADDRESSES, PRIZE_DECIMALS, + REVNET_ADDRESSES, + PRIZE_REVNET_ID, + BULK_TOKEN_SENDER_ADDRESSES, } from 'const/config' +import { BigNumber } from 'ethers' import _ from 'lodash' import { useState, useEffect } from 'react' import toast from 'react-hot-toast' @@ -14,9 +19,11 @@ import { useCitizens } from '@/lib/citizen/useCitizen' import { useAssets } from '@/lib/dashboard/hooks' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' +import useIsOperator from '@/lib/revnet/hooks/useIsOperator' import { useVotingPowers } from '@/lib/snapshot' import useWindowSize from '@/lib/team/use-window-size' import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' +import useTokenSupply from '@/lib/tokens/hooks/useTokenSupply' import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance' import { getBudget, getPayouts } from '@/lib/utils/rewards' import { runQuadraticVoting } from '@/lib/utils/voting' @@ -103,6 +110,14 @@ export function DePrize({ PRIZE_TOKEN_ADDRESSES[chain.slug], ERC20.abi ) + const { contract: revnetContract } = useContract( + REVNET_ADDRESSES[chain.slug], + REVDeployer.abi + ) + const { contract: bulkTokenSenderContract } = useContract( + BULK_TOKEN_SENDER_ADDRESSES[chain.slug] + ) + const isOperator = useIsOperator(revnetContract, userAddress, PRIZE_REVNET_ID) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) const tokenBalances = useTokenBalances( prizeContract, @@ -148,7 +163,9 @@ export function DePrize({ year, quarter ) - const prizeBudget = 2_500_000 + const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) + const prizeBudget = prizeSupply * 0.1 + const winnerPool = prizeSupply * 0.3 const prizePrice = 1 const competitorIdToPrizePayout = competitors ? Object.fromEntries( @@ -225,6 +242,41 @@ export function DePrize({ }) } } + const handleSend = async () => { + try { + const addresses = competitors.map((c) => c.treasury) + const amounts = competitors.map( + (c) => competitorIdToPrizePayout[c.id] * 10 ** PRIZE_DECIMALS + ) + console.log('amounts') + console.log(amounts) + console.log('addresses') + console.log(addresses) + console.log('PRIZE_TOKEN_ADDRESSES[chain.slug]') + console.log(PRIZE_TOKEN_ADDRESSES[chain.slug]) + // approve bulk token sender + //await prizeContract?.call('approve', [ + //BULK_TOKEN_SENDER_ADDRESSES[chain.slug], + //String(amounts.reduce((a, b) => a + b, 0)), + //]) + await bulkTokenSenderContract?.call('send', [ + PRIZE_TOKEN_ADDRESSES[chain.slug], + addresses.slice(0, 1), + amounts.map(String).slice(0, 1), + ]) + toast.success('Rewards sent successfully!', { + style: toastStyle, + }) + setTimeout(() => { + refreshRewards() + }, 5000) + } catch (error) { + console.error('Error sending rewards:', error) + toast.error('Error sending rewards. Please try again.', { + style: toastStyle, + }) + } + } return (
@@ -262,6 +314,16 @@ export function DePrize({ ).toLocaleString()} />
+ {userAddress && ( +
+

+ Winner Prize +

+ +
+ )} {userAddress && (
Voting Power - +
)} @@ -364,6 +419,14 @@ export function DePrize({ Delete Distribution )} + {isOperator && ( + + Send Rewards + + )} ) : ( diff --git a/ui/const/abis/REVDeployer.json b/ui/const/abis/REVDeployer.json new file mode 100644 index 00000000..b770e207 --- /dev/null +++ b/ui/const/abis/REVDeployer.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"contract IJBController","name":"controller","type":"address"},{"internalType":"contract IJBSuckerRegistry","name":"suckerRegistry","type":"address"},{"internalType":"uint256","name":"feeRevnetId","type":"uint256"},{"internalType":"contract IJB721TiersHookDeployer","name":"hookDeployer","type":"address"},{"internalType":"contract CTPublisher","name":"publisher","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"name":"PRBMath_MulDiv_Overflow","type":"error"},{"inputs":[],"name":"REVDeployer_CashOutDelayNotFinished","type":"error"},{"inputs":[],"name":"REVDeployer_CashOutsCantBeTurnedOffCompletely","type":"error"},{"inputs":[],"name":"REVDeployer_StageNotStarted","type":"error"},{"inputs":[],"name":"REVDeployer_StageTimesMustIncrease","type":"error"},{"inputs":[],"name":"REVDeployer_StagesRequired","type":"error"},{"inputs":[],"name":"REVDeployer_Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"revnetId","type":"uint256"},{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"ticker","type":"string"},{"internalType":"string","name":"uri","type":"string"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct REVDescription","name":"description","type":"tuple"},{"internalType":"uint32","name":"baseCurrency","type":"uint32"},{"internalType":"address","name":"splitOperator","type":"address"},{"components":[{"internalType":"uint40","name":"startsAtOrAfter","type":"uint40"},{"components":[{"internalType":"uint32","name":"chainId","type":"uint32"},{"internalType":"uint104","name":"count","type":"uint104"},{"internalType":"address","name":"beneficiary","type":"address"}],"internalType":"struct REVAutoMint[]","name":"autoMints","type":"tuple[]"},{"internalType":"uint16","name":"splitPercent","type":"uint16"},{"internalType":"uint112","name":"initialIssuance","type":"uint112"},{"internalType":"uint32","name":"issuanceDecayFrequency","type":"uint32"},{"internalType":"uint32","name":"issuanceDecayPercent","type":"uint32"},{"internalType":"uint16","name":"cashOutTaxRate","type":"uint16"},{"internalType":"uint16","name":"extraMetadata","type":"uint16"}],"internalType":"struct REVStageConfig[]","name":"stageConfigurations","type":"tuple[]"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"contract IJBPayoutTerminal","name":"terminal","type":"address"}],"internalType":"struct REVLoanSource[]","name":"loanSources","type":"tuple[]"},{"internalType":"address","name":"loans","type":"address"},{"internalType":"bool","name":"allowCrosschainSuckerExtension","type":"bool"}],"indexed":false,"internalType":"struct REVConfig","name":"configuration","type":"tuple"},{"components":[{"internalType":"contract IJBTerminal","name":"terminal","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext[]","name":"accountingContextsToAccept","type":"tuple[]"}],"indexed":false,"internalType":"struct JBTerminalConfig[]","name":"terminalConfigurations","type":"tuple[]"},{"components":[{"internalType":"contract IJBBuybackHook","name":"hook","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint32","name":"twapWindow","type":"uint32"},{"internalType":"uint32","name":"twapSlippageTolerance","type":"uint32"}],"internalType":"struct REVBuybackPoolConfig[]","name":"poolConfigurations","type":"tuple[]"}],"indexed":false,"internalType":"struct REVBuybackHookConfig","name":"buybackHookConfiguration","type":"tuple"},{"components":[{"components":[{"internalType":"contract IJBSuckerDeployer","name":"deployer","type":"address"},{"components":[{"internalType":"address","name":"localToken","type":"address"},{"internalType":"uint32","name":"minGas","type":"uint32"},{"internalType":"address","name":"remoteToken","type":"address"},{"internalType":"uint256","name":"minBridgeAmount","type":"uint256"}],"internalType":"struct JBTokenMapping[]","name":"mappings","type":"tuple[]"}],"internalType":"struct JBSuckerDeployerConfig[]","name":"deployerConfigurations","type":"tuple[]"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"indexed":false,"internalType":"struct REVSuckerDeploymentConfig","name":"suckerDeploymentConfiguration","type":"tuple"},{"components":[{"internalType":"uint48","name":"mustStartAtOrAfter","type":"uint48"},{"internalType":"uint32","name":"duration","type":"uint32"},{"internalType":"uint112","name":"weight","type":"uint112"},{"internalType":"uint32","name":"decayPercent","type":"uint32"},{"internalType":"contract IJBRulesetApprovalHook","name":"approvalHook","type":"address"},{"components":[{"internalType":"uint16","name":"reservedPercent","type":"uint16"},{"internalType":"uint16","name":"redemptionRate","type":"uint16"},{"internalType":"uint32","name":"baseCurrency","type":"uint32"},{"internalType":"bool","name":"pausePay","type":"bool"},{"internalType":"bool","name":"pauseCreditTransfers","type":"bool"},{"internalType":"bool","name":"allowOwnerMinting","type":"bool"},{"internalType":"bool","name":"allowSetCustomToken","type":"bool"},{"internalType":"bool","name":"allowTerminalMigration","type":"bool"},{"internalType":"bool","name":"allowSetTerminals","type":"bool"},{"internalType":"bool","name":"allowSetController","type":"bool"},{"internalType":"bool","name":"allowAddAccountingContext","type":"bool"},{"internalType":"bool","name":"allowAddPriceFeed","type":"bool"},{"internalType":"bool","name":"allowCrosschainSuckerExtension","type":"bool"},{"internalType":"bool","name":"ownerMustSendPayouts","type":"bool"},{"internalType":"bool","name":"holdFees","type":"bool"},{"internalType":"bool","name":"useTotalSurplusForRedemptions","type":"bool"},{"internalType":"bool","name":"useDataHookForPay","type":"bool"},{"internalType":"bool","name":"useDataHookForRedeem","type":"bool"},{"internalType":"address","name":"dataHook","type":"address"},{"internalType":"uint16","name":"metadata","type":"uint16"}],"internalType":"struct JBRulesetMetadata","name":"metadata","type":"tuple"},{"components":[{"internalType":"uint256","name":"groupId","type":"uint256"},{"components":[{"internalType":"bool","name":"preferAddToBalance","type":"bool"},{"internalType":"uint32","name":"percent","type":"uint32"},{"internalType":"uint56","name":"projectId","type":"uint56"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"uint48","name":"lockedUntil","type":"uint48"},{"internalType":"contract IJBSplitHook","name":"hook","type":"address"}],"internalType":"struct JBSplit[]","name":"splits","type":"tuple[]"}],"internalType":"struct JBSplitGroup[]","name":"splitGroups","type":"tuple[]"},{"components":[{"internalType":"address","name":"terminal","type":"address"},{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"uint224","name":"amount","type":"uint224"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBCurrencyAmount[]","name":"payoutLimits","type":"tuple[]"},{"components":[{"internalType":"uint224","name":"amount","type":"uint224"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBCurrencyAmount[]","name":"surplusAllowances","type":"tuple[]"}],"internalType":"struct JBFundAccessLimitGroup[]","name":"fundAccessLimitGroups","type":"tuple[]"}],"indexed":false,"internalType":"struct JBRulesetConfig[]","name":"rulesetConfigurations","type":"tuple[]"},{"indexed":false,"internalType":"bytes","name":"encodedConfiguration","type":"bytes"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"DeployRevnet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"revnetId","type":"uint256"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"bytes32","name":"salt","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"encodedConfiguration","type":"bytes"},{"components":[{"components":[{"internalType":"contract IJBSuckerDeployer","name":"deployer","type":"address"},{"components":[{"internalType":"address","name":"localToken","type":"address"},{"internalType":"uint32","name":"minGas","type":"uint32"},{"internalType":"address","name":"remoteToken","type":"address"},{"internalType":"uint256","name":"minBridgeAmount","type":"uint256"}],"internalType":"struct JBTokenMapping[]","name":"mappings","type":"tuple[]"}],"internalType":"struct JBSuckerDeployerConfig[]","name":"deployerConfigurations","type":"tuple[]"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"indexed":false,"internalType":"struct REVSuckerDeploymentConfig","name":"suckerDeploymentConfiguration","type":"tuple"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"DeploySuckers","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"revnetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"stageId","type":"uint256"},{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"count","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"revnetId","type":"uint256"},{"indexed":true,"internalType":"address","name":"newSplitOperator","type":"address"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"ReplaceSplitOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"revnetId","type":"uint256"},{"indexed":false,"internalType":"address","name":"additionalOperator","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"permissionIds","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"SetAdditionalOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"revnetId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cashOutDelay","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"SetCashOutDelay","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"revnetId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"stageId","type":"uint256"},{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"count","type":"uint256"},{"indexed":false,"internalType":"address","name":"caller","type":"address"}],"name":"StoreAutoMintAmount","type":"event"},{"inputs":[],"name":"CASH_OUT_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONTROLLER","outputs":[{"internalType":"contract IJBController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DIRECTORY","outputs":[{"internalType":"contract IJBDirectory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_REVNET_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"HOOK_DEPLOYER","outputs":[{"internalType":"contract IJB721TiersHookDeployer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMISSIONS","outputs":[{"internalType":"contract IJBPermissions","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROJECTS","outputs":[{"internalType":"contract IJBProjects","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLISHER","outputs":[{"internalType":"contract CTPublisher","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUCKER_REGISTRY","outputs":[{"internalType":"contract IJBSuckerRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"rulesetId","type":"uint256"},{"internalType":"uint256","name":"redeemCount","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"reclaimedAmount","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"forwardedAmount","type":"tuple"},{"internalType":"uint256","name":"redemptionRate","type":"uint256"},{"internalType":"address payable","name":"beneficiary","type":"address"},{"internalType":"bytes","name":"hookMetadata","type":"bytes"},{"internalType":"bytes","name":"redeemerMetadata","type":"bytes"}],"internalType":"struct JBAfterRedeemRecordedContext","name":"context","type":"tuple"}],"name":"afterRedeemRecordedWith","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"internalType":"uint256","name":"stageId","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"}],"name":"amountToAutoMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"internalType":"uint256","name":"stageId","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"}],"name":"autoMintFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"terminal","type":"address"},{"internalType":"address","name":"payer","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"amount","type":"tuple"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"rulesetId","type":"uint256"},{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint256","name":"reservedPercent","type":"uint256"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"internalType":"struct JBBeforePayRecordedContext","name":"context","type":"tuple"}],"name":"beforePayRecordedWith","outputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"components":[{"internalType":"contract IJBPayHook","name":"hook","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"internalType":"struct JBPayHookSpecification[]","name":"hookSpecifications","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"terminal","type":"address"},{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"uint256","name":"rulesetId","type":"uint256"},{"internalType":"uint256","name":"redeemCount","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint256","name":"value","type":"uint256"}],"internalType":"struct JBTokenAmount","name":"surplus","type":"tuple"},{"internalType":"bool","name":"useTotalSurplus","type":"bool"},{"internalType":"uint256","name":"redemptionRate","type":"uint256"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"internalType":"struct JBBeforeRedeemRecordedContext","name":"context","type":"tuple"}],"name":"beforeRedeemRecordedWith","outputs":[{"internalType":"uint256","name":"redemptionRate","type":"uint256"},{"internalType":"uint256","name":"redeemCount","type":"uint256"},{"internalType":"uint256","name":"totalSupply","type":"uint256"},{"components":[{"internalType":"contract IJBRedeemHook","name":"hook","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"metadata","type":"bytes"}],"internalType":"struct JBRedeemHookSpecification[]","name":"hookSpecifications","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"}],"name":"buybackHookOf","outputs":[{"internalType":"contract IJBRulesetDataHook","name":"buybackHook","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"}],"name":"cashOutDelayOf","outputs":[{"internalType":"uint256","name":"cashOutDelay","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"ticker","type":"string"},{"internalType":"string","name":"uri","type":"string"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct REVDescription","name":"description","type":"tuple"},{"internalType":"uint32","name":"baseCurrency","type":"uint32"},{"internalType":"address","name":"splitOperator","type":"address"},{"components":[{"internalType":"uint40","name":"startsAtOrAfter","type":"uint40"},{"components":[{"internalType":"uint32","name":"chainId","type":"uint32"},{"internalType":"uint104","name":"count","type":"uint104"},{"internalType":"address","name":"beneficiary","type":"address"}],"internalType":"struct REVAutoMint[]","name":"autoMints","type":"tuple[]"},{"internalType":"uint16","name":"splitPercent","type":"uint16"},{"internalType":"uint112","name":"initialIssuance","type":"uint112"},{"internalType":"uint32","name":"issuanceDecayFrequency","type":"uint32"},{"internalType":"uint32","name":"issuanceDecayPercent","type":"uint32"},{"internalType":"uint16","name":"cashOutTaxRate","type":"uint16"},{"internalType":"uint16","name":"extraMetadata","type":"uint16"}],"internalType":"struct REVStageConfig[]","name":"stageConfigurations","type":"tuple[]"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"contract IJBPayoutTerminal","name":"terminal","type":"address"}],"internalType":"struct REVLoanSource[]","name":"loanSources","type":"tuple[]"},{"internalType":"address","name":"loans","type":"address"},{"internalType":"bool","name":"allowCrosschainSuckerExtension","type":"bool"}],"internalType":"struct REVConfig","name":"configuration","type":"tuple"},{"components":[{"internalType":"contract IJBTerminal","name":"terminal","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext[]","name":"accountingContextsToAccept","type":"tuple[]"}],"internalType":"struct JBTerminalConfig[]","name":"terminalConfigurations","type":"tuple[]"},{"components":[{"internalType":"contract IJBBuybackHook","name":"hook","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint32","name":"twapWindow","type":"uint32"},{"internalType":"uint32","name":"twapSlippageTolerance","type":"uint32"}],"internalType":"struct REVBuybackPoolConfig[]","name":"poolConfigurations","type":"tuple[]"}],"internalType":"struct REVBuybackHookConfig","name":"buybackHookConfiguration","type":"tuple"},{"components":[{"components":[{"internalType":"contract IJBSuckerDeployer","name":"deployer","type":"address"},{"components":[{"internalType":"address","name":"localToken","type":"address"},{"internalType":"uint32","name":"minGas","type":"uint32"},{"internalType":"address","name":"remoteToken","type":"address"},{"internalType":"uint256","name":"minBridgeAmount","type":"uint256"}],"internalType":"struct JBTokenMapping[]","name":"mappings","type":"tuple[]"}],"internalType":"struct JBSuckerDeployerConfig[]","name":"deployerConfigurations","type":"tuple[]"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct REVSuckerDeploymentConfig","name":"suckerDeploymentConfiguration","type":"tuple"}],"name":"deployFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"internalType":"bytes","name":"encodedConfiguration","type":"bytes"},{"components":[{"components":[{"internalType":"contract IJBSuckerDeployer","name":"deployer","type":"address"},{"components":[{"internalType":"address","name":"localToken","type":"address"},{"internalType":"uint32","name":"minGas","type":"uint32"},{"internalType":"address","name":"remoteToken","type":"address"},{"internalType":"uint256","name":"minBridgeAmount","type":"uint256"}],"internalType":"struct JBTokenMapping[]","name":"mappings","type":"tuple[]"}],"internalType":"struct JBSuckerDeployerConfig[]","name":"deployerConfigurations","type":"tuple[]"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct REVSuckerDeploymentConfig","name":"suckerDeploymentConfiguration","type":"tuple"}],"name":"deploySuckersFor","outputs":[{"internalType":"address[]","name":"suckers","type":"address[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"ticker","type":"string"},{"internalType":"string","name":"uri","type":"string"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct REVDescription","name":"description","type":"tuple"},{"internalType":"uint32","name":"baseCurrency","type":"uint32"},{"internalType":"address","name":"splitOperator","type":"address"},{"components":[{"internalType":"uint40","name":"startsAtOrAfter","type":"uint40"},{"components":[{"internalType":"uint32","name":"chainId","type":"uint32"},{"internalType":"uint104","name":"count","type":"uint104"},{"internalType":"address","name":"beneficiary","type":"address"}],"internalType":"struct REVAutoMint[]","name":"autoMints","type":"tuple[]"},{"internalType":"uint16","name":"splitPercent","type":"uint16"},{"internalType":"uint112","name":"initialIssuance","type":"uint112"},{"internalType":"uint32","name":"issuanceDecayFrequency","type":"uint32"},{"internalType":"uint32","name":"issuanceDecayPercent","type":"uint32"},{"internalType":"uint16","name":"cashOutTaxRate","type":"uint16"},{"internalType":"uint16","name":"extraMetadata","type":"uint16"}],"internalType":"struct REVStageConfig[]","name":"stageConfigurations","type":"tuple[]"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"contract IJBPayoutTerminal","name":"terminal","type":"address"}],"internalType":"struct REVLoanSource[]","name":"loanSources","type":"tuple[]"},{"internalType":"address","name":"loans","type":"address"},{"internalType":"bool","name":"allowCrosschainSuckerExtension","type":"bool"}],"internalType":"struct REVConfig","name":"configuration","type":"tuple"},{"components":[{"internalType":"contract IJBTerminal","name":"terminal","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint32","name":"currency","type":"uint32"}],"internalType":"struct JBAccountingContext[]","name":"accountingContextsToAccept","type":"tuple[]"}],"internalType":"struct JBTerminalConfig[]","name":"terminalConfigurations","type":"tuple[]"},{"components":[{"internalType":"contract IJBBuybackHook","name":"hook","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint32","name":"twapWindow","type":"uint32"},{"internalType":"uint32","name":"twapSlippageTolerance","type":"uint32"}],"internalType":"struct REVBuybackPoolConfig[]","name":"poolConfigurations","type":"tuple[]"}],"internalType":"struct REVBuybackHookConfig","name":"buybackHookConfiguration","type":"tuple"},{"components":[{"components":[{"internalType":"contract IJBSuckerDeployer","name":"deployer","type":"address"},{"components":[{"internalType":"address","name":"localToken","type":"address"},{"internalType":"uint32","name":"minGas","type":"uint32"},{"internalType":"address","name":"remoteToken","type":"address"},{"internalType":"uint256","name":"minBridgeAmount","type":"uint256"}],"internalType":"struct JBTokenMapping[]","name":"mappings","type":"tuple[]"}],"internalType":"struct JBSuckerDeployerConfig[]","name":"deployerConfigurations","type":"tuple[]"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct REVSuckerDeploymentConfig","name":"suckerDeploymentConfiguration","type":"tuple"},{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"string","name":"baseUri","type":"string"},{"internalType":"contract IJB721TokenUriResolver","name":"tokenUriResolver","type":"address"},{"internalType":"string","name":"contractUri","type":"string"},{"components":[{"components":[{"internalType":"uint104","name":"price","type":"uint104"},{"internalType":"uint32","name":"initialSupply","type":"uint32"},{"internalType":"uint32","name":"votingUnits","type":"uint32"},{"internalType":"uint16","name":"reserveFrequency","type":"uint16"},{"internalType":"address","name":"reserveBeneficiary","type":"address"},{"internalType":"bytes32","name":"encodedIPFSUri","type":"bytes32"},{"internalType":"uint24","name":"category","type":"uint24"},{"internalType":"uint8","name":"discountPercent","type":"uint8"},{"internalType":"bool","name":"allowOwnerMint","type":"bool"},{"internalType":"bool","name":"useReserveBeneficiaryAsDefault","type":"bool"},{"internalType":"bool","name":"transfersPausable","type":"bool"},{"internalType":"bool","name":"useVotingUnits","type":"bool"},{"internalType":"bool","name":"cannotBeRemoved","type":"bool"},{"internalType":"bool","name":"cannotIncreaseDiscountPercent","type":"bool"}],"internalType":"struct JB721TierConfig[]","name":"tiers","type":"tuple[]"},{"internalType":"uint32","name":"currency","type":"uint32"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"contract IJBPrices","name":"prices","type":"address"}],"internalType":"struct JB721InitTiersConfig","name":"tiersConfig","type":"tuple"},{"internalType":"address","name":"reserveBeneficiary","type":"address"},{"components":[{"internalType":"bool","name":"noNewTiersWithReserves","type":"bool"},{"internalType":"bool","name":"noNewTiersWithVotes","type":"bool"},{"internalType":"bool","name":"noNewTiersWithOwnerMinting","type":"bool"},{"internalType":"bool","name":"preventOverspending","type":"bool"}],"internalType":"struct JB721TiersHookFlags","name":"flags","type":"tuple"}],"internalType":"struct JBDeploy721TiersHookConfig","name":"baseline721HookConfiguration","type":"tuple"},{"internalType":"bool","name":"splitOperatorCanAdjustTiers","type":"bool"},{"internalType":"bool","name":"splitOperatorCanUpdateMetadata","type":"bool"},{"internalType":"bool","name":"splitOperatorCanMint","type":"bool"},{"internalType":"bool","name":"splitOperatorCanIncreaseDiscountPercent","type":"bool"}],"internalType":"struct REVDeploy721TiersHookConfig","name":"tiered721HookConfiguration","type":"tuple"},{"components":[{"internalType":"uint24","name":"category","type":"uint24"},{"internalType":"uint104","name":"minimumPrice","type":"uint104"},{"internalType":"uint32","name":"minimumTotalSupply","type":"uint32"},{"internalType":"uint32","name":"maximumTotalSupply","type":"uint32"},{"internalType":"address[]","name":"allowedAddresses","type":"address[]"}],"internalType":"struct REVCroptopAllowedPost[]","name":"allowedPosts","type":"tuple[]"}],"name":"deployWith721sFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"contract IJB721TiersHook","name":"hook","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"internalType":"address","name":"addr","type":"address"}],"name":"hasMintPermissionFor","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"internalType":"address","name":"addr","type":"address"}],"name":"isSplitOperatorOf","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"}],"name":"loansOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"},{"internalType":"address","name":"newSplitOperator","type":"address"}],"name":"setSplitOperatorOf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"}],"name":"tiered721HookOf","outputs":[{"internalType":"contract IJB721TiersHook","name":"tiered721Hook","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"revnetId","type":"uint256"}],"name":"unrealizedAutoMintAmountOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] diff --git a/ui/const/config.ts b/ui/const/config.ts index 7c7d2725..627cc7b7 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -108,6 +108,10 @@ export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { export const PRIZE_TOKEN_ADDRESSES: Index = { sepolia: '0xf2a29F67fb5e6d7B9682591c0fD100d357dA85A7', } +export const BULK_TOKEN_SENDER_ADDRESSES: Index = { + sepolia: '0xfEcb8E75658d768C9CdB418d81607eF4Dab5d001', +} + export const PRIZE_DECIMALS = 18 export const CITIZEN_WHITELIST_ADDRESSES: Index = { @@ -159,6 +163,12 @@ export const JOBS_TABLE_ADDRESSES: Index = { sepolia: '0x1c588261069604490e73188343B2C930F35ae2EF', } +export const PRIZE_REVNET_ID = 50 + +export const REVNET_ADDRESSES: Index = { + sepolia: '0x25bc5d5a708c2e426ef3a5196cc18de6b2d5a3d1', +} + export const MARKETPLACE_ADDRESS = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? polygonConfig.Marketplace diff --git a/ui/lib/revnet/hooks/useIsOperator.tsx b/ui/lib/revnet/hooks/useIsOperator.tsx new file mode 100644 index 00000000..e1e529b9 --- /dev/null +++ b/ui/lib/revnet/hooks/useIsOperator.tsx @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react' + +export default function useIsOperator( + revnetContract: any, + operator: string, + revnetId: number +) { + const [isOperator, setIsOperator] = useState(false) + useEffect(() => { + async function checkIsOperator() { + if (!revnetContract) return + const isOperator = await revnetContract.call('isSplitOperatorOf', [ + revnetId, + operator, + ]) + setIsOperator(isOperator) + } + checkIsOperator() + }, [revnetContract]) + return isOperator +} diff --git a/ui/lib/tokens/hooks/useTokenBalances.tsx b/ui/lib/tokens/hooks/useTokenBalances.tsx index 4e8f29a4..f6216b80 100644 --- a/ui/lib/tokens/hooks/useTokenBalances.tsx +++ b/ui/lib/tokens/hooks/useTokenBalances.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' export default function useTokenBalances( tokenContract: any, diff --git a/ui/lib/tokens/hooks/useTokenSupply.tsx b/ui/lib/tokens/hooks/useTokenSupply.tsx new file mode 100644 index 00000000..2054e1bc --- /dev/null +++ b/ui/lib/tokens/hooks/useTokenSupply.tsx @@ -0,0 +1,16 @@ +import { useEffect, useState } from 'react' + +export default function useTokenSupply(tokenContract: any, decimals: number) { + const [tokenSupply, setTokenSupply] = useState(0) + + useEffect(() => { + async function getSupply() { + if (!tokenContract) return + const supply = await tokenContract.call('totalSupply') + setTokenSupply(+supply.toString() / 10 ** decimals) + } + + getSupply() + }, [tokenContract]) + return tokenSupply +} diff --git a/ui/pages/_app.tsx b/ui/pages/_app.tsx index 626e7cc4..8c54d9bf 100644 --- a/ui/pages/_app.tsx +++ b/ui/pages/_app.tsx @@ -14,8 +14,6 @@ function App({ Component, pageProps: { session, ...pageProps } }: any) { const [selectedChain, setSelectedChain]: any = useState( process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia ) - console.log('selectedChain') - console.log(selectedChain) const [lightMode, setLightMode] = useLightMode() diff --git a/ui/public/sitemap-0.xml b/ui/public/sitemap-0.xml index 7420db2d..2ee6ddeb 100644 --- a/ui/public/sitemap-0.xml +++ b/ui/public/sitemap-0.xml @@ -1,29 +1,34 @@ -amoondao.com2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/about2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/almost-there2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/analytics2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/constitution2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/dude-perfect2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/events2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/get-mooney2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/governance2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/info2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/jobs2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/join2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/join-us2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/lifeship2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/linktree2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/lock2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/marketplace2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/network2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/news2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/propose2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/sweepstakes2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/team2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/thank-you2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/vote2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/zero-gravity2024-07-14T21:19:19.708Zdaily0.7 -moondao.com/4042024-07-14T21:19:19.708Zdaily0.7 +moondao.com2024-10-31T00:28:44.874Zdaily0.7 +moondao.com/about2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/almost-there2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/analytics2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/bridge2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/citizen2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/constitution2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/current-projects2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/deprize2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/dude-perfect2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/events2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/get-mooney2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/governance2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/info2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/jobs2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/join2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/join-us2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/lifeship2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/linktree2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/lock2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/marketplace2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/network2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/news2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/propose2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/rewards2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/sweepstakes2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/team2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/thank-you2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/vote2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/zero-gravity2024-10-31T00:28:44.875Zdaily0.7 +moondao.com/4042024-10-31T00:28:44.875Zdaily0.7 \ No newline at end of file From 5b2f1b0633f9a0bc3872f272baf991d20a75f298 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Thu, 7 Nov 2024 11:14:32 -0800 Subject: [PATCH 09/44] forge install: forge-std v1.9.4 --- .gitmodules | 3 +++ contracts/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 160000 contracts/lib/forge-std diff --git a/.gitmodules b/.gitmodules index fc0f2d5c..d29bbd74 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "contracts/lib/solmate"] path = contracts/lib/solmate url = https://github.com/rari-capital/solmate +[submodule "contracts/lib/forge-std"] + path = contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std new file mode 160000 index 00000000..1eea5bae --- /dev/null +++ b/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 From 5c01d25a020a0f121bb355d1c33463b97a6cbc73 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Fri, 8 Nov 2024 15:02:41 -0800 Subject: [PATCH 10/44] adds VotingEscrowDepositor --- contracts/src/governance/IVotingEscrow.sol | 12 ++ .../src/governance/SmartWalletChecker.sol | 2 +- contracts/src/governance/VotingEscrow.vy | 1 + .../src/governance/VotingEscrowDepositor.sol | 55 +++++++-- .../src/test/VotingEscrowDepositor.t.sol | 114 +++++++++++------- .../src/test/utils/mocks/MockVotingEscrow.sol | 37 ------ 6 files changed, 129 insertions(+), 92 deletions(-) delete mode 100644 contracts/src/test/utils/mocks/MockVotingEscrow.sol diff --git a/contracts/src/governance/IVotingEscrow.sol b/contracts/src/governance/IVotingEscrow.sol index d5877f21..46e1c795 100644 --- a/contracts/src/governance/IVotingEscrow.sol +++ b/contracts/src/governance/IVotingEscrow.sol @@ -10,4 +10,16 @@ interface IVotingEscrow { function locked(address) external view returns (LockedBalance memory); function balanceOf(address) external view returns (uint256); + function deploy_for(address _addr, uint256 _value) external; + function commit_smart_wallet_checker(address) external; + function apply_smart_wallet_checker() external; + + function admin() external view returns (address); + function create_lock(uint256 _value, uint256 _unlock_time) external; + function totalSupply() external view returns (uint256); + function checkpoint() external; + function user_point_history(address, uint256) external view returns (uint256, uint256); + function user_point_history__ts(address, uint256) external view returns (uint256); + function get_last_user_slope(address) external view returns (int128); + } diff --git a/contracts/src/governance/SmartWalletChecker.sol b/contracts/src/governance/SmartWalletChecker.sol index 5b9be392..400e2b16 100644 --- a/contracts/src/governance/SmartWalletChecker.sol +++ b/contracts/src/governance/SmartWalletChecker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.6; +pragma solidity 0.8.10; //import "@openzeppelin/contracts-4.2.0/access/Ownable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/contracts/src/governance/VotingEscrow.vy b/contracts/src/governance/VotingEscrow.vy index c61a69a0..66fd976f 100644 --- a/contracts/src/governance/VotingEscrow.vy +++ b/contracts/src/governance/VotingEscrow.vy @@ -94,6 +94,7 @@ epoch: public(uint256) point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] user_point_epoch: public(HashMap[address, uint256]) + slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change # Aragon's view methods for compatibility diff --git a/contracts/src/governance/VotingEscrowDepositor.sol b/contracts/src/governance/VotingEscrowDepositor.sol index 98409859..1e9b7fe2 100644 --- a/contracts/src/governance/VotingEscrowDepositor.sol +++ b/contracts/src/governance/VotingEscrowDepositor.sol @@ -1,25 +1,60 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface IERC20 { +import "@openzeppelin/contracts/access/Ownable.sol"; + +interface IERC20Interface { function transfer(address recipient, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); } -interface IVotingEscrow { +interface IVotingEscrowInterface { function deposit_for(address _addr, uint256 _value) external; } -contract VotingEscrowDepositor { - IERC20 public token; - IVotingEscrow public escrowToken; +contract VotingEscrowDepositor is Ownable{ + IERC20Interface public token; + IVotingEscrowInterface public escrowToken; + // map from address to amount availabe to withdraw + mapping(address => uint256) public availableToWithdraw; + address[] public withdrawAddresses; constructor(address _tokenAddress, address _escrowTokenAddress) { - token = IERC20(_tokenAddress); - escrowToken = IVotingEscrow(_escrowTokenAddress); + token = IERC20Interface(_tokenAddress); + escrowToken = IVotingEscrowInterface(_escrowTokenAddress); + } + + function increaseWithdrawAmounts(address[] memory addresses, uint256[] memory amounts) external onlyOwner{ + require(addresses.length == amounts.length, "Arrays must be of equal length"); + uint256 totalAmount = 0; + for (uint256 i = 0; i < addresses.length; i++) { + if (availableToWithdraw[addresses[i]] == 0) { + withdrawAddresses.push(addresses[i]); + } + availableToWithdraw[addresses[i]] += amounts[i]; + totalAmount += amounts[i]; + } + require(token.transferFrom(msg.sender, address(this), totalAmount), "Token transfer failed"); + } + + function clearWithdrawAmounts() external onlyOwner { + for (uint256 i = 0; i < withdrawAddresses.length; i++) { + availableToWithdraw[withdrawAddresses[i]] = 0; + } + delete withdrawAddresses; + } + + function withdraw() external { + uint256 amount = availableToWithdraw[msg.sender]; + require(amount > 0, "No amount available to withdraw"); + availableToWithdraw[msg.sender] = 0; + require(token.transfer(msg.sender, amount), "Token transfer failed"); + escrowToken.deposit_for(msg.sender, amount); } - function transfer_and_deposit_for(address addr, uint256 value) external { - require(token.transfer(addr, value), "Token transfer failed"); - escrowToken.deposit_for(addr, value); + function returnTokens() external onlyOwner { + uint256 balance = token.balanceOf(address(this)); + require(token.transfer(msg.sender, balance), "Token transfer failed"); } } diff --git a/contracts/src/test/VotingEscrowDepositor.t.sol b/contracts/src/test/VotingEscrowDepositor.t.sol index 9a9374b1..94c4eddc 100644 --- a/contracts/src/test/VotingEscrowDepositor.t.sol +++ b/contracts/src/test/VotingEscrowDepositor.t.sol @@ -1,9 +1,11 @@ - // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "../src/VotingEscrowDepositor.sol"; +import "../governance/VotingEscrowDepositor.sol"; +import "../governance/SmartWalletChecker.sol"; +import "../governance/IVotingEscrow.sol"; +import "../tokens/MyToken.sol"; //Using deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 //Deploying MOONEY.. @@ -13,64 +15,88 @@ import "../src/VotingEscrowDepositor.sol"; //Deployed vMOONEY to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 //Deployment manifest saved to ./deployments/local.json -address MOONEY = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512; -address vMOONEY = 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0; -contract MockToken is IERC20 { - mapping(address => uint256) public balanceOf; - - function transfer(address recipient, uint256 amount) external override returns (bool) { - require(balanceOf[msg.sender] >= amount, "Insufficient balance"); - balanceOf[msg.sender] -= amount; - balanceOf[recipient] += amount; - return true; - } - - function mint(address to, uint256 amount) external { - balanceOf[to] += amount; - } -} - -contract MockVotingEscrow is IVotingEscrow { - mapping(address => uint256) public deposits; - - function deposit_for(address _addr, uint256 _value) external override { - deposits[_addr] += _value; - } -} - contract VotingEscrowDepositorTest is Test { VotingEscrowDepositor public depositor; - MockToken public token; - MockVotingEscrow public escrowToken; + MyToken public token; + IVotingEscrow public escrowToken; + SmartWalletChecker public checker; + //0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 address public user = address(0x123); - uint256 public initialBalance = 1000; + //uint256 public initialBalance = 1000; + uint256 public initialBalance = 126144000 * 2; + uint256 depositAmount = 126144000; + + address public MOONEY = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512; + address public vMOONEY = 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0; function setUp() public { - token = new MockToken(); - escrowToken = new MockVotingEscrow(); - depositor = new VotingEscrowDepositor(address(MOONEY), address(vMOONEY)); + token = MyToken(MOONEY); + escrowToken = IVotingEscrow(vMOONEY); + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + depositor = new VotingEscrowDepositor(address(MOONEY), address(vMOONEY)); + checker = new SmartWalletChecker(true); + checker.approveWallet(address(depositor)); + // TODO we really shouldn't need this? + checker.approveWallet(address(user)); + //escrowToken.admin(); + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + escrowToken.commit_smart_wallet_checker(address(checker)); + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + escrowToken.apply_smart_wallet_checker(); + //token.transfer(address(depositor), initialBalance); + address[] memory addresses = new address[](1); + addresses[0] = address(user); + uint256[] memory amounts = new uint256[](1); + amounts[0] = depositAmount; + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + token.approve(address(depositor), depositAmount); + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + depositor.increaseWithdrawAmounts(addresses, amounts); + + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + token.transfer(address(user), initialBalance); + vm.prank(user); + token.approve(address(escrowToken), initialBalance); + vm.prank(user); + escrowToken.create_lock(initialBalance, block.timestamp + 4*60 * 60 * 24 * 365); // Mint tokens for the depositor contract to transfer - token.mint(address(depositor), initialBalance); + //token.mint(address(depositor), initialBalance); } function testTransferAndDepositFor() public { - uint256 depositAmount = 500; + //uint256 depositAmount = 500; + // Verify initial balances and deposits - assertEq(token.balanceOf(address(depositor)), initialBalance); - assertEq(token.balanceOf(user), 0); - assertEq(escrowToken.deposits(user), 0); + //assertEq(token.balanceOf(address(depositor)), initialBalance); + //assertEq(token.balanceOf(user), 0); + //assertEq(escrowToken.balanceOf(user), initialBalance); + escrowToken.checkpoint(); + + //assertEq(escrowToken.admin(), address(0)); // Call transfer_and_deposit_for - depositor.transfer_and_deposit_for(user, depositAmount); + vm.prank(user); + token.approve(address(escrowToken), depositAmount); + vm.prank(user); + //depositor.transfer_and_deposit_for(user, depositAmount); + depositor.withdraw(); // Verify token transfer - assertEq(token.balanceOf(address(depositor)), initialBalance - depositAmount); - assertEq(token.balanceOf(user), depositAmount); - - // Verify escrow deposit - assertEq(escrowToken.deposits(user), depositAmount); + //assertEq(token.balanceOf(address(depositor)), initialBalance - depositAmount); + //assertEq(token.balanceOf(user), 0); + ////assertEq(escrowToken.totalSupply(), initialBalance + depositAmount); + //escrowToken.user_point_history(user, 0); + //escrowToken.user_point_history(user, 1); + //escrowToken.user_point_history(user, 2); + //escrowToken.get_last_user_slope(user); + //escrowToken.user_point_history__ts(user, 0); + //escrowToken.user_point_history__ts(user, 1); + //escrowToken.user_point_history__ts(user, 2); + + //// Verify escrow deposit + assertEq(escrowToken.balanceOf(user), depositAmount + initialBalance); } } diff --git a/contracts/src/test/utils/mocks/MockVotingEscrow.sol b/contracts/src/test/utils/mocks/MockVotingEscrow.sol deleted file mode 100644 index a5293439..00000000 --- a/contracts/src/test/utils/mocks/MockVotingEscrow.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; -import {IVotingEscrow} from "../../../governance/IVotingEscrow.sol"; - -contract MockVotingEscrow is IVotingEscrow { - string name; - string symbol; - - mapping(address => uint256) internal _balance; - - constructor(string memory _name, string memory _symbol) { - name = _name; - symbol = _symbol; - } - - function allowance(address owner, address spender) external view returns (uint256) {} - - function approve(address spender, uint256 amount) external returns (bool) {} - - function totalSupply() external view returns (uint256) {} - - function transfer(address to, uint256 amount) external returns (bool) {} - - function transferFrom(address from, address to, uint256 amount) external returns (bool) {} - - function setBalance(address to, uint256 amount) public { - _balance[to] = amount; - } - - function balanceOf(address account) public view returns (uint256) { - return _balance[account]; - } - - function locked(address) external view returns (LockedBalance memory) {} -} - - From 5208c18e6ef91b28b80cd87c5170c1bdc0f7053f Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Fri, 15 Nov 2024 13:26:10 -0800 Subject: [PATCH 11/44] wip --- contracts/foundry.toml | 2 +- contracts/scripts/VotingEscrowDepositor.s.sol | 29 +++++++++++++ .../src/governance/SmartWalletChecker.sol | 2 +- .../src/governance/VotingEscrowDepositor.sol | 7 +++- .../src/test/VotingEscrowDepositor.t.sol | 15 ++++--- ui/components/nance/RetroactiveRewards.tsx | 42 +++++++++++++++++++ ui/const/config.ts | 9 ++++ ui/lib/utils/hooks/useWithdrawAmount.ts | 23 ++++++++++ 8 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 contracts/scripts/VotingEscrowDepositor.s.sol create mode 100644 ui/lib/utils/hooks/useWithdrawAmount.ts diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 2fe11be0..a5f4ed35 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -5,7 +5,7 @@ libs = ["lib"] evm_version = "berlin" #[profile.default.compiler] -#version = "0.8.17" +#version = "0.8.10" # Vyper compiler settings [profile.default.vyper] diff --git a/contracts/scripts/VotingEscrowDepositor.s.sol b/contracts/scripts/VotingEscrowDepositor.s.sol new file mode 100644 index 00000000..b11b2561 --- /dev/null +++ b/contracts/scripts/VotingEscrowDepositor.s.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.8.0; + +import "forge-std/Script.sol"; +import "../src/governance/VotingEscrowDepositor.sol"; + +contract MyScript is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + uint256 networkChainId = block.chainid; + address MOONEY; + address VMOONEY; + // arbitrum + if (networkChainId == 42161) { + MOONEY = 0x1Fa56414549BdccBB09916f61f0A5827f779a85c; + VMOONEY = 0xB255c74F8576f18357cE6184DA033c6d93C71899; + } else if (networkChainId == 11155111) { + MOONEY = 0x85A3C597F43B0cCE657793Cf31b05DF6969FbD2C; + VMOONEY = 0xA4F6A4B135b9AF7909442A7a3bF7797b61e609b1; + } else { + revert("Unsupported network"); + } + + + VotingEscrowDepositor sender = new VotingEscrowDepositor(MOONEY, VMOONEY); + + vm.stopBroadcast(); + } +} diff --git a/contracts/src/governance/SmartWalletChecker.sol b/contracts/src/governance/SmartWalletChecker.sol index 400e2b16..752ec7a5 100644 --- a/contracts/src/governance/SmartWalletChecker.sol +++ b/contracts/src/governance/SmartWalletChecker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.10; +pragma solidity ^0.8.0; //import "@openzeppelin/contracts-4.2.0/access/Ownable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/contracts/src/governance/VotingEscrowDepositor.sol b/contracts/src/governance/VotingEscrowDepositor.sol index 1e9b7fe2..04930848 100644 --- a/contracts/src/governance/VotingEscrowDepositor.sol +++ b/contracts/src/governance/VotingEscrowDepositor.sol @@ -20,7 +20,7 @@ contract VotingEscrowDepositor is Ownable{ mapping(address => uint256) public availableToWithdraw; address[] public withdrawAddresses; - constructor(address _tokenAddress, address _escrowTokenAddress) { + constructor(address _tokenAddress, address _escrowTokenAddress){ token = IERC20Interface(_tokenAddress); escrowToken = IVotingEscrowInterface(_escrowTokenAddress); } @@ -57,4 +57,9 @@ contract VotingEscrowDepositor is Ownable{ uint256 balance = token.balanceOf(address(this)); require(token.transfer(msg.sender, balance), "Token transfer failed"); } + + function sendVotingEscrowTokens(address _addr, uint256 _value) external onlyOwner { + require(token.transfer(_addr, _value), "Token transfer failed"); + escrowToken.deposit_for(_addr, _value); + } } diff --git a/contracts/src/test/VotingEscrowDepositor.t.sol b/contracts/src/test/VotingEscrowDepositor.t.sol index 94c4eddc..8f5934d1 100644 --- a/contracts/src/test/VotingEscrowDepositor.t.sol +++ b/contracts/src/test/VotingEscrowDepositor.t.sol @@ -27,8 +27,10 @@ contract VotingEscrowDepositorTest is Test { uint256 public initialBalance = 126144000 * 2; uint256 depositAmount = 126144000; - address public MOONEY = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512; - address public vMOONEY = 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0; + address public MOONEY = 0x5FbDB2315678afecb367f032d93F642f64180aa3; + + address public vMOONEY = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512; + function setUp() public { token = MyToken(MOONEY); @@ -54,7 +56,6 @@ contract VotingEscrowDepositorTest is Test { token.approve(address(depositor), depositAmount); vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); depositor.increaseWithdrawAmounts(addresses, amounts); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); token.transfer(address(user), initialBalance); vm.prank(user); @@ -63,6 +64,10 @@ contract VotingEscrowDepositorTest is Test { escrowToken.create_lock(initialBalance, block.timestamp + 4*60 * 60 * 24 * 365); // Mint tokens for the depositor contract to transfer //token.mint(address(depositor), initialBalance); + vm.prank(user); + token.approve(address(escrowToken), depositAmount); + vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + depositor.sendVotingEscrowTokens(address(user), depositAmount); } function testTransferAndDepositFor() public { @@ -79,10 +84,8 @@ contract VotingEscrowDepositorTest is Test { // Call transfer_and_deposit_for vm.prank(user); - token.approve(address(escrowToken), depositAmount); - vm.prank(user); //depositor.transfer_and_deposit_for(user, depositAmount); - depositor.withdraw(); + //depositor.withdraw(); // Verify token transfer //assertEq(token.balanceOf(address(depositor)), initialBalance - depositAmount); diff --git a/ui/components/nance/RetroactiveRewards.tsx b/ui/components/nance/RetroactiveRewards.tsx index 12de480f..a8458c7a 100644 --- a/ui/components/nance/RetroactiveRewards.tsx +++ b/ui/components/nance/RetroactiveRewards.tsx @@ -3,6 +3,7 @@ import { useAddress, useContract } from '@thirdweb-dev/react' import { DISTRIBUTION_TABLE_ADDRESSES, SNAPSHOT_RETROACTIVE_REWARDS_ID, + VOTING_ESCROW_DEPOSITOR_ADDRESSES, } from 'const/config' import _ from 'lodash' import { useState, useEffect } from 'react' @@ -13,6 +14,7 @@ import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' import { useVotingPowers } from '@/lib/snapshot' import useWindowSize from '@/lib/team/use-window-size' +import useWithdrawAmount from '@/lib/utils/hooks/useWithdrawAmount' import { getBudget, getPayouts } from '@/lib/utils/rewards' import { computeRewardPercentages } from '@/lib/utils/voting' import Asset from '@/components/dashboard/treasury/balance/Asset' @@ -125,6 +127,21 @@ export function RetroactiveRewards({ const { contract: distributionTableContract } = useContract( DISTRIBUTION_TABLE_ADDRESSES[chain.slug] ) + const { contract: votingEscrowDepositorContract } = useContract( + VOTING_ESCROW_DEPOSITOR_ADDRESSES[chain.slug] + ) + //console.log('votingEscrowDepositorContract') + //console.log(votingEscrowDepositorContract) + const withdrawable = useWithdrawAmount( + votingEscrowDepositorContract, + userAddress + ) + //console.log('withdrawable') + //console.log(withdrawable) + ;(async () => { + console.log(await withdrawable) + })() + const { tokens } = useAssets() const { ethBudget, usdBudget, mooneyBudget, ethPrice } = getBudget( tokens, @@ -205,6 +222,22 @@ export function RetroactiveRewards({ }) } } + const handleWithdraw = async () => { + try { + await votingEscrowDepositorContract?.call('withdraw', [userAddress]) + toast.success('Withdrawal successful!', { + style: toastStyle, + }) + setTimeout(() => { + refreshRewards() + }, 5000) + } catch (error) { + console.error('Error withdrawing:', error) + toast.error('Error withdrawing. Please try again.', { + style: toastStyle, + }) + } + } return (
@@ -394,6 +427,15 @@ export function RetroactiveRewards({ )} + {userAddress && withdrawable > 0 && ( + + Withdraw Rewards + + )} diff --git a/ui/const/config.ts b/ui/const/config.ts index 627cc7b7..317a2b14 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -101,6 +101,15 @@ export const DISTRIBUTION_TABLE_ADDRESSES: Index = { arbitrum: '0xabD8D3693439A72393220d87aee159952261Ad1f', 'arbitrum-sepolia': '0xd1D57F18252D06a6b28DE96B6cbF7F4283A4F205', } +export const VOTING_ESCROW_DEPOSITOR_ADDRESSES: Index = { + arbitrum: '0xBE19a62384014F103686dfE6D9d50B1D3E81B2d0', + sepolia: '0xe77ede9B472E9AE450a1AcD4A90dcd3fb2e50cD0', +} +export const SMART_WALLET_CHECKER_ADDRESSES: Index = { + arbitrum: '0x609BaFab765135091DB407b53D77c4C471Df3e8F', + sepolia: '0x1cF442024fAeE8aAF2d5E26d79718231Bf59d740', +} + export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { sepolia: '0xA441f20115c868dc66bC1977E1c17D4B9A0189c7', } diff --git a/ui/lib/utils/hooks/useWithdrawAmount.ts b/ui/lib/utils/hooks/useWithdrawAmount.ts new file mode 100644 index 00000000..c077d4c5 --- /dev/null +++ b/ui/lib/utils/hooks/useWithdrawAmount.ts @@ -0,0 +1,23 @@ +import { useState, useEffect } from 'react' + +export default function useWithdrawAmount( + votingEscrowDepositorContract: any, + userAddress: string +) { + const [withdrawAmount, setWithdrawAmount] = useState(0) + + useEffect(() => { + async function fetchWithdrawAmount() { + if (!votingEscrowDepositorContract || !userAddress) return + const theWithdrawAmount = await votingEscrowDepositorContract.call( + 'availableToWithdraw', + [userAddress] + ) + setWithdrawAmount(theWithdrawAmount) + } + + fetchWithdrawAmount() + }, [votingEscrowDepositorContract, userAddress]) + + return withdrawAmount +} From 2d8583175f92f827a4b21170b62b27811322e3d5 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Wed, 20 Nov 2024 14:26:59 -0800 Subject: [PATCH 12/44] wip --- README.md | 2 +- ui/components/nance/DePrize.tsx | 108 +++++++++++++++++++++++++- ui/components/onboarding/TeamTier.tsx | 17 ++-- ui/const/config.ts | 4 +- ui/pages/_app.tsx | 4 +- ui/pages/deprize.tsx | 10 ++- 6 files changed, 126 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0e2c8a5d..366e22a9 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,4 @@ cp .env.testnet .env.local Start the development server: ``` yarn dev -``` \ No newline at end of file +``` diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 4fe415a9..eb2682b7 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -1,22 +1,27 @@ -import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' +import { Arbitrum, Sepolia, ArbitrumSepolia } from '@thirdweb-dev/chains' import { useAddress, useContract } from '@thirdweb-dev/react' +import CompetitorABI from 'const/abis/Competitor.json' import ERC20 from 'const/abis/ERC20.json' import REVDeployer from 'const/abis/REVDeployer.json' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, SNAPSHOT_RETROACTIVE_REWARDS_ID, PRIZE_TOKEN_ADDRESSES, + COMPETITOR_TABLE_ADDRESSES, PRIZE_DECIMALS, REVNET_ADDRESSES, PRIZE_REVNET_ID, BULK_TOKEN_SENDER_ADDRESSES, } from 'const/config' +import { TEAM_ADDRESSES } from 'const/config' import { BigNumber } from 'ethers' import _ from 'lodash' +import { useRouter } from 'next/router' import { useState, useEffect } from 'react' import toast from 'react-hot-toast' import { useCitizens } from '@/lib/citizen/useCitizen' import { useAssets } from '@/lib/dashboard/hooks' +import { useTeamWearer } from '@/lib/hats/useTeamWearer' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' import useIsOperator from '@/lib/revnet/hooks/useIsOperator' @@ -31,6 +36,7 @@ import Asset from '@/components/dashboard/treasury/balance/Asset' import Container from '@/components/layout/Container' import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' +import Modal from '@/components/layout/Modal' import { NoticeFooter } from '@/components/layout/NoticeFooter' import StandardButton from '../layout/StandardButton' @@ -114,6 +120,12 @@ export function DePrize({ REVNET_ADDRESSES[chain.slug], REVDeployer.abi ) + console.log('COMPETITOR_TABLE_ADDRESSES[chain.slug]') + console.log(COMPETITOR_TABLE_ADDRESSES[chain.slug]) + const { contract: competitorContract } = useContract( + COMPETITOR_TABLE_ADDRESSES[chain.slug], + CompetitorABI + ) const { contract: bulkTokenSenderContract } = useContract( BULK_TOKEN_SENDER_ADDRESSES[chain.slug] ) @@ -135,15 +147,18 @@ export function DePrize({ userAddress in addressToQuadraticVotingPower) && addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 + const router = useRouter() // All competitors need at least one citizen distribution to do iterative normalization const isCitizens = useCitizens(chain, addresses) const citizenDistributions = distributions?.filter((_, i) => isCitizens[i]) const nonCitizenDistributions = distributions?.filter( (_, i) => !isCitizens[i] ) - const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => - citizenDistributions.some(({ distribution }) => id in distribution) - ) + console.log('competitors') + console.log(competitors) + //const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => + //citizenDistributions.some(({ distribution }) => id in distribution) + //) const readyToRunVoting = votingPowerSumIsNonZero const budgetPercent = 100 @@ -163,6 +178,10 @@ export function DePrize({ year, quarter ) + //const isCompetitor = competitors.some( + //(competitor) => competitor.treasury === userAddress + //) + const isCompetitor = false const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) const prizeBudget = prizeSupply * 0.1 const winnerPool = prizeSupply * 0.3 @@ -277,6 +296,76 @@ export function DePrize({ }) } } + const DEPRIZE_ID = 1 + const [joinModalOpen, setJoinModalOpen] = useState(false) + + // Get user's teams + const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) + const userTeams = useTeamWearer(teamContract, chain, userAddress) + + const handleJoinWithTeam = async (teamId: string) => { + try { + await competitorContract?.call('insertIntoTable', [ + teamId, + DEPRIZE_ID, + userAddress, + '{}', + ]) + toast.success('Joined as a competitor!', { + style: toastStyle, + }) + setJoinModalOpen(false) + setTimeout(() => { + refreshRewards() + }, 5000) + } catch (error) { + console.error('Error joining as a competitor:', error) + toast.error('Error joining as a competitor. Please try again.', { + style: toastStyle, + }) + } + } + + const JoinModal = () => ( + setJoinModalOpen(false)} + title="Join DePrize Competition" + > +
+

Select a Team or Create New One

+ + {/* Existing Teams */} + {userTeams && userTeams.length > 0 && ( +
+

Your Teams

+
+ {userTeams.map((team: any) => ( + + ))} +
+
+ )} + + {/* Create New Team */} +
+ + Create New Team + +
+
+
+ ) return (
@@ -295,6 +384,17 @@ export function DePrize({ popOverEffect={false} isProfile > + {!isCompetitor && ( + <> + setJoinModalOpen(true)} + className="gradient-2 rounded-full" + > + Join + + {joinModalOpen && } + + )}
{ const [applyModalEnabled, setApplyModalEnabled] = useState(false) const handleTeamClick = async () => { - const teamWhitelistContract = await sdk?.getContract( - TEAM_WHITELIST_ADDRESSES[selectedChain.slug] - ) - const isWhitelisted = await teamWhitelistContract?.call('isWhitelisted', [ - address, - ]) + //const teamWhitelistContract = await sdk?.getContract( + //TEAM_WHITELIST_ADDRESSES[selectedChain.slug] + //) + //const isWhitelisted = await teamWhitelistContract?.call('isWhitelisted', [ + //address, + //]) + const isWhitelisted = true if (isWhitelisted || process.env.NEXT_PUBLIC_ENV === 'dev') { setSelectedTier('team') } else { @@ -47,8 +48,8 @@ const TeamTier = ({ setSelectedTier, compact = false }: TeamTierProps) => { 'Capital Raising Tools: Leverage new tools to raise capital or solicit donations from a global network of space enthusiasts.', 'Onchain Tools: Utilize advanced and secure onchain tools to manage your organization and interface with smart contracts.', ]} - buttoncta={compact ? "Learn More" : "Create a Team"} - onClick={compact ? ()=>{} :handleTeamClick} + buttoncta={compact ? 'Learn More' : 'Create a Team'} + onClick={compact ? () => {} : handleTeamClick} type="team" compact={compact} /> diff --git a/ui/const/config.ts b/ui/const/config.ts index 317a2b14..7998fe34 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -95,7 +95,8 @@ export const PROJECT_TABLE_ADDRESSES: Index = { 'arbitrum-sepolia': '0xF0A3DB6161D1Ee7B99197CDeD4EdFc462EAE80e0', } export const COMPETITOR_TABLE_ADDRESSES: Index = { - sepolia: '0xb5D65d5867eBF99556a0F79fF6eB5A1F5680bFBc', + sepolia: '0x55d76e41B94fe40e8F25eC37574B1AeDE0a6E909', + 'arbitrum-sepolia': '0xEc9Eb5D1474692c0860583e76D3611c5C8F41b7B', } export const DISTRIBUTION_TABLE_ADDRESSES: Index = { arbitrum: '0xabD8D3693439A72393220d87aee159952261Ad1f', @@ -112,6 +113,7 @@ export const SMART_WALLET_CHECKER_ADDRESSES: Index = { export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { sepolia: '0xA441f20115c868dc66bC1977E1c17D4B9A0189c7', + 'arbitrum-sepolia': '0x99263dB0c9BA4e1b48A5c824A6c094D851b62470', } // TODO don't hard code, pull from d-prize contract export const PRIZE_TOKEN_ADDRESSES: Index = { diff --git a/ui/pages/_app.tsx b/ui/pages/_app.tsx index 8c54d9bf..5226dc7d 100644 --- a/ui/pages/_app.tsx +++ b/ui/pages/_app.tsx @@ -1,5 +1,5 @@ import { PrivyProvider } from '@privy-io/react-auth' -import { Chain, Arbitrum, Sepolia } from '@thirdweb-dev/chains' +import { Chain, Arbitrum, Sepolia, ArbitrumSepolia } from '@thirdweb-dev/chains' import { NextQueryParamProvider } from 'next-query-params' import React, { useEffect, useState } from 'react' import { PrivyThirdwebSDKProvider } from '../lib/privy/PrivyThirdwebSDKProvider' @@ -12,7 +12,7 @@ import '../styles/globals.css' function App({ Component, pageProps: { session, ...pageProps } }: any) { const [selectedChain, setSelectedChain]: any = useState( - process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : ArbitrumSepolia ) const [lightMode, setLightMode] = useLightMode() diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index f896c4de..54bd78b9 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -1,4 +1,4 @@ -import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' +import { Arbitrum, Sepolia, ArbitrumSepolia } from '@thirdweb-dev/chains' import CompetitorABI from 'const/abis/Competitor.json' import { COMPETITOR_TABLE_ADDRESSES, @@ -22,8 +22,9 @@ export default function Rewards({ competitors, distributions }: DePrizeProps) { export async function getStaticProps() { // TODO uncomment - //const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia - const chain = Sepolia + const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + //const chain = ArbitrumSepolia + ////const chain = ArbitrumSepolia const sdk = initSDK(chain) const competitorTableContract = await sdk.getContract( @@ -38,6 +39,8 @@ export async function getStaticProps() { const competitorBoardTableName = await competitorTableContract.call( 'getTableName' ) + //console.log('competitorBoardTableName') + //console.log(competitorBoardTableName) const distributionTableName = await distributionTableContract.call( 'getTableName' ) @@ -51,6 +54,7 @@ export async function getStaticProps() { `${TABLELAND_ENDPOINT}?statement=${competitorStatement}` ) const competitors = await competitorsRes.json() + console.log('competitors', competitors) const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId} AND year = ${currentYear} AND quarter = ${currentQuarter}` const distributionsRes = await fetch( From b68c10030601e1d1dd4d7b7fa82611ee9c8ed4a2 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Fri, 22 Nov 2024 11:13:35 -0800 Subject: [PATCH 13/44] wip --- ui/components/nance/DePrize.tsx | 55 ++++++++++++---------- ui/components/subscription/TeamPreview.tsx | 39 +++++++++++++++ ui/const/abis/DePrizeDistribution.json | 1 + ui/const/config.ts | 6 +-- ui/pages/_app.tsx | 2 +- ui/pages/deprize.tsx | 9 +++- 6 files changed, 82 insertions(+), 30 deletions(-) create mode 100644 ui/components/subscription/TeamPreview.tsx create mode 100644 ui/const/abis/DePrizeDistribution.json diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index eb2682b7..6fbdfda0 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -3,6 +3,7 @@ import { useAddress, useContract } from '@thirdweb-dev/react' import CompetitorABI from 'const/abis/Competitor.json' import ERC20 from 'const/abis/ERC20.json' import REVDeployer from 'const/abis/REVDeployer.json' +import TeamABI from 'const/abis/Team.json' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, SNAPSHOT_RETROACTIVE_REWARDS_ID, @@ -14,6 +15,7 @@ import { BULK_TOKEN_SENDER_ADDRESSES, } from 'const/config' import { TEAM_ADDRESSES } from 'const/config' +import { HATS_ADDRESS } from 'const/config' import { BigNumber } from 'ethers' import _ from 'lodash' import { useRouter } from 'next/router' @@ -25,7 +27,6 @@ import { useTeamWearer } from '@/lib/hats/useTeamWearer' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' import useIsOperator from '@/lib/revnet/hooks/useIsOperator' -import { useVotingPowers } from '@/lib/snapshot' import useWindowSize from '@/lib/team/use-window-size' import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' import useTokenSupply from '@/lib/tokens/hooks/useTokenSupply' @@ -33,11 +34,13 @@ import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance' import { getBudget, getPayouts } from '@/lib/utils/rewards' import { runQuadraticVoting } from '@/lib/utils/voting' import Asset from '@/components/dashboard/treasury/balance/Asset' +import { Hat } from '@/components/hats/Hat' import Container from '@/components/layout/Container' import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' import Modal from '@/components/layout/Modal' import { NoticeFooter } from '@/components/layout/NoticeFooter' +import { TeamPreview } from '@/components/subscription/TeamPreview' import StandardButton from '../layout/StandardButton' export type Metadata = { @@ -107,21 +110,14 @@ export function DePrize({ const addresses = distributions ? distributions.map((d) => d.address) : [] - const { data: _vps } = useVotingPowers( - addresses, - SNAPSHOT_SPACE_NAME, - SNAPSHOT_RETROACTIVE_REWARDS_ID - ) const { contract: prizeContract } = useContract( PRIZE_TOKEN_ADDRESSES[chain.slug], ERC20.abi ) const { contract: revnetContract } = useContract( REVNET_ADDRESSES[chain.slug], - REVDeployer.abi + REVDeployer ) - console.log('COMPETITOR_TABLE_ADDRESSES[chain.slug]') - console.log(COMPETITOR_TABLE_ADDRESSES[chain.slug]) const { contract: competitorContract } = useContract( COMPETITOR_TABLE_ADDRESSES[chain.slug], CompetitorABI @@ -131,11 +127,13 @@ export function DePrize({ ) const isOperator = useIsOperator(revnetContract, userAddress, PRIZE_REVNET_ID) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) - const tokenBalances = useTokenBalances( - prizeContract, - PRIZE_DECIMALS, - addresses - ) + //const prizeBalance = 0 + const tokenBalances = [] + //const tokenBalances = useTokenBalances( + //prizeContract, + //PRIZE_DECIMALS, + //addresses + //) const addressToQuadraticVotingPower = Object.fromEntries( addresses.map((address, i) => [address, Math.sqrt(tokenBalances[i])]) ) @@ -182,7 +180,8 @@ export function DePrize({ //(competitor) => competitor.treasury === userAddress //) const isCompetitor = false - const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) + //const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) + const prizeSupply = 0 const prizeBudget = prizeSupply * 0.1 const winnerPool = prizeSupply * 0.3 const prizePrice = 1 @@ -300,9 +299,11 @@ export function DePrize({ const [joinModalOpen, setJoinModalOpen] = useState(false) // Get user's teams + const { contract: hatsContract } = useContract(HATS_ADDRESS) const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) const userTeams = useTeamWearer(teamContract, chain, userAddress) - + console.log('userTeams') + console.log(userTeams) const handleJoinWithTeam = async (teamId: string) => { try { await competitorContract?.call('insertIntoTable', [ @@ -340,13 +341,19 @@ export function DePrize({

Your Teams

{userTeams.map((team: any) => ( - + <> + + + ))}
@@ -370,7 +377,7 @@ export function DePrize({ return (
diff --git a/ui/components/subscription/TeamPreview.tsx b/ui/components/subscription/TeamPreview.tsx new file mode 100644 index 00000000..d3336185 --- /dev/null +++ b/ui/components/subscription/TeamPreview.tsx @@ -0,0 +1,39 @@ +import { ArrowUpRightIcon } from '@heroicons/react/24/outline' +import { Chain } from '@thirdweb-dev/chains' +import { NFT, ThirdwebNftMedia } from '@thirdweb-dev/react' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' + +type TeamPreviewProps = { + teamId: any + teamContract?: any +} + +export function TeamPreview({ teamId, teamContract }: TeamPreviewProps) { + const [teamNFT, setTeamNFT] = useState() + + useEffect(() => { + async function getTeamNFT() { + const nft = await teamContract.erc721.get(teamId) + setTeamNFT(nft) + } + + if (teamContract?.erc721?.get && teamId) { + getTeamNFT() + } + }, [teamId, teamContract]) + + return ( +
+ {teamNFT && ( +
+ +
+ )} +
+ ) +} diff --git a/ui/const/abis/DePrizeDistribution.json b/ui/const/abis/DePrizeDistribution.json new file mode 100644 index 00000000..f6ac8fcc --- /dev/null +++ b/ui/const/abis/DePrizeDistribution.json @@ -0,0 +1 @@ +[{"type":"constructor","inputs":[{"name":"_table_prefix","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"type":"function","name":"deleteFromTable","inputs":[{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"quarter","type":"uint256","internalType":"uint256"},{"name":"year","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getTableId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTableName","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"insertIntoTable","inputs":[{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"quarter","type":"uint256","internalType":"uint256"},{"name":"year","type":"uint256","internalType":"uint256"},{"name":"distribution","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"onERC721Received","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"bytes4","internalType":"bytes4"}],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setAccessControl","inputs":[{"name":"controller","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"updateTableCol","inputs":[{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"quarter","type":"uint256","internalType":"uint256"},{"name":"year","type":"uint256","internalType":"uint256"},{"name":"distribution","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"ChainNotSupported","inputs":[{"name":"chainid","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"StringsInsufficientHexLength","inputs":[{"name":"value","type":"uint256","internalType":"uint256"},{"name":"length","type":"uint256","internalType":"uint256"}]}] diff --git a/ui/const/config.ts b/ui/const/config.ts index b18a0689..9c77ac9f 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -117,8 +117,8 @@ export const SMART_WALLET_CHECKER_ADDRESSES: Index = { } export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { - sepolia: '0xA441f20115c868dc66bC1977E1c17D4B9A0189c7', - 'arbitrum-sepolia': '0x99263dB0c9BA4e1b48A5c824A6c094D851b62470', + sepolia: '0xd785f72027be7c3dcaDB06E69d7f18bb195fB296', + 'arbitrum-sepolia': '0xBE19a62384014F103686dfE6D9d50B1D3E81B2d0', } // TODO don't hard code, pull from d-prize contract export const PRIZE_TOKEN_ADDRESSES: Index = { @@ -176,7 +176,7 @@ export const TEAM_DISCOUNTLIST_ADDRESSES: Index = { export const MOONDAO_HAT_TREE_IDS: Index = { arbitrum: '0x0000002a', - sepolia: '0x0000017c', + sepolia: '0x00000182', } export const JOBS_TABLE_ADDRESSES: Index = { diff --git a/ui/pages/_app.tsx b/ui/pages/_app.tsx index 5226dc7d..8ef7a2a7 100644 --- a/ui/pages/_app.tsx +++ b/ui/pages/_app.tsx @@ -12,7 +12,7 @@ import '../styles/globals.css' function App({ Component, pageProps: { session, ...pageProps } }: any) { const [selectedChain, setSelectedChain]: any = useState( - process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : ArbitrumSepolia + process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia ) const [lightMode, setLightMode] = useLightMode() diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 54bd78b9..7d02ba73 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -1,5 +1,6 @@ import { Arbitrum, Sepolia, ArbitrumSepolia } from '@thirdweb-dev/chains' import CompetitorABI from 'const/abis/Competitor.json' +import DePrizeDistributionTableABI from 'const/abis/DePrizeDistribution.json' import { COMPETITOR_TABLE_ADDRESSES, DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, @@ -9,7 +10,10 @@ import { useRouter } from 'next/router' import { initSDK } from '@/lib/thirdweb/thirdweb' import { DePrize, DePrizeProps } from '../components/nance/DePrize' -export default function Rewards({ competitors, distributions }: DePrizeProps) { +export default function DePrizePage({ + competitors, + distributions, +}: DePrizeProps) { const router = useRouter() return ( Date: Fri, 22 Nov 2024 17:18:29 -0500 Subject: [PATCH 14/44] added new allocation with new contracts --- ui/components/nance/DePrize.tsx | 65 ++++++--------------------------- ui/const/config.ts | 3 +- ui/lib/utils/voting.ts | 12 ++++++ ui/package.json | 2 +- ui/pages/deprize.tsx | 2 +- 5 files changed, 26 insertions(+), 58 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 6fbdfda0..3545fbda 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -1,4 +1,4 @@ -import { Arbitrum, Sepolia, ArbitrumSepolia } from '@thirdweb-dev/chains' +import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import { useAddress, useContract } from '@thirdweb-dev/react' import CompetitorABI from 'const/abis/Competitor.json' import ERC20 from 'const/abis/ERC20.json' @@ -28,7 +28,6 @@ import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' import useIsOperator from '@/lib/revnet/hooks/useIsOperator' import useWindowSize from '@/lib/team/use-window-size' -import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' import useTokenSupply from '@/lib/tokens/hooks/useTokenSupply' import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance' import { getBudget, getPayouts } from '@/lib/utils/rewards' @@ -56,8 +55,6 @@ export type Competitor = { } export type Distribution = { deprize: number - year: number - quarter: number address: string distribution: { [key: string]: number } } @@ -90,8 +87,6 @@ export function DePrize({ if (distributions && userAddress) { for (const d of distributions) { if ( - d.year === year && - d.quarter === quarter && d.address.toLowerCase() === userAddress.toLowerCase() ) { setDistribution(d.distribution) @@ -146,12 +141,7 @@ export function DePrize({ addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 const router = useRouter() - // All competitors need at least one citizen distribution to do iterative normalization - const isCitizens = useCitizens(chain, addresses) - const citizenDistributions = distributions?.filter((_, i) => isCitizens[i]) - const nonCitizenDistributions = distributions?.filter( - (_, i) => !isCitizens[i] - ) + console.log('competitors') console.log(competitors) //const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => @@ -206,60 +196,24 @@ export function DePrize({ return } try { - if (edit) { - await distributionTableContract?.call('updateTableCol', [ - deprize, - quarter, - year, - JSON.stringify(distribution), - ]) - toast.success('Distribution edited successfully!', { - style: toastStyle, - }) - setTimeout(() => { - refreshRewards() - }, 5000) - } else { - await distributionTableContract?.call('insertIntoTable', [ - deprize, - quarter, - year, - JSON.stringify(distribution), - ]) - toast.success('Distribution submitted successfully!', { - style: toastStyle, - }) - setTimeout(() => { - refreshRewards() - }, 5000) - } - } catch (error) { - console.error('Error submitting distribution:', error) - toast.error('Error submitting distribution. Please try again.', { - style: toastStyle, - }) - } - } - const handleDelete = async () => { - try { - await distributionTableContract?.call('deleteFromTable', [ + await distributionTableContract?.call('insertIntoTable', [ deprize, - quarter, - year, + JSON.stringify(distribution), ]) - toast.success('Distribution deleted successfully!', { + toast.success('Distribution submitted successfully!', { style: toastStyle, }) setTimeout(() => { refreshRewards() }, 5000) } catch (error) { - console.error('Error deleting distribution:', error) - toast.error('Error deleting distribution. Please try again.', { + console.error('Error submitting distribution:', error) + toast.error('Error submitting distribution. Please try again.', { style: toastStyle, }) } } + const handleSend = async () => { try { const addresses = competitors.map((c) => c.treasury) @@ -471,6 +425,9 @@ export function DePrize({ ) } className="border rounded px-2 py-1 w-20" + style={{ + backgroundColor: 'var(--black)', + }} min="1" max="100" disabled={!userAddress || !userHasVotingPower} diff --git a/ui/const/config.ts b/ui/const/config.ts index 9c77ac9f..f1a0b6e1 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -117,8 +117,7 @@ export const SMART_WALLET_CHECKER_ADDRESSES: Index = { } export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { - sepolia: '0xd785f72027be7c3dcaDB06E69d7f18bb195fB296', - 'arbitrum-sepolia': '0xBE19a62384014F103686dfE6D9d50B1D3E81B2d0', + sepolia: '0x3d83B3DF12Fb5e11D447E8Dca4376C6ebc0352e6', } // TODO don't hard code, pull from d-prize contract export const PRIZE_TOKEN_ADDRESSES: Index = { diff --git a/ui/lib/utils/voting.ts b/ui/lib/utils/voting.ts index 8f09c191..a6f4d129 100644 --- a/ui/lib/utils/voting.ts +++ b/ui/lib/utils/voting.ts @@ -237,3 +237,15 @@ export function computeRewardPercentages( ] return runQuadraticVoting(allDistributions, addressToQuadraticVotingPower) } + + + +export function getCurrentVoterRewards( + + // fetchAllocationFromTableland() + // add bloctimestamp + + + + +) diff --git a/ui/package.json b/ui/package.json index c01fa8f9..5cbbf46a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -3,7 +3,7 @@ "private": true, "license": "MIT", "engines": { - "node": ">=18 <=20" + "node": ">=18" }, "scripts": { "dev": "next dev", diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 7d02ba73..83586a52 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -61,7 +61,7 @@ export async function getStaticProps() { const competitors = await competitorsRes.json() console.log('competitors', competitors) - const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId} AND year = ${currentYear} AND quarter = ${currentQuarter}` + const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId}` const distributionsRes = await fetch( `${TABLELAND_ENDPOINT}?statement=${distributionStatement}` ) From f25bebcc57735b8d1f44bced646b903ecbcc66a9 Mon Sep 17 00:00:00 2001 From: pmoncada Date: Tue, 26 Nov 2024 17:41:27 -0500 Subject: [PATCH 15/44] testing voter rewards --- ui/components/nance/DePrize.tsx | 81 ++++++++++++++++++++++++++++----- ui/const/config.ts | 2 +- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 3545fbda..d9a3a7fe 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -41,6 +41,8 @@ import Modal from '@/components/layout/Modal' import { NoticeFooter } from '@/components/layout/NoticeFooter' import { TeamPreview } from '@/components/subscription/TeamPreview' import StandardButton from '../layout/StandardButton' +import { useVotingPowers } from '@/lib/snapshot' +import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' export type Metadata = { social: string @@ -56,6 +58,7 @@ export type Competitor = { export type Distribution = { deprize: number address: string + timestamp: number distribution: { [key: string]: number } } @@ -85,6 +88,9 @@ export function DePrize({ // Check if the user already has a distribution for the current quarter useEffect(() => { if (distributions && userAddress) { + // // Calculate the current voter rewards for that user on each competitor. + // const voterRewards = getVoterRewards(distributions) + for (const d of distributions) { if ( d.address.toLowerCase() === userAddress.toLowerCase() @@ -96,6 +102,7 @@ export function DePrize({ } } }, [userAddress, distributions]) + const handleDistributionChange = (competitorId: string, value: number) => { setDistribution((prev) => ({ ...prev, @@ -103,6 +110,58 @@ export function DePrize({ })) } + const getVoterRewards = (distributions: Distribution[]) => { + if (distributions.length === 0) { + return {} + } + + // Populate a map of the latest allocations: From user to distribution (NOTE: assumes *voting power* - not percentage). + let userToDistributions : { [ key: string]: { [key: string]: number }} = {} + + // Voter rewards given a certain competitor ID, mapping from user to percentage of rewards. + let competitorToVoterRewardPercentages : { [ key: string]: { [key: string]: number }} = {} + + let previousTimestamp = distributions[0].timestamp + let elapsedTime = 0 + + for (let i = 0; i < distributions.length; i++) { + const d = distributions[i] + // Calculate the delta between the current timestamp and the distribution timestamp. + const delta = previousTimestamp - d.timestamp + + // Iterate through all the competitors and calculate the voter reward percentages. + for (const competitor of competitors) { + // const newAllocationToCompetitor = d.distribution[competitor.id] * d.votingPower + let totalVotingPowerToCompetitor = 0 + + // Get the total voting power allocation to the competitor, from all users. + for (const [, distribution] of Object.entries(userToDistributions)) { + totalVotingPowerToCompetitor += distribution[competitor.id] + } + + // For every user, calculate the percentage they make up of the total voting power to the competitor. + for (const [userID, distribution] of Object.entries(userToDistributions)) { + const percentageInTimeWindow = distribution[competitor.id] / totalVotingPowerToCompetitor + const previousPercentage = competitorToVoterRewardPercentages[competitor.id][userID] + + // Update the voter reward percentage, using a time-weighted average. + competitorToVoterRewardPercentages[competitor.id][userID] = + (previousPercentage * elapsedTime + percentageInTimeWindow * delta) / + (elapsedTime + delta) + } + } + + // Update the allUserDistributions map with the new distribution. + userToDistributions[d.address] = d.distribution + + // Update the elapsed time and previous timestamp. + elapsedTime += delta + previousTimestamp = d.timestamp + } + + return competitorToVoterRewardPercentages + } + const addresses = distributions ? distributions.map((d) => d.address) : [] const { contract: prizeContract } = useContract( @@ -122,23 +181,21 @@ export function DePrize({ ) const isOperator = useIsOperator(revnetContract, userAddress, PRIZE_REVNET_ID) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) - //const prizeBalance = 0 - const tokenBalances = [] - //const tokenBalances = useTokenBalances( - //prizeContract, - //PRIZE_DECIMALS, - //addresses - //) + const tokenBalances = useTokenBalances( + prizeContract, + PRIZE_DECIMALS, + addresses + ) const addressToQuadraticVotingPower = Object.fromEntries( addresses.map((address, i) => [address, Math.sqrt(tokenBalances[i])]) ) const votingPowerSumIsNonZero = _.sum(Object.values(addressToQuadraticVotingPower)) > 0 - const userHasVotingPower = - userAddress && - (userAddress.toLowerCase() in addressToQuadraticVotingPower || - userAddress in addressToQuadraticVotingPower) && - addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 + const userHasVotingPower = true + // userAddress && + // (userAddress.toLowerCase() in addressToQuadraticVotingPower || + // userAddress in addressToQuadraticVotingPower) && + // addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 const router = useRouter() diff --git a/ui/const/config.ts b/ui/const/config.ts index f1a0b6e1..b4a9d2e9 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -117,7 +117,7 @@ export const SMART_WALLET_CHECKER_ADDRESSES: Index = { } export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { - sepolia: '0x3d83B3DF12Fb5e11D447E8Dca4376C6ebc0352e6', + sepolia: '0xdd8144346390194EBa35F9551a13283a9143f21F', } // TODO don't hard code, pull from d-prize contract export const PRIZE_TOKEN_ADDRESSES: Index = { From 6534b25310e1d6b77b3345b26ed09a7deb377400 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 26 Nov 2024 14:42:24 -0800 Subject: [PATCH 16/44] wip --- ui/components/nance/CompetitorPreview.tsx | 43 ++++++++++++++ ui/components/nance/DePrize.tsx | 67 +++++++++++----------- ui/components/subscription/TeamPreview.tsx | 7 +-- ui/const/abis/Competitor.json | 2 +- ui/const/config.ts | 3 +- ui/pages/deprize.tsx | 16 ++---- 6 files changed, 83 insertions(+), 55 deletions(-) create mode 100644 ui/components/nance/CompetitorPreview.tsx diff --git a/ui/components/nance/CompetitorPreview.tsx b/ui/components/nance/CompetitorPreview.tsx new file mode 100644 index 00000000..af756479 --- /dev/null +++ b/ui/components/nance/CompetitorPreview.tsx @@ -0,0 +1,43 @@ +import { NFT, ThirdwebNftMedia } from '@thirdweb-dev/react' +import { useEffect, useState } from 'react' + +type CompetitorPreviewProps = { + teamId: any + teamContract?: any +} + +export function CompetitorPreview({ + teamId, + teamContract, +}: CompetitorPreviewProps) { + const [teamNFT, setTeamNFT] = useState() + + useEffect(() => { + async function getTeamNFT() { + const nft = await teamContract.erc721.get(teamId) + setTeamNFT(nft) + } + + if (teamContract?.erc721?.get && teamId) { + getTeamNFT() + } + }, [teamId, teamContract]) + console.log('teamNFT') + console.log(teamNFT) + + return ( +
+ {teamNFT && ( +
+ +
{teamNFT.metadata.name}
+
+ )} +
+ ) +} diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 6fbdfda0..0e9b3863 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -1,3 +1,4 @@ +import { XMarkIcon } from '@heroicons/react/24/outline' import { Arbitrum, Sepolia, ArbitrumSepolia } from '@thirdweb-dev/chains' import { useAddress, useContract } from '@thirdweb-dev/react' import CompetitorABI from 'const/abis/Competitor.json' @@ -40,6 +41,7 @@ import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' import Modal from '@/components/layout/Modal' import { NoticeFooter } from '@/components/layout/NoticeFooter' +import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { TeamPreview } from '@/components/subscription/TeamPreview' import StandardButton from '../layout/StandardButton' @@ -127,7 +129,6 @@ export function DePrize({ ) const isOperator = useIsOperator(revnetContract, userAddress, PRIZE_REVNET_ID) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) - //const prizeBalance = 0 const tokenBalances = [] //const tokenBalances = useTokenBalances( //prizeContract, @@ -140,10 +141,11 @@ export function DePrize({ const votingPowerSumIsNonZero = _.sum(Object.values(addressToQuadraticVotingPower)) > 0 const userHasVotingPower = - userAddress && - (userAddress.toLowerCase() in addressToQuadraticVotingPower || - userAddress in addressToQuadraticVotingPower) && - addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0 + prizeBalance > 0 || + (userAddress && + (userAddress.toLowerCase() in addressToQuadraticVotingPower || + userAddress in addressToQuadraticVotingPower) && + addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0) const router = useRouter() // All competitors need at least one citizen distribution to do iterative normalization @@ -152,8 +154,6 @@ export function DePrize({ const nonCitizenDistributions = distributions?.filter( (_, i) => !isCitizens[i] ) - console.log('competitors') - console.log(competitors) //const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => //citizenDistributions.some(({ distribution }) => id in distribution) //) @@ -266,12 +266,6 @@ export function DePrize({ const amounts = competitors.map( (c) => competitorIdToPrizePayout[c.id] * 10 ** PRIZE_DECIMALS ) - console.log('amounts') - console.log(amounts) - console.log('addresses') - console.log(addresses) - console.log('PRIZE_TOKEN_ADDRESSES[chain.slug]') - console.log(PRIZE_TOKEN_ADDRESSES[chain.slug]) // approve bulk token sender //await prizeContract?.call('approve', [ //BULK_TOKEN_SENDER_ADDRESSES[chain.slug], @@ -302,14 +296,11 @@ export function DePrize({ const { contract: hatsContract } = useContract(HATS_ADDRESS) const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) const userTeams = useTeamWearer(teamContract, chain, userAddress) - console.log('userTeams') - console.log(userTeams) const handleJoinWithTeam = async (teamId: string) => { try { await competitorContract?.call('insertIntoTable', [ - teamId, DEPRIZE_ID, - userAddress, + teamId, '{}', ]) toast.success('Joined as a competitor!', { @@ -330,10 +321,22 @@ export function DePrize({ const JoinModal = () => ( setJoinModalOpen(false)} + // close if clicked outside of modal + onClick={(e) => { + if (e.target.id === 'modal-backdrop') setJoinModalOpen(false) + }} title="Join DePrize Competition" > +
-

Select a Team or Create New One

+

Select a Team or Create a New One

{/* Existing Teams */} {userTeams && userTeams.length > 0 && ( @@ -343,16 +346,15 @@ export function DePrize({ {userTeams.map((team: any) => ( <> - ))}
@@ -479,15 +481,10 @@ export function DePrize({     
- - Competitor {competitor.id}: - - {competitor.name} +
{readyToRunVoting && tokens && tokens[0] && ( <> @@ -538,7 +535,7 @@ export function DePrize({ ) : ( Get Voting Power diff --git a/ui/components/subscription/TeamPreview.tsx b/ui/components/subscription/TeamPreview.tsx index d3336185..2ab8902f 100644 --- a/ui/components/subscription/TeamPreview.tsx +++ b/ui/components/subscription/TeamPreview.tsx @@ -1,7 +1,4 @@ -import { ArrowUpRightIcon } from '@heroicons/react/24/outline' -import { Chain } from '@thirdweb-dev/chains' import { NFT, ThirdwebNftMedia } from '@thirdweb-dev/react' -import { useRouter } from 'next/router' import { useEffect, useState } from 'react' type TeamPreviewProps = { @@ -29,8 +26,8 @@ export function TeamPreview({ teamId, teamContract }: TeamPreviewProps) {
)} diff --git a/ui/const/abis/Competitor.json b/ui/const/abis/Competitor.json index a8ee1306..79746098 100644 --- a/ui/const/abis/Competitor.json +++ b/ui/const/abis/Competitor.json @@ -1 +1 @@ -[{"type":"constructor","inputs":[{"name":"_table_prefix","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"type":"function","name":"currId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getPolicy","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getPolicy","inputs":[{"name":"caller","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getTableId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTableName","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"insertIntoTable","inputs":[{"name":"name","type":"string","internalType":"string"},{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"treasury","type":"string","internalType":"string"},{"name":"metadata","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setAccessControl","inputs":[{"name":"controller","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"ChainNotSupported","inputs":[{"name":"chainid","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]}] +[{"type":"constructor","inputs":[{"name":"_table_prefix","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"type":"function","name":"currId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getPolicy","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getPolicy","inputs":[{"name":"caller","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getTableId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTableName","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"insertIntoTable","inputs":[{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"teamId","type":"uint256","internalType":"uint256"},{"name":"metadata","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setAccessControl","inputs":[{"name":"controller","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"ChainNotSupported","inputs":[{"name":"chainid","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]}] diff --git a/ui/const/config.ts b/ui/const/config.ts index 9c77ac9f..7585c66d 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -100,8 +100,7 @@ export const PROJECT_TABLE_ADDRESSES: Index = { 'arbitrum-sepolia': '0xF0A3DB6161D1Ee7B99197CDeD4EdFc462EAE80e0', } export const COMPETITOR_TABLE_ADDRESSES: Index = { - sepolia: '0x55d76e41B94fe40e8F25eC37574B1AeDE0a6E909', - 'arbitrum-sepolia': '0xEc9Eb5D1474692c0860583e76D3611c5C8F41b7B', + sepolia: '0x9057Fff69e8b016a214C4f894430F71dad50b42c', } export const DISTRIBUTION_TABLE_ADDRESSES: Index = { arbitrum: '0xabD8D3693439A72393220d87aee159952261Ad1f', diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 7d02ba73..78524d96 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -25,10 +25,7 @@ export default function DePrizePage({ } export async function getStaticProps() { - // TODO uncomment const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia - //const chain = ArbitrumSepolia - ////const chain = ArbitrumSepolia const sdk = initSDK(chain) const competitorTableContract = await sdk.getContract( @@ -41,14 +38,10 @@ export async function getStaticProps() { DePrizeDistributionTableABI ) - const competitorBoardTableName = await competitorTableContract.call( - 'getTableName' - ) - //console.log('competitorBoardTableName') - //console.log(competitorBoardTableName) - const distributionTableName = await distributionTableContract.call( - 'getTableName' - ) + const competitorBoardTableName = + await competitorTableContract.call('getTableName') + const distributionTableName = + await distributionTableContract.call('getTableName') // TODO don't hardcode const dePrizeId = 1 @@ -59,7 +52,6 @@ export async function getStaticProps() { `${TABLELAND_ENDPOINT}?statement=${competitorStatement}` ) const competitors = await competitorsRes.json() - console.log('competitors', competitors) const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId} AND year = ${currentYear} AND quarter = ${currentQuarter}` const distributionsRes = await fetch( From 1a095e72fc0716d4405633ff414679db5d8795bb Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 26 Nov 2024 15:02:13 -0800 Subject: [PATCH 17/44] fix cursed hook --- ui/lib/tokens/hooks/useTokenBalances.tsx | 34 +++++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/ui/lib/tokens/hooks/useTokenBalances.tsx b/ui/lib/tokens/hooks/useTokenBalances.tsx index f6216b80..24101f9d 100644 --- a/ui/lib/tokens/hooks/useTokenBalances.tsx +++ b/ui/lib/tokens/hooks/useTokenBalances.tsx @@ -5,21 +5,35 @@ export default function useTokenBalances( decimals: number, addresses: string[] ) { - const [tokenBalances, setTokenBalances] = useState([]) + const [tokenBalances, setTokenBalances] = useState([]) useEffect(() => { async function getBalances() { - if (!tokenContract) return - const balances = await Promise.all( - addresses.map(async (address) => { - const balance = await tokenContract.call('balanceOf', [address]) - return +balance.toString() / 10 ** decimals - }) - ) - setTokenBalances(balances) + if (!tokenContract || !addresses || addresses.length === 0) return + + try { + const balances = await Promise.all( + addresses.map(async (address) => { + try { + const balance = await tokenContract.balanceOf(address) // Adjust this for your library + return +balance.toString() / 10 ** decimals + } catch (error) { + console.error( + `Failed to fetch balance for address ${address}:`, + error + ) + return 0 + } + }) + ) + setTokenBalances(balances) + } catch (error) { + console.error('Error fetching balances:', error) + } } getBalances() - }, [tokenContract, addresses]) + }, [tokenContract, addresses, decimals]) + return tokenBalances } From 9e5f6eb646819d555855dc4b823fc2c380558231 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 26 Nov 2024 15:34:10 -0800 Subject: [PATCH 18/44] wip --- contracts/curve.py | 671 ----------------------- ui/components/nance/DePrize.tsx | 145 ++--- ui/components/nance/JoinDePrizeModal.tsx | 75 +++ 3 files changed, 121 insertions(+), 770 deletions(-) delete mode 100644 contracts/curve.py create mode 100644 ui/components/nance/JoinDePrizeModal.tsx diff --git a/contracts/curve.py b/contracts/curve.py deleted file mode 100644 index d92c57af..00000000 --- a/contracts/curve.py +++ /dev/null @@ -1,671 +0,0 @@ -# @version 0.3.0 -""" -@title Voting Escrow -@author Curve Finance -@license MIT -@notice Votes have a weight depending on time, so that users are - committed to the future of (whatever they are voting for) -@dev Vote weight decays linearly over time. Lock time cannot be - more than `MAXTIME` (4 years). -""" - -# Voting escrow to have time-weighted votes -# Votes have a weight depending on time, so that users are committed -# to the future of (whatever they are voting for). -# The weight in this implementation is linear, and lock cannot be more than maxtime: -# w ^ -# 1 + / -# | / -# | / -# | / -# |/ -# 0 +--------+------> time -# maxtime (4 years?) - -struct Point: - bias: int128 - slope: int128 # - dweight / dt - ts: uint256 - blk: uint256 # block -# We cannot really do block numbers per se b/c slope is per time, not per block -# and per block could be fairly bad b/c Ethereum changes blocktimes. -# What we can do is to extrapolate ***At functions - -struct LockedBalance: - amount: int128 - end: uint256 - - -interface ERC20: - def decimals() -> uint256: view - def name() -> String[64]: view - def symbol() -> String[32]: view - def transfer(to: address, amount: uint256) -> bool: nonpayable - def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable - - -# Interface for checking whether address belongs to a whitelisted -# type of a smart wallet. -# When new types are added - the whole contract is changed -# The check() method is modifying to be able to use caching -# for individual wallet addresses -interface SmartWalletChecker: - def check(addr: address) -> bool: nonpayable - -DEPOSIT_FOR_TYPE: constant(int128) = 0 -CREATE_LOCK_TYPE: constant(int128) = 1 -INCREASE_LOCK_AMOUNT: constant(int128) = 2 -INCREASE_UNLOCK_TIME: constant(int128) = 3 - - -event CommitOwnership: - admin: address - -event ApplyOwnership: - admin: address - -event Deposit: - provider: indexed(address) - value: uint256 - locktime: indexed(uint256) - type: int128 - ts: uint256 - -event Withdraw: - provider: indexed(address) - value: uint256 - ts: uint256 - -event Supply: - prevSupply: uint256 - supply: uint256 - - -WEEK: constant(uint256) = 7 * 86400 # all future times are rounded by week -MAXTIME: constant(uint256) = 4 * 365 * 86400 # 4 years -MULTIPLIER: constant(uint256) = 10 ** 18 - -token: public(address) -supply: public(uint256) - -locked: public(HashMap[address, LockedBalance]) - -epoch: public(uint256) -point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point -user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] -user_point_epoch: public(HashMap[address, uint256]) -slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change - -# Aragon's view methods for compatibility -controller: public(address) -transfersEnabled: public(bool) - -name: public(String[64]) -symbol: public(String[32]) -version: public(String[32]) -decimals: public(uint256) - -# Checker for whitelisted (smart contract) wallets which are allowed to deposit -# The goal is to prevent tokenizing the escrow -future_smart_wallet_checker: public(address) -smart_wallet_checker: public(address) - -admin: public(address) # Can and will be a smart contract -future_admin: public(address) - - -@external -def __init__(token_addr: address, _name: String[64], _symbol: String[32], _version: String[32]): - """ - @notice Contract constructor - @param token_addr `ERC20CRV` token address - @param _name Token name - @param _symbol Token symbol - @param _version Contract version - required for Aragon compatibility - """ - self.admin = msg.sender - self.token = token_addr - self.point_history[0].blk = block.number - self.point_history[0].ts = block.timestamp - self.controller = msg.sender - self.transfersEnabled = True - - _decimals: uint256 = ERC20(token_addr).decimals() - assert _decimals <= 255 - self.decimals = _decimals - - self.name = _name - self.symbol = _symbol - self.version = _version - - -@external -def commit_transfer_ownership(addr: address): - """ - @notice Transfer ownership of VotingEscrow contract to `addr` - @param addr Address to have ownership transferred to - """ - assert msg.sender == self.admin # dev: admin only - self.future_admin = addr - log CommitOwnership(addr) - - -@external -def apply_transfer_ownership(): - """ - @notice Apply ownership transfer - """ - assert msg.sender == self.admin # dev: admin only - _admin: address = self.future_admin - assert _admin != ZERO_ADDRESS # dev: admin not set - self.admin = _admin - log ApplyOwnership(_admin) - - -@external -def commit_smart_wallet_checker(addr: address): - """ - @notice Set an external contract to check for approved smart contract wallets - @param addr Address of Smart contract checker - """ - assert msg.sender == self.admin - self.future_smart_wallet_checker = addr - - -@external -def apply_smart_wallet_checker(): - """ - @notice Apply setting external contract to check approved smart contract wallets - """ - assert msg.sender == self.admin - self.smart_wallet_checker = self.future_smart_wallet_checker - - -@internal -def assert_not_contract(addr: address): - """ - @notice Check if the call is from a whitelisted smart contract, revert if not - @param addr Address to be checked - """ - if addr != tx.origin: - checker: address = self.smart_wallet_checker - if checker != ZERO_ADDRESS: - if SmartWalletChecker(checker).check(addr): - return - raise "Smart contract depositors not allowed" - - -@external -@view -def get_last_user_slope(addr: address) -> int128: - """ - @notice Get the most recently recorded rate of voting power decrease for `addr` - @param addr Address of the user wallet - @return Value of the slope - """ - uepoch: uint256 = self.user_point_epoch[addr] - return self.user_point_history[addr][uepoch].slope - - -@external -@view -def user_point_history__ts(_addr: address, _idx: uint256) -> uint256: - """ - @notice Get the timestamp for checkpoint `_idx` for `_addr` - @param _addr User wallet address - @param _idx User epoch number - @return Epoch time of the checkpoint - """ - return self.user_point_history[_addr][_idx].ts - - -@external -@view -def locked__end(_addr: address) -> uint256: - """ - @notice Get timestamp when `_addr`'s lock finishes - @param _addr User wallet - @return Epoch time of the lock end - """ - return self.locked[_addr].end - - -@internal -def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance): - """ - @notice Record global and per-user data to checkpoint - @param addr User's wallet address. No user checkpoint if 0x0 - @param old_locked Pevious locked amount / end lock time for the user - @param new_locked New locked amount / end lock time for the user - """ - u_old: Point = empty(Point) - u_new: Point = empty(Point) - old_dslope: int128 = 0 - new_dslope: int128 = 0 - _epoch: uint256 = self.epoch - - if addr != ZERO_ADDRESS: - # Calculate slopes and biases - # Kept at zero when they have to - if old_locked.end > block.timestamp and old_locked.amount > 0: - u_old.slope = old_locked.amount / MAXTIME - u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128) - if new_locked.end > block.timestamp and new_locked.amount > 0: - u_new.slope = new_locked.amount / MAXTIME - u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128) - - # Read values of scheduled changes in the slope - # old_locked.end can be in the past and in the future - # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros - old_dslope = self.slope_changes[old_locked.end] - if new_locked.end != 0: - if new_locked.end == old_locked.end: - new_dslope = old_dslope - else: - new_dslope = self.slope_changes[new_locked.end] - - last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number}) - if _epoch > 0: - last_point = self.point_history[_epoch] - last_checkpoint: uint256 = last_point.ts - # initial_last_point is used for extrapolation to calculate block number - # (approximately, for *At methods) and save them - # as we cannot figure that out exactly from inside the contract - initial_last_point: Point = last_point - block_slope: uint256 = 0 # dblock/dt - if block.timestamp > last_point.ts: - block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts) - # If last point is already recorded in this block, slope=0 - # But that's ok b/c we know the block in such case - - # Go over weeks to fill history and calculate what the current point is - t_i: uint256 = (last_checkpoint / WEEK) * WEEK - for i in range(255): - # Hopefully it won't happen that this won't get used in 5 years! - # If it does, users will be able to withdraw but vote weight will be broken - t_i += WEEK - d_slope: int128 = 0 - if t_i > block.timestamp: - t_i = block.timestamp - else: - d_slope = self.slope_changes[t_i] - last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128) - last_point.slope += d_slope - if last_point.bias < 0: # This can happen - last_point.bias = 0 - if last_point.slope < 0: # This cannot happen - just in case - last_point.slope = 0 - last_checkpoint = t_i - last_point.ts = t_i - last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER - _epoch += 1 - if t_i == block.timestamp: - last_point.blk = block.number - break - else: - self.point_history[_epoch] = last_point - - self.epoch = _epoch - # Now point_history is filled until t=now - - if addr != ZERO_ADDRESS: - # If last point was in this block, the slope change has been applied already - # But in such case we have 0 slope(s) - last_point.slope += (u_new.slope - u_old.slope) - last_point.bias += (u_new.bias - u_old.bias) - if last_point.slope < 0: - last_point.slope = 0 - if last_point.bias < 0: - last_point.bias = 0 - - # Record the changed point into history - self.point_history[_epoch] = last_point - - if addr != ZERO_ADDRESS: - # Schedule the slope changes (slope is going down) - # We subtract new_user_slope from [new_locked.end] - # and add old_user_slope to [old_locked.end] - if old_locked.end > block.timestamp: - # old_dslope was - u_old.slope, so we cancel that - old_dslope += u_old.slope - if new_locked.end == old_locked.end: - old_dslope -= u_new.slope # It was a new deposit, not extension - self.slope_changes[old_locked.end] = old_dslope - - if new_locked.end > block.timestamp: - if new_locked.end > old_locked.end: - new_dslope -= u_new.slope # old slope disappeared at this point - self.slope_changes[new_locked.end] = new_dslope - # else: we recorded it already in old_dslope - - # Now handle user history - user_epoch: uint256 = self.user_point_epoch[addr] + 1 - - self.user_point_epoch[addr] = user_epoch - u_new.ts = block.timestamp - u_new.blk = block.number - self.user_point_history[addr][user_epoch] = u_new - - -@internal -def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128): - """ - @notice Deposit and lock tokens for a user - @param _addr User's wallet address - @param _value Amount to deposit - @param unlock_time New time when to unlock the tokens, or 0 if unchanged - @param locked_balance Previous locked amount / timestamp - """ - _locked: LockedBalance = locked_balance - supply_before: uint256 = self.supply - - self.supply = supply_before + _value - old_locked: LockedBalance = _locked - # Adding to existing lock, or if a lock is expired - creating a new one - _locked.amount += convert(_value, int128) - if unlock_time != 0: - _locked.end = unlock_time - self.locked[_addr] = _locked - - # Possibilities: - # Both old_locked.end could be current or expired (>/< block.timestamp) - # value == 0 (extend lock) or value > 0 (add to lock or extend lock) - # _locked.end > block.timestamp (always) - self._checkpoint(_addr, old_locked, _locked) - - if _value != 0: - assert ERC20(self.token).transferFrom(_addr, self, _value) - - log Deposit(_addr, _value, _locked.end, type, block.timestamp) - log Supply(supply_before, supply_before + _value) - - -@external -def checkpoint(): - """ - @notice Record global data to checkpoint - """ - self._checkpoint(ZERO_ADDRESS, empty(LockedBalance), empty(LockedBalance)) - - -@external -@nonreentrant('lock') -def deposit_for(_addr: address, _value: uint256): - """ - @notice Deposit `_value` tokens for `_addr` and add to the lock - @dev Anyone (even a smart contract) can deposit for someone else, but - cannot extend their locktime and deposit for a brand new user - @param _addr User's wallet address - @param _value Amount to add to user's lock - """ - _locked: LockedBalance = self.locked[_addr] - - assert _value > 0 # dev: need non-zero value - assert _locked.amount > 0, "No existing lock found" - assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" - - self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE) - - -@external -@nonreentrant('lock') -def create_lock(_value: uint256, _unlock_time: uint256): - """ - @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time` - @param _value Amount to deposit - @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks - """ - self.assert_not_contract(msg.sender) - unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks - _locked: LockedBalance = self.locked[msg.sender] - - assert _value > 0 # dev: need non-zero value - assert _locked.amount == 0, "Withdraw old tokens first" - assert unlock_time > block.timestamp, "Can only lock until time in the future" - assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max" - - self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE) - - -@external -@nonreentrant('lock') -def increase_amount(_value: uint256): - """ - @notice Deposit `_value` additional tokens for `msg.sender` - without modifying the unlock time - @param _value Amount of tokens to deposit and add to the lock - """ - self.assert_not_contract(msg.sender) - _locked: LockedBalance = self.locked[msg.sender] - - assert _value > 0 # dev: need non-zero value - assert _locked.amount > 0, "No existing lock found" - assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw" - - self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT) - - -@external -@nonreentrant('lock') -def increase_unlock_time(_unlock_time: uint256): - """ - @notice Extend the unlock time for `msg.sender` to `_unlock_time` - @param _unlock_time New epoch time for unlocking - """ - self.assert_not_contract(msg.sender) - _locked: LockedBalance = self.locked[msg.sender] - unlock_time: uint256 = (_unlock_time / WEEK) * WEEK # Locktime is rounded down to weeks - - assert _locked.end > block.timestamp, "Lock expired" - assert _locked.amount > 0, "Nothing is locked" - assert unlock_time > _locked.end, "Can only increase lock duration" - assert unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max" - - self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME) - - -@external -@nonreentrant('lock') -def withdraw(): - """ - @notice Withdraw all tokens for `msg.sender` - @dev Only possible if the lock has expired - """ - _locked: LockedBalance = self.locked[msg.sender] - assert block.timestamp >= _locked.end, "The lock didn't expire" - value: uint256 = convert(_locked.amount, uint256) - - old_locked: LockedBalance = _locked - _locked.end = 0 - _locked.amount = 0 - self.locked[msg.sender] = _locked - supply_before: uint256 = self.supply - self.supply = supply_before - value - - # old_locked can have either expired <= timestamp or zero end - # _locked has only 0 end - # Both can have >= 0 amount - self._checkpoint(msg.sender, old_locked, _locked) - - assert ERC20(self.token).transfer(msg.sender, value) - - log Withdraw(msg.sender, value, block.timestamp) - log Supply(supply_before, supply_before - value) - - -# The following ERC20/minime-compatible methods are not real balanceOf and supply! -# They measure the weights for the purpose of voting, so they don't represent -# real coins. - -@internal -@view -def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256: - """ - @notice Binary search to estimate timestamp for block number - @param _block Block to find - @param max_epoch Don't go beyond this epoch - @return Approximate timestamp for block - """ - # Binary search - _min: uint256 = 0 - _max: uint256 = max_epoch - for i in range(128): # Will be always enough for 128-bit numbers - if _min >= _max: - break - _mid: uint256 = (_min + _max + 1) / 2 - if self.point_history[_mid].blk <= _block: - _min = _mid - else: - _max = _mid - 1 - return _min - - -@external -@view -def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256: - """ - @notice Get the current voting power for `msg.sender` - @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility - @param addr User wallet address - @param _t Epoch time to return voting power at - @return User voting power - """ - _epoch: uint256 = self.user_point_epoch[addr] - if _epoch == 0: - return 0 - else: - last_point: Point = self.user_point_history[addr][_epoch] - last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128) - if last_point.bias < 0: - last_point.bias = 0 - return convert(last_point.bias, uint256) - - -@external -@view -def balanceOfAt(addr: address, _block: uint256) -> uint256: - """ - @notice Measure voting power of `addr` at block height `_block` - @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime - @param addr User's wallet address - @param _block Block to calculate the voting power at - @return Voting power - """ - # Copying and pasting totalSupply code because Vyper cannot pass by - # reference yet - assert _block <= block.number - - # Binary search - _min: uint256 = 0 - _max: uint256 = self.user_point_epoch[addr] - for i in range(128): # Will be always enough for 128-bit numbers - if _min >= _max: - break - _mid: uint256 = (_min + _max + 1) / 2 - if self.user_point_history[addr][_mid].blk <= _block: - _min = _mid - else: - _max = _mid - 1 - - upoint: Point = self.user_point_history[addr][_min] - - max_epoch: uint256 = self.epoch - _epoch: uint256 = self.find_block_epoch(_block, max_epoch) - point_0: Point = self.point_history[_epoch] - d_block: uint256 = 0 - d_t: uint256 = 0 - if _epoch < max_epoch: - point_1: Point = self.point_history[_epoch + 1] - d_block = point_1.blk - point_0.blk - d_t = point_1.ts - point_0.ts - else: - d_block = block.number - point_0.blk - d_t = block.timestamp - point_0.ts - block_time: uint256 = point_0.ts - if d_block != 0: - block_time += d_t * (_block - point_0.blk) / d_block - - upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128) - if upoint.bias >= 0: - return convert(upoint.bias, uint256) - else: - return 0 - - -@internal -@view -def supply_at(point: Point, t: uint256) -> uint256: - """ - @notice Calculate total voting power at some point in the past - @param point The point (bias/slope) to start search from - @param t Time to calculate the total voting power at - @return Total voting power at that time - """ - last_point: Point = point - t_i: uint256 = (last_point.ts / WEEK) * WEEK - for i in range(255): - t_i += WEEK - d_slope: int128 = 0 - if t_i > t: - t_i = t - else: - d_slope = self.slope_changes[t_i] - last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128) - if t_i == t: - break - last_point.slope += d_slope - last_point.ts = t_i - - if last_point.bias < 0: - last_point.bias = 0 - return convert(last_point.bias, uint256) - - -@external -@view -def totalSupply(t: uint256 = block.timestamp) -> uint256: - """ - @notice Calculate total voting power - @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility - @return Total voting power - """ - _epoch: uint256 = self.epoch - last_point: Point = self.point_history[_epoch] - return self.supply_at(last_point, t) - - -@external -@view -def totalSupplyAt(_block: uint256) -> uint256: - """ - @notice Calculate total voting power at some point in the past - @param _block Block to calculate the total voting power at - @return Total voting power at `_block` - """ - assert _block <= block.number - _epoch: uint256 = self.epoch - target_epoch: uint256 = self.find_block_epoch(_block, _epoch) - - point: Point = self.point_history[target_epoch] - dt: uint256 = 0 - if target_epoch < _epoch: - point_next: Point = self.point_history[target_epoch + 1] - if point.blk != point_next.blk: - dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk) - else: - if point.blk != block.number: - dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk) - # Now dt contains info on how far are we beyond point - - return self.supply_at(point, point.ts + dt) - - -# Dummy methods for compatibility with Aragon - -@external -def changeController(_newController: address): - """ - @dev Dummy method required for Aragon compatibility - """ - assert msg.sender == self.controller - self.controller = _newController diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 0a22fc93..077fe0d2 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -1,10 +1,10 @@ -import { XMarkIcon } from '@heroicons/react/24/outline' import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import { useAddress, useContract } from '@thirdweb-dev/react' import CompetitorABI from 'const/abis/Competitor.json' import ERC20 from 'const/abis/ERC20.json' import REVDeployer from 'const/abis/REVDeployer.json' import TeamABI from 'const/abis/Team.json' +import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, SNAPSHOT_RETROACTIVE_REWARDS_ID, @@ -19,7 +19,6 @@ import { TEAM_ADDRESSES } from 'const/config' import { HATS_ADDRESS } from 'const/config' import { BigNumber } from 'ethers' import _ from 'lodash' -import { useRouter } from 'next/router' import { useState, useEffect } from 'react' import toast from 'react-hot-toast' import { useCitizens } from '@/lib/citizen/useCitizen' @@ -38,9 +37,8 @@ import { Hat } from '@/components/hats/Hat' import Container from '@/components/layout/Container' import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' -import Modal from '@/components/layout/Modal' import { NoticeFooter } from '@/components/layout/NoticeFooter' -import { CompetitorPreview } from '@/components/nance/CompetitorPreview' +import { JoinDePrizeModal } from '@/components/nance/JoinDePrizeModal' import { TeamPreview } from '@/components/subscription/TeamPreview' import StandardButton from '../layout/StandardButton' import { useVotingPowers } from '@/lib/snapshot' @@ -51,10 +49,8 @@ export type Metadata = { } export type Competitor = { id: string - name: string deprize: number - title: string - treasury: string + teamId: number metadata: Metadata } export type Distribution = { @@ -78,6 +74,8 @@ export function DePrize({ const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const { isMobile } = useWindowSize() + console.log('distributions') + console.log(distributions) const userAddress = useAddress() const year = new Date().getFullYear() const quarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 @@ -94,9 +92,7 @@ export function DePrize({ // const voterRewards = getVoterRewards(distributions) for (const d of distributions) { - if ( - d.address.toLowerCase() === userAddress.toLowerCase() - ) { + if (d.address.toLowerCase() === userAddress.toLowerCase()) { setDistribution(d.distribution) setEdit(true) break @@ -118,10 +114,12 @@ export function DePrize({ } // Populate a map of the latest allocations: From user to distribution (NOTE: assumes *voting power* - not percentage). - let userToDistributions : { [ key: string]: { [key: string]: number }} = {} + let userToDistributions: { [key: string]: { [key: string]: number } } = {} // Voter rewards given a certain competitor ID, mapping from user to percentage of rewards. - let competitorToVoterRewardPercentages : { [ key: string]: { [key: string]: number }} = {} + let competitorToVoterRewardPercentages: { + [key: string]: { [key: string]: number } + } = {} let previousTimestamp = distributions[0].timestamp let elapsedTime = 0 @@ -142,13 +140,18 @@ export function DePrize({ } // For every user, calculate the percentage they make up of the total voting power to the competitor. - for (const [userID, distribution] of Object.entries(userToDistributions)) { - const percentageInTimeWindow = distribution[competitor.id] / totalVotingPowerToCompetitor - const previousPercentage = competitorToVoterRewardPercentages[competitor.id][userID] + for (const [userID, distribution] of Object.entries( + userToDistributions + )) { + const percentageInTimeWindow = + distribution[competitor.id] / totalVotingPowerToCompetitor + const previousPercentage = + competitorToVoterRewardPercentages[competitor.id][userID] // Update the voter reward percentage, using a time-weighted average. - competitorToVoterRewardPercentages[competitor.id][userID] = - (previousPercentage * elapsedTime + percentageInTimeWindow * delta) / + competitorToVoterRewardPercentages[competitor.id][userID] = + (previousPercentage * elapsedTime + + percentageInTimeWindow * delta) / (elapsedTime + delta) } } @@ -181,14 +184,20 @@ export function DePrize({ const { contract: bulkTokenSenderContract } = useContract( BULK_TOKEN_SENDER_ADDRESSES[chain.slug] ) + const { contract: distributionTableContract } = useContract( + DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug] + ) + const { contract: hatsContract } = useContract(HATS_ADDRESS) + const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) + const isOperator = useIsOperator(revnetContract, userAddress, PRIZE_REVNET_ID) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) - const tokenBalances = [] //const tokenBalances = useTokenBalances( //prizeContract, //PRIZE_DECIMALS, //addresses //) + const tokenBalances = [] const addressToQuadraticVotingPower = Object.fromEntries( addresses.map((address, i) => [address, Math.sqrt(tokenBalances[i])]) ) @@ -201,16 +210,6 @@ export function DePrize({ userAddress in addressToQuadraticVotingPower) && addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0) - const router = useRouter() - // All competitors need at least one citizen distribution to do iterative normalization - const isCitizens = useCitizens(chain, addresses) - const citizenDistributions = distributions?.filter((_, i) => isCitizens[i]) - const nonCitizenDistributions = distributions?.filter( - (_, i) => !isCitizens[i] - ) - //const allCompetitorsHaveCitizenDistribution = competitors.every(({ id }) => - //citizenDistributions.some(({ distribution }) => id in distribution) - //) const readyToRunVoting = votingPowerSumIsNonZero const budgetPercent = 100 @@ -221,21 +220,13 @@ export function DePrize({ budgetPercent ) - const { contract: distributionTableContract } = useContract( - DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug] - ) const { tokens } = useAssets() const { ethBudget, usdBudget, mooneyBudget, ethPrice } = getBudget( tokens, year, quarter ) - //const isCompetitor = competitors.some( - //(competitor) => competitor.treasury === userAddress - //) - const isCompetitor = false - //const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) - const prizeSupply = 0 + const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) const prizeBudget = prizeSupply * 0.1 const winnerPool = prizeSupply * 0.3 const prizePrice = 1 @@ -277,7 +268,7 @@ export function DePrize({ }) } } - + const handleSend = async () => { try { const addresses = competitors.map((c) => c.treasury) @@ -311,9 +302,16 @@ export function DePrize({ const [joinModalOpen, setJoinModalOpen] = useState(false) // Get user's teams - const { contract: hatsContract } = useContract(HATS_ADDRESS) - const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) const userTeams = useTeamWearer(teamContract, chain, userAddress) + + //const isCompetitor = userTeams.some((team) => + //competitors.some( + //(competitor) => competitor.teamId.toString() === team.teamId + //) + //) + const isCompetitor = false + console.log('isCompetitor') + console.log(isCompetitor) const handleJoinWithTeam = async (teamId: string) => { try { await competitorContract?.call('insertIntoTable', [ @@ -336,64 +334,6 @@ export function DePrize({ } } - const JoinModal = () => ( - setJoinModalOpen(false)} - // close if clicked outside of modal - onClick={(e) => { - if (e.target.id === 'modal-backdrop') setJoinModalOpen(false) - }} - title="Join DePrize Competition" - > - -
-

Select a Team or Create a New One

- - {/* Existing Teams */} - {userTeams && userTeams.length > 0 && ( -
-

Your Teams

-
- {userTeams.map((team: any) => ( - <> - - - ))} -
-
- )} - - {/* Create New Team */} -
- - Create New Team - -
-
-
- ) - return (
Join - {joinModalOpen && } + {joinModalOpen && ( + + )} )}
void + teamContract: any + handleJoinWithTeam: (teamId: string) => void +} + +export function JoinDePrizeModal({ + userTeams, + setJoinModalOpen, + teamContract, + handleJoinWithTeam, +}: JoinDePrizeModalProps) { + return ( + setJoinModalOpen(false)} + // close if clicked outside of modal + onClick={(e) => { + if (e.target.id === 'modal-backdrop') setJoinModalOpen(false) + }} + title="Join DePrize Competition" + > + +
+

Select a Team or Create a New One

+ + {/* Existing Teams */} + {userTeams && userTeams.length > 0 && ( +
+

Your Teams

+
+ {userTeams.map((team: any) => ( + <> + + + ))} +
+
+ )} + + {/* Create New Team */} +
+ + Create New Team + +
+
+
+ ) +} From e85f46b0b7dad0f92e2a3aef1ca23ee555ba1a7b Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 26 Nov 2024 15:37:33 -0800 Subject: [PATCH 19/44] wip --- .gitmodules | 3 -- README.md | 2 +- contracts/scripts/dev-deploy.ts | 60 ++++++++++++++------------------- contracts/scripts/helpers.ts | 19 ++++++----- 4 files changed, 36 insertions(+), 48 deletions(-) diff --git a/.gitmodules b/.gitmodules index d29bbd74..fc0f2d5c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "contracts/lib/solmate"] path = contracts/lib/solmate url = https://github.com/rari-capital/solmate -[submodule "contracts/lib/forge-std"] - path = contracts/lib/forge-std - url = https://github.com/foundry-rs/forge-std diff --git a/README.md b/README.md index 366e22a9..0e2c8a5d 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,4 @@ cp .env.testnet .env.local Start the development server: ``` yarn dev -``` +``` \ No newline at end of file diff --git a/contracts/scripts/dev-deploy.ts b/contracts/scripts/dev-deploy.ts index b3ef3ee6..971d79fb 100644 --- a/contracts/scripts/dev-deploy.ts +++ b/contracts/scripts/dev-deploy.ts @@ -1,33 +1,22 @@ import { wallet, dec, save } from "./helpers"; import { ethers, BigNumber, Contract } from "ethers"; -import { formatUnits } from "@ethersproject/units"; +import { formatUnits } from "@ethersproject/units" -import MyToken from "../out/MyToken.sol/MyToken.json"; -import VotingEscrow from "../out/VotingEscrow.vy/VotingEscrow.json"; +import MyToken from '../out/MyToken.sol/MyToken.json'; +import VotingEscrow from '../out/VotingEscrow.vy/VotingEscrow.json'; +import MerkleDistributorV2 from '../out/MerkleDistributorV2.sol/MerkleDistributorV2.json'; const getContractFactory = (artifact: any) => { return new ethers.ContractFactory(artifact.abi, artifact.bytecode.object, wallet); -}; - -const deployContract = async ({ - name, - deployer, - factory, - args, -}: { - name: string; - deployer: ethers.Wallet; - factory: ethers.ContractFactory; - args: Array; -}) => { - console.log(`Deploying ${name}..`); - const contract = await factory.connect(deployer).deploy(...args, { - gasLimit: BigNumber.from(8000000), - }); +} + +const deployContract = async ({ name, deployer, factory, args }: { name: string, deployer: ethers.Wallet, factory: ethers.ContractFactory, args: Array}) => { + console.log(`Deploying ${name}..`) + const contract = await factory.connect(deployer).deploy(...args); await contract.deployed(); - console.log(`Deployed ${name} to: ${contract.address}`); + console.log(`Deployed ${name} to: ${contract.address}`) return contract; -}; +} const deployMOONEY = async () => { const supply: BigNumber = BigNumber.from(process.env.MOONEY_SUPPLY ?? dec(42069, 18)); @@ -37,14 +26,14 @@ const deployMOONEY = async () => { name: "MOONEY", deployer: wallet, factory: Factory, - args: ["Mooney", "MOONEY"], + args: [] }); - //await MOONEYToken.connect(wallet).mint(wallet.address, supply); - console.log(`Minted ${formatUnits(supply, 18)} tokens to deployer address`); + await MOONEYToken.connect(wallet).mint(wallet.address, supply); + console.log(`Minted ${formatUnits(supply, 18)} tokens to deployer address`) return MOONEYToken; -}; +} const deployVMOONEY = async (MOONEYToken: Contract) => { const Factory = getContractFactory(VotingEscrow); @@ -53,11 +42,11 @@ const deployVMOONEY = async (MOONEYToken: Contract) => { name: "vMOONEY", deployer: wallet, factory: Factory, - args: [MOONEYToken.address, "Vote-escrowed MOONEY", "vMOONEY", "vMOONEY_1.0.0"], - }); + args: [MOONEYToken.address, "Vote-escrowed MOONEY", "vMOONEY", "vMOONEY_1.0.0"] + }) return vMOONEY; -}; +} // const deployAirdropDistributor = async (MOONEYToken: Contract, root: string) => { // const Factory = getContractFactory(MerkleDistributorV2); @@ -130,7 +119,7 @@ const deployVMOONEY = async (MOONEYToken: Contract) => { // factory: passportIssuerFactory, // args: [] // }) - + // await passportToken.connect(wallet).transferControl(passportIssuer.address); // // TODO: Set renderer @@ -150,15 +139,16 @@ const main = async () => { const vMOONEY = await deployVMOONEY(MOONEY); const deployment = { - MOONEYToken: MOONEY.address, - vMOONEYToken: vMOONEY.address, - }; + "MOONEYToken": MOONEY.address, + "vMOONEYToken": vMOONEY.address, + } const manifestFile = "./deployments/local.json"; save(deployment, manifestFile); - console.log(`Deployment manifest saved to ${manifestFile}`); -}; + console.log(`Deployment manifest saved to ${manifestFile}`) +} + main().catch((error) => { console.error(error); diff --git a/contracts/scripts/helpers.ts b/contracts/scripts/helpers.ts index 7cec616e..dbbe5c4e 100644 --- a/contracts/scripts/helpers.ts +++ b/contracts/scripts/helpers.ts @@ -1,7 +1,7 @@ import fs from "fs"; import { ethers } from "ethers"; import dotenv from "dotenv"; -dotenv.config(); +dotenv.config() let validConfig = true; if (process.env.RPC_URL === undefined) { @@ -14,18 +14,19 @@ if (process.env.DEPLOYER_PRIVATE_KEY === undefined) { } if (!validConfig) process.exit(1); -export const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); -export const wallet = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY ?? "", provider); +export const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL) +export const wallet = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY ?? "", provider) // Deployment helpers export const dec = (val: number, scale: number) => { const zerosCount = scale; const strVal = val.toString(); - const strZeros = "0".repeat(zerosCount); + const strZeros = ('0').repeat(zerosCount); return strVal.concat(strZeros); -}; + } + export const save = (info: object, path: string) => { const content = JSON.stringify(info, null, 1); @@ -36,7 +37,7 @@ export const save = (info: object, path: string) => { fs.mkdirSync(dir, { recursive: true }); } - return fs.writeFile(path, content, { encoding: "utf-8" }, (err) => { - if (err) console.log(err); - }); -}; + return fs.writeFile(path, content, { encoding: "utf-8"}, (err) => { if(err) console.log(err); }) +} + + From fc0036df026b8084f67a986f785e7c6d9587bd7d Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 26 Nov 2024 15:43:04 -0800 Subject: [PATCH 20/44] cleanup --- contracts/README.md | 5 +- contracts/foundry.toml | 10 +- contracts/scripts/VotingEscrowDepositor.s.sol | 29 -- contracts/src/governance/IVotingEscrow.sol | 12 - .../src/governance/SmartWalletChecker.sol | 63 ----- contracts/src/governance/VotingEscrow.vy | 5 +- .../src/governance/VotingEscrowDepositor.sol | 65 ----- contracts/src/sweepstakes/mooney_mumbai.sol | 35 +++ contracts/src/sweepstakes/tts2_mumbai.sol | 250 ++++++++++++++++++ contracts/src/test/MerkleDistributorV2.t.sol | 112 ++++++++ .../src/test/VotingEscrowDepositor.t.sol | 105 -------- .../src/test/utils/mocks/MockVotingEscrow.sol | 37 +++ ui/components/nance/DePrize.tsx | 1 - ui/components/onboarding/TeamTier.tsx | 17 +- ui/components/subscription/TeamPreview.tsx | 36 --- ui/lib/utils/hooks/useWithdrawAmount.ts | 23 -- 16 files changed, 446 insertions(+), 359 deletions(-) delete mode 100644 contracts/scripts/VotingEscrowDepositor.s.sol delete mode 100644 contracts/src/governance/SmartWalletChecker.sol delete mode 100644 contracts/src/governance/VotingEscrowDepositor.sol create mode 100644 contracts/src/sweepstakes/mooney_mumbai.sol create mode 100644 contracts/src/sweepstakes/tts2_mumbai.sol create mode 100644 contracts/src/test/MerkleDistributorV2.t.sol delete mode 100644 contracts/src/test/VotingEscrowDepositor.t.sol create mode 100644 contracts/src/test/utils/mocks/MockVotingEscrow.sol delete mode 100644 ui/components/subscription/TeamPreview.tsx delete mode 100644 ui/lib/utils/hooks/useWithdrawAmount.ts diff --git a/contracts/README.md b/contracts/README.md index 52d3dbbc..84426e88 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -33,10 +33,7 @@ yarn install cp .env.sample .env # Install Vyper -pip install vyper==0.3.1 - -# Add vyper to path -export PATH="$PATH:$HOME/Library/Python/3.9/bin" +pip install vyper==0.2.4 # Install Git submodules forge install diff --git a/contracts/foundry.toml b/contracts/foundry.toml index a5f4ed35..8d2dd106 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -1,12 +1,4 @@ -[profile.default] +[default] src = "src" out = "out" libs = ["lib"] -evm_version = "berlin" - -#[profile.default.compiler] -#version = "0.8.10" - -# Vyper compiler settings -[profile.default.vyper] -vyper_version = "0.3.1" diff --git a/contracts/scripts/VotingEscrowDepositor.s.sol b/contracts/scripts/VotingEscrowDepositor.s.sol deleted file mode 100644 index b11b2561..00000000 --- a/contracts/scripts/VotingEscrowDepositor.s.sol +++ /dev/null @@ -1,29 +0,0 @@ -pragma solidity ^0.8.0; - -import "forge-std/Script.sol"; -import "../src/governance/VotingEscrowDepositor.sol"; - -contract MyScript is Script { - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - uint256 networkChainId = block.chainid; - address MOONEY; - address VMOONEY; - // arbitrum - if (networkChainId == 42161) { - MOONEY = 0x1Fa56414549BdccBB09916f61f0A5827f779a85c; - VMOONEY = 0xB255c74F8576f18357cE6184DA033c6d93C71899; - } else if (networkChainId == 11155111) { - MOONEY = 0x85A3C597F43B0cCE657793Cf31b05DF6969FbD2C; - VMOONEY = 0xA4F6A4B135b9AF7909442A7a3bF7797b61e609b1; - } else { - revert("Unsupported network"); - } - - - VotingEscrowDepositor sender = new VotingEscrowDepositor(MOONEY, VMOONEY); - - vm.stopBroadcast(); - } -} diff --git a/contracts/src/governance/IVotingEscrow.sol b/contracts/src/governance/IVotingEscrow.sol index 46e1c795..d5877f21 100644 --- a/contracts/src/governance/IVotingEscrow.sol +++ b/contracts/src/governance/IVotingEscrow.sol @@ -10,16 +10,4 @@ interface IVotingEscrow { function locked(address) external view returns (LockedBalance memory); function balanceOf(address) external view returns (uint256); - function deploy_for(address _addr, uint256 _value) external; - function commit_smart_wallet_checker(address) external; - function apply_smart_wallet_checker() external; - - function admin() external view returns (address); - function create_lock(uint256 _value, uint256 _unlock_time) external; - function totalSupply() external view returns (uint256); - function checkpoint() external; - function user_point_history(address, uint256) external view returns (uint256, uint256); - function user_point_history__ts(address, uint256) external view returns (uint256); - function get_last_user_slope(address) external view returns (int128); - } diff --git a/contracts/src/governance/SmartWalletChecker.sol b/contracts/src/governance/SmartWalletChecker.sol deleted file mode 100644 index 752ec7a5..00000000 --- a/contracts/src/governance/SmartWalletChecker.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -//import "@openzeppelin/contracts-4.2.0/access/Ownable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract SmartWalletChecker is Ownable { - bool public isWhitelistEnabled; - mapping(address => bool) public wallets; - address public checker; - address public future_checker; - - event ApproveWallet(address); - event RevokeWallet(address); - event WhitelistEnabled(bool); - - constructor(bool _isWhitelistEnabled) public Ownable() { - // Set state variables - setIsWhitelistEnabled(_isWhitelistEnabled); - } - - function commitSetChecker(address _checker) external onlyOwner { - future_checker = _checker; - } - - function applySetChecker() external onlyOwner { - checker = future_checker; - } - - function approveWallet(address _wallet) external onlyOwner { - wallets[_wallet] = true; - - emit ApproveWallet(_wallet); - } - - function revokeWallet(address _wallet) external onlyOwner { - wallets[_wallet] = false; - - emit RevokeWallet(_wallet); - } - - function setIsWhitelistEnabled(bool _isWhitelistEnabled) public onlyOwner { - isWhitelistEnabled = _isWhitelistEnabled; - - emit WhitelistEnabled(_isWhitelistEnabled); - } - - function check(address _wallet) external view returns (bool) { - if (!isWhitelistEnabled) { - return true; - } - - bool _check = wallets[_wallet]; - if (_check) { - return _check; - } else { - if (checker != address(0)) { - return SmartWalletChecker(checker).check(_wallet); - } - } - return false; - } -} diff --git a/contracts/src/governance/VotingEscrow.vy b/contracts/src/governance/VotingEscrow.vy index 66fd976f..bd09ec9b 100644 --- a/contracts/src/governance/VotingEscrow.vy +++ b/contracts/src/governance/VotingEscrow.vy @@ -1,4 +1,4 @@ -# @version 0.3.1 +# @version 0.3.0 """ @title Voting Escrow @author Curve Finance @@ -94,7 +94,6 @@ epoch: public(uint256) point_history: public(Point[100000000000000000000000000000]) # epoch -> unsigned point user_point_history: public(HashMap[address, Point[1000000000]]) # user -> Point[user_epoch] user_point_epoch: public(HashMap[address, uint256]) - slope_changes: public(HashMap[uint256, int128]) # time -> signed slope change # Aragon's view methods for compatibility @@ -669,4 +668,4 @@ def changeController(_newController: address): @dev Dummy method required for Aragon compatibility """ assert msg.sender == self.controller - self.controller = _newController + self.controller = _newController \ No newline at end of file diff --git a/contracts/src/governance/VotingEscrowDepositor.sol b/contracts/src/governance/VotingEscrowDepositor.sol deleted file mode 100644 index 04930848..00000000 --- a/contracts/src/governance/VotingEscrowDepositor.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; - -interface IERC20Interface { - function transfer(address recipient, uint256 amount) external returns (bool); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - function balanceOf(address account) external view returns (uint256); -} - -interface IVotingEscrowInterface { - function deposit_for(address _addr, uint256 _value) external; -} - -contract VotingEscrowDepositor is Ownable{ - IERC20Interface public token; - IVotingEscrowInterface public escrowToken; - // map from address to amount availabe to withdraw - mapping(address => uint256) public availableToWithdraw; - address[] public withdrawAddresses; - - constructor(address _tokenAddress, address _escrowTokenAddress){ - token = IERC20Interface(_tokenAddress); - escrowToken = IVotingEscrowInterface(_escrowTokenAddress); - } - - function increaseWithdrawAmounts(address[] memory addresses, uint256[] memory amounts) external onlyOwner{ - require(addresses.length == amounts.length, "Arrays must be of equal length"); - uint256 totalAmount = 0; - for (uint256 i = 0; i < addresses.length; i++) { - if (availableToWithdraw[addresses[i]] == 0) { - withdrawAddresses.push(addresses[i]); - } - availableToWithdraw[addresses[i]] += amounts[i]; - totalAmount += amounts[i]; - } - require(token.transferFrom(msg.sender, address(this), totalAmount), "Token transfer failed"); - } - - function clearWithdrawAmounts() external onlyOwner { - for (uint256 i = 0; i < withdrawAddresses.length; i++) { - availableToWithdraw[withdrawAddresses[i]] = 0; - } - delete withdrawAddresses; - } - - function withdraw() external { - uint256 amount = availableToWithdraw[msg.sender]; - require(amount > 0, "No amount available to withdraw"); - availableToWithdraw[msg.sender] = 0; - require(token.transfer(msg.sender, amount), "Token transfer failed"); - escrowToken.deposit_for(msg.sender, amount); - } - - function returnTokens() external onlyOwner { - uint256 balance = token.balanceOf(address(this)); - require(token.transfer(msg.sender, balance), "Token transfer failed"); - } - - function sendVotingEscrowTokens(address _addr, uint256 _value) external onlyOwner { - require(token.transfer(_addr, _value), "Token transfer failed"); - escrowToken.deposit_for(_addr, _value); - } -} diff --git a/contracts/src/sweepstakes/mooney_mumbai.sol b/contracts/src/sweepstakes/mooney_mumbai.sol new file mode 100644 index 00000000..b0773447 --- /dev/null +++ b/contracts/src/sweepstakes/mooney_mumbai.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.6; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; +import "@paulrberg/contracts/token/erc20/Erc20Permit.sol"; + +import "./ITickets.sol"; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MumbaiMooney is ERC20, ERC20Permit, Ownable, ITickets { + constructor(string memory _name, string memory _symbol) + ERC20(_name, _symbol) + ERC20Permit(_name) + { + _mint(msg.sender, 1000000 * 10 ** 18); + } + + function print(address _account, uint256 _amount) + external + override + onlyOwner + { + return _mint(_account, _amount); + } + + function redeem(address _account, uint256 _amount) + external + override + onlyOwner + { + return _burn(_account, _amount); + } +} \ No newline at end of file diff --git a/contracts/src/sweepstakes/tts2_mumbai.sol b/contracts/src/sweepstakes/tts2_mumbai.sol new file mode 100644 index 00000000..bc05bb2e --- /dev/null +++ b/contracts/src/sweepstakes/tts2_mumbai.sol @@ -0,0 +1,250 @@ +/* + NAME: TICKET-TO-SPACE-2 + CHAIN: MAINNET +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import "@thirdweb-dev/contracts/eip/ERC721A.sol"; +import "@thirdweb-dev/contracts/extension/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; +import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +contract TicketToSpace2 is ERC721A, Ownable, VRFConsumerBaseV2 { + VRFCoordinatorV2Interface COORDINATOR; + LinkTokenInterface immutable LINKTOKEN = + LinkTokenInterface(0x326C977E6efc84E512bB9C30f76E30c160eD06FB); //https://vrf.chain.link/mainnet + bytes32 immutable keyHash = + 0x4b09e658ed251bcafeebbc69400383d49f344ace09b9576fe248bb02c003fe9f; //200gwei mainnet + address immutable vrfCoordinator_ = 0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed; + + struct RequestStatus { + bool paid; // paid? + bool fulfilled; // whether the request has been successfully fulfilled + uint256[] randomWords; + } + + mapping(uint256 => RequestStatus) + public s_requests; /* requestId --> requestStatus */ + + mapping(uint256 => address) + public winners; + + uint256 public winnersCount = 5; + + string private _nftName = "Ticket to Space NFT 2"; + string private _image = "ipfs://Qmba3umb3db7DqCA19iRSSbtzv9nYUmP8Cibo5QMkLpgpP"; + + IERC20 public mooneyToken = IERC20(0x3818f3273D1f46259b737342Ad30e576A7A74f09); + uint256 public mintPrice = 100 * 10 ** 18; + + bytes32 public previousEntrantsMerkleRoot; + + //VRF subscription ID. + uint64 s_subscriptionId; + + uint256[] public requestIds; + uint256 public lastRequestId; + + uint16 immutable requestConfirmations = 6; + uint32 immutable numWords = 1; + uint256 public immutable maxTokens = 2**256 - 1; + uint256 public immutable maxWalletMints = 50; + + bool public allowTransfers = false; + bool public allowMinting = false; + + bool internal locked; //re-entry lock + + //EVENTS + event RequestSent(uint256 requestId, uint32 numWords); + event RequestFulfilled(uint256 requestId, uint256[] randomWords); + event WinnerSelected(uint256 winnerNum, address winner); + + constructor() VRFConsumerBaseV2(0x7a1BaC17Ccc5b313516C5E16fb24f7659aA5ebed) ERC721A("Ticket to Space 2", "TTS2") { + COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator_); + _setupOwner(msg.sender); + } + + //MODIFIERS + modifier reEntrancyGuard(){ + require(!locked, "No re-entrancy"); + locked = true; + _; + locked = false; + } + + //FUNCTIONS + function _canSetOwner() internal view virtual override returns (bool) { + return msg.sender == owner(); + } + + function setImage(string memory image) public onlyOwner { + _image = image; + } + + function setSubscript(uint64 subscriptionId_) external onlyOwner { + s_subscriptionId = subscriptionId_; + } + + function setAllowTransfers(bool allowTransfers_) external onlyOwner { + allowTransfers = allowTransfers_; + } + + function setAllowMinting(bool allowMinting_) external onlyOwner { + allowMinting = allowMinting_; + } + + function setMooneyToken(IERC20 mooneyToken_) external onlyOwner { + mooneyToken = mooneyToken_; + } + + function setMintPrice(uint256 mintPrice_) external onlyOwner { + mintPrice = mintPrice_; + } + + function addMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + previousEntrantsMerkleRoot = _merkleRoot; + } + + function toString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + function tokenURI(uint256 tokenId) public view virtual override returns (string memory){ + require(_exists(tokenId), "URI query for nonexistent token"); + string memory json = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"name": "', + _nftName, + " #", + toString(tokenId), + '", "image": "', + _image, + '"}' + ) + ) + ) + ); + return string(abi.encodePacked("data:application/json;base64,", json)); + } + + function getSupply() public view returns (uint256) { + return _currentIndex; + } + + function canClaimFree(bytes32[] calldata merkleProof, address receiver) + public + view + returns (bool) + { + + return + MerkleProof.verify( + merkleProof, + previousEntrantsMerkleRoot, + keccak256(abi.encodePacked(receiver)) + ) && balanceOf(receiver) == 0; + } + + function claimFree(bytes32[] calldata merkleProof) public { + require(allowMinting, "Minting is not currently open"); + require(_currentIndex < maxTokens, "error:10003 NFT mint limit reached"); + require(balanceOf(msg.sender) < maxWalletMints, "Address has already minted the maximum amount of NFTs"); + + address claimAddress = msg.sender; + require(canClaimFree(merkleProof, claimAddress), "Address cannot claim for free, or has already claimed"); + _safeMint(claimAddress, 1); + } + + function mint(uint256 count) public payable { + require(allowMinting, "Minting is not currently open"); + require(_currentIndex + count < maxTokens, "Tickets already minted"); + require(count + balanceOf(msg.sender) <= maxWalletMints, "Mint amount is more than allowed"); + + mooneyToken.transferFrom(msg.sender, address(0x000000000000000000000000000000000000dEaD), mintPrice*count); + _safeMint(msg.sender, count); + } + + function _beforeTokenTransfers(address from, address to, uint256 tokenId, uint256 batchSize) + internal + override + { + super._beforeTokenTransfers(from, to, tokenId, batchSize); + //non-transferable after mint until allowTransfers = true + if(from != address(0x0) && !allowTransfers) revert("Transfers disabled"); + } + + function chooseWinner(uint32 limit) external onlyOwner returns(uint256 requestId) { + uint32 callbackGasLimit = limit; + requestId = COORDINATOR.requestRandomWords( + keyHash, + s_subscriptionId, + requestConfirmations, + callbackGasLimit, + numWords + ); + s_requests[requestId] = RequestStatus({ + randomWords: new uint256[](0), + paid: true, + fulfilled: false + }); + requestIds.push(requestId); + lastRequestId = requestId; + emit RequestSent(requestId, numWords); + return requestId; + } + + function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) + internal + override + { + require(s_requests[_requestId].paid, "request not found"); + s_requests[_requestId].fulfilled = true; + s_requests[_requestId].randomWords = _randomWords; + addWinner(this.ownerOf(_randomWords[0] % _currentIndex)); + emit RequestFulfilled(_requestId, _randomWords); + } + + function getRequestStatus( + uint256 _requestId + ) + external + view + returns (bool paid, bool fulfilled, uint256[] memory randomWords) + { + RequestStatus memory request = s_requests[_requestId]; + return (request.paid, request.fulfilled, request.randomWords); + } + + function addWinner(address winner) internal { + require(winnersCount > 0, "All winners already chosen"); + winners[winnersCount] = winner; + winnersCount -= 1; + emit WinnerSelected(winnersCount, winner); + } +} diff --git a/contracts/src/test/MerkleDistributorV2.t.sol b/contracts/src/test/MerkleDistributorV2.t.sol new file mode 100644 index 00000000..34cad461 --- /dev/null +++ b/contracts/src/test/MerkleDistributorV2.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity =0.8.10; + +import {DSTest} from "./utils/test.sol"; +import {Hevm} from "./utils/Hevm.sol"; +import {Signatures as sig} from "./utils/Signatures.sol"; +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MerkleDistributorV2} from "../distributors/MerkleDistributorV2.sol"; + +contract MerkleDistributorTest is DSTest { + Hevm evm = Hevm(HEVM_ADDRESS); + + MockERC20 token; + MerkleDistributorV2 distributor; + + uint256 public constant tokenSupply = 42069 * 1e21; + uint256 public constant airdropAllowance = 1337 * 1e21; + bytes32 public constant merkleRoot = 0x5cdea970c9f23ca3ad7c3d706650a7d1a1cf0269632e0059c9dcfcb544d3a5c8; + + function setUp() public { + token = new MockERC20("Nation3 Network Token", "NATION", tokenSupply); + distributor = new MerkleDistributorV2(); + distributor.setUp(address(this), token, merkleRoot); + // Set allowance for the airdrop + token.approve(address(distributor), airdropAllowance); + } + + function getValidClaimer() + public + pure + returns ( + uint256 index, + address account, + uint256 amount, + bytes32[] memory proofs + ) + { + index = 19; + account = 0xBC61c73CFc191321DA837def848784c002279a01; + amount = 5; + + proofs = new bytes32[](3); + proofs[0] = 0xebe77a4d8819f67553d4563538abf6fd6417c99b9b85486d47458fb8e42fbdd6; + proofs[1] = 0xc1e42302dfdf0b0d2c52bc4f0fdbb35f8b4d02b1a276fe895d77b4116d639f05; + proofs[2] = 0x5221b4a135c004944c72c1a4aa7cd3f203283a3a0822fabd5dc8efbab6689980; + } + + function testInit() public { + assertEq(distributor.sender(), address(this)); + assertEq(address(distributor.token()), address(token)); + assertEq(distributor.merkleRoot(), merkleRoot); + } + + function testClaim() public { + (uint256 index, address account, uint256 amount, bytes32[] memory proofs) = getValidClaimer(); + + distributor.claim(index, account, amount, proofs); + + // Check that the tokens has been sent from sender and the allowance adjust properly + uint256 diffSenderBalance = tokenSupply - token.balanceOf(address(this)); + uint256 diffDistributorAllowance = airdropAllowance - token.allowance(address(this), address(distributor)); + assertEq(token.balanceOf(account), amount); + assertEq(diffSenderBalance, amount); + assertEq(diffDistributorAllowance, amount); + + // Trying to claim again must revert + evm.expectRevert(sig.selector("AlreadyClaimed()")); + distributor.claim(index, account, amount, proofs); + } + + function testClaimInvalidProofs() public { + (uint256 index, address account, uint256 amount, bytes32[] memory proofs) = getValidClaimer(); + + // Invalid proofs + bytes32[] memory badProofs = new bytes32[](2); + badProofs[0] = proofs[1]; + badProofs[1] = proofs[0]; + + evm.expectRevert(sig.selector("InvalidProof()")); + distributor.claim(index, account, amount, badProofs); + } + + function testClaimInvalidAccount() public { + (uint256 index, , uint256 amount, bytes32[] memory proofs) = getValidClaimer(); + + // Random account + address badAccount = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + + evm.expectRevert(sig.selector("InvalidProof()")); + distributor.claim(index, badAccount, amount, proofs); + } + + function testClaimInvalidAmount() public { + (uint256 index, address account, , bytes32[] memory proofs) = getValidClaimer(); + + // Random amount + uint256 badAmount = 20; + + evm.expectRevert(sig.selector("InvalidProof()")); + distributor.claim(index, account, badAmount, proofs); + } + + function testDropReset() public { + (uint256 index, address account, uint256 amount, bytes32[] memory proofs) = getValidClaimer(); + + distributor.claim(index, account, amount, proofs); + + distributor.setUp(address(this), token, merkleRoot); + + distributor.claim(index, account, amount, proofs); + } +} diff --git a/contracts/src/test/VotingEscrowDepositor.t.sol b/contracts/src/test/VotingEscrowDepositor.t.sol deleted file mode 100644 index 8f5934d1..00000000 --- a/contracts/src/test/VotingEscrowDepositor.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import "../governance/VotingEscrowDepositor.sol"; -import "../governance/SmartWalletChecker.sol"; -import "../governance/IVotingEscrow.sol"; -import "../tokens/MyToken.sol"; - -//Using deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -//Deploying MOONEY.. -//Deployed MOONEY to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -//Minted 0.0000000001 tokens to deployer address -//Deploying vMOONEY.. -//Deployed vMOONEY to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 -//Deployment manifest saved to ./deployments/local.json - -contract VotingEscrowDepositorTest is Test { - VotingEscrowDepositor public depositor; - MyToken public token; - IVotingEscrow public escrowToken; - SmartWalletChecker public checker; - - //0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - address public user = address(0x123); - //uint256 public initialBalance = 1000; - uint256 public initialBalance = 126144000 * 2; - uint256 depositAmount = 126144000; - - address public MOONEY = 0x5FbDB2315678afecb367f032d93F642f64180aa3; - - address public vMOONEY = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512; - - - function setUp() public { - token = MyToken(MOONEY); - - escrowToken = IVotingEscrow(vMOONEY); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - depositor = new VotingEscrowDepositor(address(MOONEY), address(vMOONEY)); - checker = new SmartWalletChecker(true); - checker.approveWallet(address(depositor)); - // TODO we really shouldn't need this? - checker.approveWallet(address(user)); - //escrowToken.admin(); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - escrowToken.commit_smart_wallet_checker(address(checker)); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - escrowToken.apply_smart_wallet_checker(); - //token.transfer(address(depositor), initialBalance); - address[] memory addresses = new address[](1); - addresses[0] = address(user); - uint256[] memory amounts = new uint256[](1); - amounts[0] = depositAmount; - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - token.approve(address(depositor), depositAmount); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - depositor.increaseWithdrawAmounts(addresses, amounts); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - token.transfer(address(user), initialBalance); - vm.prank(user); - token.approve(address(escrowToken), initialBalance); - vm.prank(user); - escrowToken.create_lock(initialBalance, block.timestamp + 4*60 * 60 * 24 * 365); - // Mint tokens for the depositor contract to transfer - //token.mint(address(depositor), initialBalance); - vm.prank(user); - token.approve(address(escrowToken), depositAmount); - vm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - depositor.sendVotingEscrowTokens(address(user), depositAmount); - } - - function testTransferAndDepositFor() public { - //uint256 depositAmount = 500; - - - // Verify initial balances and deposits - //assertEq(token.balanceOf(address(depositor)), initialBalance); - //assertEq(token.balanceOf(user), 0); - //assertEq(escrowToken.balanceOf(user), initialBalance); - escrowToken.checkpoint(); - - //assertEq(escrowToken.admin(), address(0)); - - // Call transfer_and_deposit_for - vm.prank(user); - //depositor.transfer_and_deposit_for(user, depositAmount); - //depositor.withdraw(); - - // Verify token transfer - //assertEq(token.balanceOf(address(depositor)), initialBalance - depositAmount); - //assertEq(token.balanceOf(user), 0); - ////assertEq(escrowToken.totalSupply(), initialBalance + depositAmount); - //escrowToken.user_point_history(user, 0); - //escrowToken.user_point_history(user, 1); - //escrowToken.user_point_history(user, 2); - //escrowToken.get_last_user_slope(user); - //escrowToken.user_point_history__ts(user, 0); - //escrowToken.user_point_history__ts(user, 1); - //escrowToken.user_point_history__ts(user, 2); - - //// Verify escrow deposit - assertEq(escrowToken.balanceOf(user), depositAmount + initialBalance); - } -} diff --git a/contracts/src/test/utils/mocks/MockVotingEscrow.sol b/contracts/src/test/utils/mocks/MockVotingEscrow.sol new file mode 100644 index 00000000..a5293439 --- /dev/null +++ b/contracts/src/test/utils/mocks/MockVotingEscrow.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.10; +import {IVotingEscrow} from "../../../governance/IVotingEscrow.sol"; + +contract MockVotingEscrow is IVotingEscrow { + string name; + string symbol; + + mapping(address => uint256) internal _balance; + + constructor(string memory _name, string memory _symbol) { + name = _name; + symbol = _symbol; + } + + function allowance(address owner, address spender) external view returns (uint256) {} + + function approve(address spender, uint256 amount) external returns (bool) {} + + function totalSupply() external view returns (uint256) {} + + function transfer(address to, uint256 amount) external returns (bool) {} + + function transferFrom(address from, address to, uint256 amount) external returns (bool) {} + + function setBalance(address to, uint256 amount) public { + _balance[to] = amount; + } + + function balanceOf(address account) public view returns (uint256) { + return _balance[account]; + } + + function locked(address) external view returns (LockedBalance memory) {} +} + + diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 077fe0d2..e12f1345 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -39,7 +39,6 @@ import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' import { JoinDePrizeModal } from '@/components/nance/JoinDePrizeModal' -import { TeamPreview } from '@/components/subscription/TeamPreview' import StandardButton from '../layout/StandardButton' import { useVotingPowers } from '@/lib/snapshot' import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' diff --git a/ui/components/onboarding/TeamTier.tsx b/ui/components/onboarding/TeamTier.tsx index e05ddd52..1c4379c7 100644 --- a/ui/components/onboarding/TeamTier.tsx +++ b/ui/components/onboarding/TeamTier.tsx @@ -18,13 +18,12 @@ const TeamTier = ({ setSelectedTier, compact = false }: TeamTierProps) => { const [applyModalEnabled, setApplyModalEnabled] = useState(false) const handleTeamClick = async () => { - //const teamWhitelistContract = await sdk?.getContract( - //TEAM_WHITELIST_ADDRESSES[selectedChain.slug] - //) - //const isWhitelisted = await teamWhitelistContract?.call('isWhitelisted', [ - //address, - //]) - const isWhitelisted = true + const teamWhitelistContract = await sdk?.getContract( + TEAM_WHITELIST_ADDRESSES[selectedChain.slug] + ) + const isWhitelisted = await teamWhitelistContract?.call('isWhitelisted', [ + address, + ]) if (isWhitelisted || process.env.NEXT_PUBLIC_ENV === 'dev') { setSelectedTier('team') } else { @@ -48,8 +47,8 @@ const TeamTier = ({ setSelectedTier, compact = false }: TeamTierProps) => { 'Capital Raising Tools: Leverage new tools to raise capital or solicit donations from a global network of space enthusiasts.', 'Onchain Tools: Utilize advanced and secure onchain tools to manage your organization and interface with smart contracts.', ]} - buttoncta={compact ? 'Learn More' : 'Create a Team'} - onClick={compact ? () => {} : handleTeamClick} + buttoncta={compact ? "Learn More" : "Create a Team"} + onClick={compact ? ()=>{} :handleTeamClick} type="team" compact={compact} /> diff --git a/ui/components/subscription/TeamPreview.tsx b/ui/components/subscription/TeamPreview.tsx deleted file mode 100644 index 2ab8902f..00000000 --- a/ui/components/subscription/TeamPreview.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { NFT, ThirdwebNftMedia } from '@thirdweb-dev/react' -import { useEffect, useState } from 'react' - -type TeamPreviewProps = { - teamId: any - teamContract?: any -} - -export function TeamPreview({ teamId, teamContract }: TeamPreviewProps) { - const [teamNFT, setTeamNFT] = useState() - - useEffect(() => { - async function getTeamNFT() { - const nft = await teamContract.erc721.get(teamId) - setTeamNFT(nft) - } - - if (teamContract?.erc721?.get && teamId) { - getTeamNFT() - } - }, [teamId, teamContract]) - - return ( -
- {teamNFT && ( -
- -
- )} -
- ) -} diff --git a/ui/lib/utils/hooks/useWithdrawAmount.ts b/ui/lib/utils/hooks/useWithdrawAmount.ts deleted file mode 100644 index c077d4c5..00000000 --- a/ui/lib/utils/hooks/useWithdrawAmount.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useState, useEffect } from 'react' - -export default function useWithdrawAmount( - votingEscrowDepositorContract: any, - userAddress: string -) { - const [withdrawAmount, setWithdrawAmount] = useState(0) - - useEffect(() => { - async function fetchWithdrawAmount() { - if (!votingEscrowDepositorContract || !userAddress) return - const theWithdrawAmount = await votingEscrowDepositorContract.call( - 'availableToWithdraw', - [userAddress] - ) - setWithdrawAmount(theWithdrawAmount) - } - - fetchWithdrawAmount() - }, [votingEscrowDepositorContract, userAddress]) - - return withdrawAmount -} From cc8c840ab01392ac0ac89065dcd32268c3f41e65 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 11:25:27 -0800 Subject: [PATCH 21/44] cleanup --- ui/components/nance/CompetitorPreview.tsx | 2 -- ui/components/nance/JoinDePrizeModal.tsx | 4 --- ui/components/nance/RetroactiveRewards.tsx | 42 ---------------------- 3 files changed, 48 deletions(-) diff --git a/ui/components/nance/CompetitorPreview.tsx b/ui/components/nance/CompetitorPreview.tsx index af756479..b49488eb 100644 --- a/ui/components/nance/CompetitorPreview.tsx +++ b/ui/components/nance/CompetitorPreview.tsx @@ -22,8 +22,6 @@ export function CompetitorPreview({ getTeamNFT() } }, [teamId, teamContract]) - console.log('teamNFT') - console.log(teamNFT) return (
diff --git a/ui/components/nance/JoinDePrizeModal.tsx b/ui/components/nance/JoinDePrizeModal.tsx index 231709bf..0bb49cf4 100644 --- a/ui/components/nance/JoinDePrizeModal.tsx +++ b/ui/components/nance/JoinDePrizeModal.tsx @@ -18,10 +18,6 @@ export function JoinDePrizeModal({ return ( setJoinModalOpen(false)} - // close if clicked outside of modal - onClick={(e) => { - if (e.target.id === 'modal-backdrop') setJoinModalOpen(false) - }} title="Join DePrize Competition" >
From 153ff91391efde0a75576aff50b393ccf36fe01c Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 11:31:33 -0800 Subject: [PATCH 22/44] cleanup --- ui/components/nance/DePrize.tsx | 1 - ui/components/nance/JoinDePrizeModal.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index e12f1345..bbc62c14 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -7,7 +7,6 @@ import TeamABI from 'const/abis/Team.json' import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, - SNAPSHOT_RETROACTIVE_REWARDS_ID, PRIZE_TOKEN_ADDRESSES, COMPETITOR_TABLE_ADDRESSES, PRIZE_DECIMALS, diff --git a/ui/components/nance/JoinDePrizeModal.tsx b/ui/components/nance/JoinDePrizeModal.tsx index 0bb49cf4..6322beef 100644 --- a/ui/components/nance/JoinDePrizeModal.tsx +++ b/ui/components/nance/JoinDePrizeModal.tsx @@ -59,7 +59,6 @@ export function JoinDePrizeModal({ Create New Team From 2394c23f4b54c67e7911564cb985c25b9393ba0c Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 11:40:12 -0800 Subject: [PATCH 23/44] cleanup --- ui/components/nance/DePrize.tsx | 283 ++++++-------------------------- ui/const/config.ts | 1 + ui/pages/deprize.tsx | 6 +- 3 files changed, 58 insertions(+), 232 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index bbc62c14..32d51a8c 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -7,6 +7,7 @@ import TeamABI from 'const/abis/Team.json' import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, + DEPRIZE_ID, PRIZE_TOKEN_ADDRESSES, COMPETITOR_TABLE_ADDRESSES, PRIZE_DECIMALS, @@ -72,100 +73,8 @@ export function DePrize({ const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const { isMobile } = useWindowSize() - console.log('distributions') - console.log(distributions) const userAddress = useAddress() - const year = new Date().getFullYear() const quarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 - const deprize = 1 - - const [edit, setEdit] = useState(false) - const [distribution, setDistribution] = useState<{ [key: string]: number }>( - {} - ) - // Check if the user already has a distribution for the current quarter - useEffect(() => { - if (distributions && userAddress) { - // // Calculate the current voter rewards for that user on each competitor. - // const voterRewards = getVoterRewards(distributions) - - for (const d of distributions) { - if (d.address.toLowerCase() === userAddress.toLowerCase()) { - setDistribution(d.distribution) - setEdit(true) - break - } - } - } - }, [userAddress, distributions]) - - const handleDistributionChange = (competitorId: string, value: number) => { - setDistribution((prev) => ({ - ...prev, - [competitorId]: Math.min(100, Math.max(1, value)), - })) - } - - const getVoterRewards = (distributions: Distribution[]) => { - if (distributions.length === 0) { - return {} - } - - // Populate a map of the latest allocations: From user to distribution (NOTE: assumes *voting power* - not percentage). - let userToDistributions: { [key: string]: { [key: string]: number } } = {} - - // Voter rewards given a certain competitor ID, mapping from user to percentage of rewards. - let competitorToVoterRewardPercentages: { - [key: string]: { [key: string]: number } - } = {} - - let previousTimestamp = distributions[0].timestamp - let elapsedTime = 0 - - for (let i = 0; i < distributions.length; i++) { - const d = distributions[i] - // Calculate the delta between the current timestamp and the distribution timestamp. - const delta = previousTimestamp - d.timestamp - - // Iterate through all the competitors and calculate the voter reward percentages. - for (const competitor of competitors) { - // const newAllocationToCompetitor = d.distribution[competitor.id] * d.votingPower - let totalVotingPowerToCompetitor = 0 - - // Get the total voting power allocation to the competitor, from all users. - for (const [, distribution] of Object.entries(userToDistributions)) { - totalVotingPowerToCompetitor += distribution[competitor.id] - } - - // For every user, calculate the percentage they make up of the total voting power to the competitor. - for (const [userID, distribution] of Object.entries( - userToDistributions - )) { - const percentageInTimeWindow = - distribution[competitor.id] / totalVotingPowerToCompetitor - const previousPercentage = - competitorToVoterRewardPercentages[competitor.id][userID] - - // Update the voter reward percentage, using a time-weighted average. - competitorToVoterRewardPercentages[competitor.id][userID] = - (previousPercentage * elapsedTime + - percentageInTimeWindow * delta) / - (elapsedTime + delta) - } - } - - // Update the allUserDistributions map with the new distribution. - userToDistributions[d.address] = d.distribution - - // Update the elapsed time and previous timestamp. - elapsedTime += delta - previousTimestamp = d.timestamp - } - - return competitorToVoterRewardPercentages - } - - const addresses = distributions ? distributions.map((d) => d.address) : [] const { contract: prizeContract } = useContract( PRIZE_TOKEN_ADDRESSES[chain.slug], @@ -179,154 +88,96 @@ export function DePrize({ COMPETITOR_TABLE_ADDRESSES[chain.slug], CompetitorABI ) - const { contract: bulkTokenSenderContract } = useContract( - BULK_TOKEN_SENDER_ADDRESSES[chain.slug] - ) const { contract: distributionTableContract } = useContract( DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug] ) - const { contract: hatsContract } = useContract(HATS_ADDRESS) const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) - const isOperator = useIsOperator(revnetContract, userAddress, PRIZE_REVNET_ID) - const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) - //const tokenBalances = useTokenBalances( - //prizeContract, - //PRIZE_DECIMALS, - //addresses - //) - const tokenBalances = [] - const addressToQuadraticVotingPower = Object.fromEntries( - addresses.map((address, i) => [address, Math.sqrt(tokenBalances[i])]) + const [edit, setEdit] = useState(false) + const [distribution, setDistribution] = useState<{ [key: string]: number }>( + {} ) - const votingPowerSumIsNonZero = - _.sum(Object.values(addressToQuadraticVotingPower)) > 0 - const userHasVotingPower = - prizeBalance > 0 || - (userAddress && - (userAddress.toLowerCase() in addressToQuadraticVotingPower || - userAddress in addressToQuadraticVotingPower) && - addressToQuadraticVotingPower[userAddress.toLowerCase()] > 0) - - const readyToRunVoting = votingPowerSumIsNonZero - - const budgetPercent = 100 - const competitorIdToEstimatedPercentage: { [key: string]: number } = - runQuadraticVoting( - distributions, - addressToQuadraticVotingPower, - budgetPercent - ) + // Check if the user already has a distribution + useEffect(() => { + if (distributions && userAddress) { + for (const d of distributions) { + if (d.address.toLowerCase() === userAddress.toLowerCase()) { + setDistribution(d.distribution) + setEdit(true) + break + } + } + } + }, [userAddress, distributions]) - const { tokens } = useAssets() - const { ethBudget, usdBudget, mooneyBudget, ethPrice } = getBudget( - tokens, - year, - quarter - ) + const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) + const userHasVotingPower = prizeBalance > 0 const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) const prizeBudget = prizeSupply * 0.1 const winnerPool = prizeSupply * 0.3 const prizePrice = 1 - const competitorIdToPrizePayout = competitors - ? Object.fromEntries( - competitors.map(({ id }) => [ - id, - (prizeBudget * competitorIdToEstimatedPercentage[id]) / 100, - ]) - ) - : {} - const handleSubmit = async () => { - const totalPercentage = Object.values(distribution).reduce( - (sum, value) => sum + value, - 0 + const [joinModalOpen, setJoinModalOpen] = useState(false) + + const userTeams = useTeamWearer(teamContract, chain, userAddress) + + const isCompetitor = userTeams.some((team) => + competitors.some( + (competitor) => competitor.teamId.toString() === team.teamId ) - if (totalPercentage !== 100) { - toast.error('Total distribution must equal 100%', { - style: toastStyle, - }) - return - } + ) + const handleJoinWithTeam = async (teamId: string) => { try { - await distributionTableContract?.call('insertIntoTable', [ - deprize, - JSON.stringify(distribution), + await competitorContract?.call('insertIntoTable', [ + DEPRIZE_ID, + teamId, + '{}', ]) - toast.success('Distribution submitted successfully!', { + toast.success('Joined as a competitor!', { style: toastStyle, }) + setJoinModalOpen(false) setTimeout(() => { refreshRewards() }, 5000) } catch (error) { - console.error('Error submitting distribution:', error) - toast.error('Error submitting distribution. Please try again.', { + console.error('Error joining as a competitor:', error) + toast.error('Error joining as a competitor. Please try again.', { style: toastStyle, }) } } - - const handleSend = async () => { - try { - const addresses = competitors.map((c) => c.treasury) - const amounts = competitors.map( - (c) => competitorIdToPrizePayout[c.id] * 10 ** PRIZE_DECIMALS - ) - // approve bulk token sender - //await prizeContract?.call('approve', [ - //BULK_TOKEN_SENDER_ADDRESSES[chain.slug], - //String(amounts.reduce((a, b) => a + b, 0)), - //]) - await bulkTokenSenderContract?.call('send', [ - PRIZE_TOKEN_ADDRESSES[chain.slug], - addresses.slice(0, 1), - amounts.map(String).slice(0, 1), - ]) - toast.success('Rewards sent successfully!', { - style: toastStyle, - }) - setTimeout(() => { - refreshRewards() - }, 5000) - } catch (error) { - console.error('Error sending rewards:', error) - toast.error('Error sending rewards. Please try again.', { + const handleDistributionChange = (competitorId: string, value: number) => { + setDistribution((prev) => ({ + ...prev, + [competitorId]: Math.min(100, Math.max(1, value)), + })) + } + const handleSubmit = async () => { + const totalPercentage = Object.values(distribution).reduce( + (sum, value) => sum + value, + 0 + ) + if (totalPercentage !== 100) { + toast.error('Total distribution must equal 100%', { style: toastStyle, }) + return } - } - const DEPRIZE_ID = 1 - const [joinModalOpen, setJoinModalOpen] = useState(false) - - // Get user's teams - const userTeams = useTeamWearer(teamContract, chain, userAddress) - - //const isCompetitor = userTeams.some((team) => - //competitors.some( - //(competitor) => competitor.teamId.toString() === team.teamId - //) - //) - const isCompetitor = false - console.log('isCompetitor') - console.log(isCompetitor) - const handleJoinWithTeam = async (teamId: string) => { try { - await competitorContract?.call('insertIntoTable', [ + await distributionTableContract?.call('insertIntoTable', [ DEPRIZE_ID, - teamId, - '{}', + JSON.stringify(distribution), ]) - toast.success('Joined as a competitor!', { + toast.success('Distribution submitted successfully!', { style: toastStyle, }) - setJoinModalOpen(false) setTimeout(() => { refreshRewards() }, 5000) } catch (error) { - console.error('Error joining as a competitor:', error) - toast.error('Error joining as a competitor. Please try again.', { + console.error('Error submitting distribution:', error) + toast.error('Error submitting distribution. Please try again.', { style: toastStyle, }) } @@ -452,24 +303,6 @@ export function DePrize({ teamContract={teamContract} /> - {readyToRunVoting && tokens && tokens[0] && ( - <> -
- {competitorIdToEstimatedPercentage[ - competitor.id - ].toFixed(2)} - % -
-
- {Number( - competitorIdToPrizePayout[ - competitor.id - ].toPrecision(3) - ).toLocaleString()}{' '} - PRIZE -
- - )} ))} @@ -489,14 +322,6 @@ export function DePrize({ Delete Distribution
)} - {isOperator && ( - - Send Rewards - - )} ) : ( diff --git a/ui/const/config.ts b/ui/const/config.ts index 4b011ac5..f7ee4d27 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -126,6 +126,7 @@ export const BULK_TOKEN_SENDER_ADDRESSES: Index = { sepolia: '0xfEcb8E75658d768C9CdB418d81607eF4Dab5d001', } +export const DEPRIZE_ID = 1 export const PRIZE_DECIMALS = 18 export const CITIZEN_WHITELIST_ADDRESSES: Index = { diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 7166aa3a..c1227170 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -4,6 +4,7 @@ import DePrizeDistributionTableABI from 'const/abis/DePrizeDistribution.json' import { COMPETITOR_TABLE_ADDRESSES, DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, + DEPRIZE_ID, TABLELAND_ENDPOINT, } from 'const/config' import { useRouter } from 'next/router' @@ -44,16 +45,15 @@ export async function getStaticProps() { await distributionTableContract.call('getTableName') // TODO don't hardcode - const dePrizeId = 1 const currentYear = new Date().getFullYear() const currentQuarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 - const competitorStatement = `SELECT * FROM ${competitorBoardTableName} WHERE deprize = ${dePrizeId}` + const competitorStatement = `SELECT * FROM ${competitorBoardTableName} WHERE deprize = ${DEPRIZE_ID}` const competitorsRes = await fetch( `${TABLELAND_ENDPOINT}?statement=${competitorStatement}` ) const competitors = await competitorsRes.json() - const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${dePrizeId}` + const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${DEPRIZE_ID}` const distributionsRes = await fetch( `${TABLELAND_ENDPOINT}?statement=${distributionStatement}` ) From b7ca89513ba35cbab0d310f68d1a108816638a0f Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:32:18 -0800 Subject: [PATCH 24/44] cleanup --- ui/components/layout/Sidebar/Navigation.ts | 6 -- ui/components/nance/DePrize.tsx | 107 +-------------------- ui/const/config.ts | 19 +--- ui/lib/utils/voting.ts | 12 --- ui/pages/_app.tsx | 2 +- 5 files changed, 8 insertions(+), 138 deletions(-) diff --git a/ui/components/layout/Sidebar/Navigation.ts b/ui/components/layout/Sidebar/Navigation.ts index 1a746ba8..fc49b3a6 100644 --- a/ui/components/layout/Sidebar/Navigation.ts +++ b/ui/components/layout/Sidebar/Navigation.ts @@ -6,7 +6,6 @@ import { RocketLaunchIcon, Squares2X2Icon, UserGroupIcon, - MoonIcon, } from '@heroicons/react/24/outline' import IconOrg from '../../assets/IconOrg' @@ -20,11 +19,6 @@ export const navigation = [ { name: 'Create a Team', href: '/team' }, ], }, - { - name: 'De-Prize', - href: '/deprize', - icon: MoonIcon, - }, { name: 'Network', href: '/network', diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 32d51a8c..adb91380 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -4,7 +4,6 @@ import CompetitorABI from 'const/abis/Competitor.json' import ERC20 from 'const/abis/ERC20.json' import REVDeployer from 'const/abis/REVDeployer.json' import TeamABI from 'const/abis/Team.json' -import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, DEPRIZE_ID, @@ -13,7 +12,6 @@ import { PRIZE_DECIMALS, REVNET_ADDRESSES, PRIZE_REVNET_ID, - BULK_TOKEN_SENDER_ADDRESSES, } from 'const/config' import { TEAM_ADDRESSES } from 'const/config' import { HATS_ADDRESS } from 'const/config' @@ -27,21 +25,21 @@ import { useTeamWearer } from '@/lib/hats/useTeamWearer' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' import useIsOperator from '@/lib/revnet/hooks/useIsOperator' +import { useVotingPowers } from '@/lib/snapshot' import useWindowSize from '@/lib/team/use-window-size' +import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' import useTokenSupply from '@/lib/tokens/hooks/useTokenSupply' import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance' import { getBudget, getPayouts } from '@/lib/utils/rewards' -import { runQuadraticVoting } from '@/lib/utils/voting' import Asset from '@/components/dashboard/treasury/balance/Asset' import { Hat } from '@/components/hats/Hat' import Container from '@/components/layout/Container' import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' import { NoticeFooter } from '@/components/layout/NoticeFooter' +import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { JoinDePrizeModal } from '@/components/nance/JoinDePrizeModal' import StandardButton from '../layout/StandardButton' -import { useVotingPowers } from '@/lib/snapshot' -import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' export type Metadata = { social: string @@ -97,19 +95,6 @@ export function DePrize({ const [distribution, setDistribution] = useState<{ [key: string]: number }>( {} ) - // Check if the user already has a distribution - useEffect(() => { - if (distributions && userAddress) { - for (const d of distributions) { - if (d.address.toLowerCase() === userAddress.toLowerCase()) { - setDistribution(d.distribution) - setEdit(true) - break - } - } - } - }, [userAddress, distributions]) - const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) const userHasVotingPower = prizeBalance > 0 const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) @@ -147,42 +132,6 @@ export function DePrize({ }) } } - const handleDistributionChange = (competitorId: string, value: number) => { - setDistribution((prev) => ({ - ...prev, - [competitorId]: Math.min(100, Math.max(1, value)), - })) - } - const handleSubmit = async () => { - const totalPercentage = Object.values(distribution).reduce( - (sum, value) => sum + value, - 0 - ) - if (totalPercentage !== 100) { - toast.error('Total distribution must equal 100%', { - style: toastStyle, - }) - return - } - try { - await distributionTableContract?.call('insertIntoTable', [ - DEPRIZE_ID, - JSON.stringify(distribution), - ]) - toast.success('Distribution submitted successfully!', { - style: toastStyle, - }) - setTimeout(() => { - refreshRewards() - }, 5000) - } catch (error) { - console.error('Error submitting distribution:', error) - toast.error('Error submitting distribution. Please try again.', { - style: toastStyle, - }) - } - } - return (
-
-

- Distribute -

- {readyToRunVoting && ( -

- Estimated Rewards -

- )} -
{competitors && competitors.map((competitor, i: number) => ( @@ -276,27 +215,6 @@ export function DePrize({ key={i} className="flex items-center w-full py-1 text-[17px]" > -
- - handleDistributionChange( - competitor.id, - parseInt(e.target.value) - ) - } - className="border rounded px-2 py-1 w-20" - style={{ - backgroundColor: 'var(--black)', - }} - min="1" - max="100" - disabled={!userAddress || !userHasVotingPower} - /> - % -
-     
))}
- {competitors && userHasVotingPower ? ( - - - {edit ? 'Edit Distribution' : 'Submit Distribution'} - - {edit && ( - - Delete Distribution - - )} - - ) : ( + {!userHasVotingPower && ( Date: Tue, 3 Dec 2024 15:36:02 -0800 Subject: [PATCH 25/44] cleanup --- ui/components/nance/DePrize.tsx | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index adb91380..c92205d0 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -3,7 +3,6 @@ import { useAddress, useContract } from '@thirdweb-dev/react' import CompetitorABI from 'const/abis/Competitor.json' import ERC20 from 'const/abis/ERC20.json' import REVDeployer from 'const/abis/REVDeployer.json' -import TeamABI from 'const/abis/Team.json' import { DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, DEPRIZE_ID, @@ -14,25 +13,14 @@ import { PRIZE_REVNET_ID, } from 'const/config' import { TEAM_ADDRESSES } from 'const/config' -import { HATS_ADDRESS } from 'const/config' -import { BigNumber } from 'ethers' -import _ from 'lodash' -import { useState, useEffect } from 'react' +import { useState } from 'react' import toast from 'react-hot-toast' -import { useCitizens } from '@/lib/citizen/useCitizen' -import { useAssets } from '@/lib/dashboard/hooks' import { useTeamWearer } from '@/lib/hats/useTeamWearer' import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig' -import { SNAPSHOT_SPACE_NAME } from '@/lib/nance/constants' -import useIsOperator from '@/lib/revnet/hooks/useIsOperator' -import { useVotingPowers } from '@/lib/snapshot' import useWindowSize from '@/lib/team/use-window-size' -import useTokenBalances from '@/lib/tokens/hooks/useTokenBalances' import useTokenSupply from '@/lib/tokens/hooks/useTokenSupply' import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance' -import { getBudget, getPayouts } from '@/lib/utils/rewards' import Asset from '@/components/dashboard/treasury/balance/Asset' -import { Hat } from '@/components/hats/Hat' import Container from '@/components/layout/Container' import ContentLayout from '@/components/layout/ContentLayout' import Head from '@/components/layout/Head' @@ -41,9 +29,7 @@ import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import { JoinDePrizeModal } from '@/components/nance/JoinDePrizeModal' import StandardButton from '../layout/StandardButton' -export type Metadata = { - social: string -} +export type Metadata = {} export type Competitor = { id: string deprize: number @@ -78,23 +64,12 @@ export function DePrize({ PRIZE_TOKEN_ADDRESSES[chain.slug], ERC20.abi ) - const { contract: revnetContract } = useContract( - REVNET_ADDRESSES[chain.slug], - REVDeployer - ) const { contract: competitorContract } = useContract( COMPETITOR_TABLE_ADDRESSES[chain.slug], CompetitorABI ) - const { contract: distributionTableContract } = useContract( - DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug] - ) const { contract: teamContract } = useContract(TEAM_ADDRESSES[chain.slug]) - const [edit, setEdit] = useState(false) - const [distribution, setDistribution] = useState<{ [key: string]: number }>( - {} - ) const prizeBalance = useWatchTokenBalance(prizeContract, PRIZE_DECIMALS) const userHasVotingPower = prizeBalance > 0 const prizeSupply = useTokenSupply(prizeContract, PRIZE_DECIMALS) From 7c77920f0f1c9d808547241c7c5a9c97cecda914 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:36:58 -0800 Subject: [PATCH 26/44] cleanup --- ui/components/nance/DePrize.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index c92205d0..ec3521c0 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -56,7 +56,7 @@ export function DePrize({ }: DePrizeProps) { const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const { isMobile } = useWindowSize() - + const [joinModalOpen, setJoinModalOpen] = useState(false) const userAddress = useAddress() const quarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 @@ -77,8 +77,6 @@ export function DePrize({ const winnerPool = prizeSupply * 0.3 const prizePrice = 1 - const [joinModalOpen, setJoinModalOpen] = useState(false) - const userTeams = useTeamWearer(teamContract, chain, userAddress) const isCompetitor = userTeams.some((team) => From 8b5713f28e2a4eef777004948ec80ff93d1f08f2 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:40:39 -0800 Subject: [PATCH 27/44] update submodule --- contracts/lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std index 1eea5bae..da591f56 160000 --- a/contracts/lib/forge-std +++ b/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 +Subproject commit da591f56d8884c5824c0c1b3103fbcfd81123c4c From f069e1b26614fb5974891f9ba81a4678b011ec04 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:41:39 -0800 Subject: [PATCH 28/44] remove submodule --- contracts/lib/forge-std | 1 - 1 file changed, 1 deletion(-) delete mode 160000 contracts/lib/forge-std diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std deleted file mode 160000 index da591f56..00000000 --- a/contracts/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit da591f56d8884c5824c0c1b3103fbcfd81123c4c From 7e1292d1ac34675d0a3d7d8ebde957c2db275bf9 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:43:00 -0800 Subject: [PATCH 29/44] removes unused hook --- ui/lib/revnet/hooks/useIsOperator.tsx | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 ui/lib/revnet/hooks/useIsOperator.tsx diff --git a/ui/lib/revnet/hooks/useIsOperator.tsx b/ui/lib/revnet/hooks/useIsOperator.tsx deleted file mode 100644 index e1e529b9..00000000 --- a/ui/lib/revnet/hooks/useIsOperator.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useState } from 'react' - -export default function useIsOperator( - revnetContract: any, - operator: string, - revnetId: number -) { - const [isOperator, setIsOperator] = useState(false) - useEffect(() => { - async function checkIsOperator() { - if (!revnetContract) return - const isOperator = await revnetContract.call('isSplitOperatorOf', [ - revnetId, - operator, - ]) - setIsOperator(isOperator) - } - checkIsOperator() - }, [revnetContract]) - return isOperator -} From 4b98a0a0a20e2c7088a1244aa9990c9dea7097cc Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:46:12 -0800 Subject: [PATCH 30/44] fix number formatting --- ui/components/nance/DePrize.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index ec3521c0..340870aa 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -79,11 +79,12 @@ export function DePrize({ const userTeams = useTeamWearer(teamContract, chain, userAddress) - const isCompetitor = userTeams.some((team) => - competitors.some( - (competitor) => competitor.teamId.toString() === team.teamId - ) - ) + const isCompetitor = false + //userTeams.some((team) => + //competitors.some( + //(competitor) => competitor.teamId.toString() === team.teamId + //) + //) const handleJoinWithTeam = async (teamId: string) => { try { await competitorContract?.call('insertIntoTable', [ @@ -166,7 +167,11 @@ export function DePrize({

Winner Prize

- +
)} {userAddress && ( From 593d4a7ff806bafb5938c82fc5d744afdd4a72b9 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:47:08 -0800 Subject: [PATCH 31/44] undo debug --- ui/components/nance/DePrize.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 340870aa..38222150 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -79,12 +79,11 @@ export function DePrize({ const userTeams = useTeamWearer(teamContract, chain, userAddress) - const isCompetitor = false - //userTeams.some((team) => - //competitors.some( - //(competitor) => competitor.teamId.toString() === team.teamId - //) - //) + const isCompetitor = userTeams.some((team) => + competitors.some( + (competitor) => competitor.teamId.toString() === team.teamId + ) + ) const handleJoinWithTeam = async (teamId: string) => { try { await competitorContract?.call('insertIntoTable', [ From ea7f57786c000967f84cf6be39cf87c26b58ae82 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:50:40 -0800 Subject: [PATCH 32/44] fix unit test --- ui/components/nance/DePrize.tsx | 2 +- ui/public/sitemap-0.xml | 33 ++------------------------------- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index 38222150..101b3b11 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -79,7 +79,7 @@ export function DePrize({ const userTeams = useTeamWearer(teamContract, chain, userAddress) - const isCompetitor = userTeams.some((team) => + const isCompetitor = userTeams.some((team: any) => competitors.some( (competitor) => competitor.teamId.toString() === team.teamId ) diff --git a/ui/public/sitemap-0.xml b/ui/public/sitemap-0.xml index 2ee6ddeb..4c692bc1 100644 --- a/ui/public/sitemap-0.xml +++ b/ui/public/sitemap-0.xml @@ -1,34 +1,5 @@ -moondao.com2024-10-31T00:28:44.874Zdaily0.7 -moondao.com/about2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/almost-there2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/analytics2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/bridge2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/citizen2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/constitution2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/current-projects2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/deprize2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/dude-perfect2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/events2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/get-mooney2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/governance2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/info2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/jobs2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/join2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/join-us2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/lifeship2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/linktree2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/lock2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/marketplace2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/network2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/news2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/propose2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/rewards2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/sweepstakes2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/team2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/thank-you2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/vote2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/zero-gravity2024-10-31T00:28:44.875Zdaily0.7 -moondao.com/4042024-10-31T00:28:44.875Zdaily0.7 +moondao.com2024-12-03T23:50:30.663Zdaily0.7 +moondao.com/deprize2024-12-03T23:50:30.664Zdaily0.7 \ No newline at end of file From 2d4dd6a1aa9e5073c42cc419e0f9c6f458d1ea98 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:54:59 -0800 Subject: [PATCH 33/44] fixes build error --- ui/components/nance/JoinDePrizeModal.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/components/nance/JoinDePrizeModal.tsx b/ui/components/nance/JoinDePrizeModal.tsx index 6322beef..d4c7e92e 100644 --- a/ui/components/nance/JoinDePrizeModal.tsx +++ b/ui/components/nance/JoinDePrizeModal.tsx @@ -1,7 +1,8 @@ import { XMarkIcon } from '@heroicons/react/24/outline' +import Modal from '@/components/layout/Modal' import { CompetitorPreview } from '@/components/nance/CompetitorPreview' import StandardButton from '../layout/StandardButton' -import Modal from '@/components/layout/Modal' + type JoinDePrizeModalProps = { userTeams: any[] setJoinModalOpen: (open: boolean) => void @@ -16,10 +17,7 @@ export function JoinDePrizeModal({ handleJoinWithTeam, }: JoinDePrizeModalProps) { return ( - setJoinModalOpen(false)} - title="Join DePrize Competition" - > +
+
+

+ Competitors +

+
{competitors && competitors.map((competitor, i: number) => ( From afa3fe2b4c78f1274748bda7d4f5ecd805549320 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Tue, 3 Dec 2024 15:59:00 -0800 Subject: [PATCH 35/44] removes titles from depirze modal --- ui/components/nance/JoinDePrizeModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/components/nance/JoinDePrizeModal.tsx b/ui/components/nance/JoinDePrizeModal.tsx index d4c7e92e..fb0f32a3 100644 --- a/ui/components/nance/JoinDePrizeModal.tsx +++ b/ui/components/nance/JoinDePrizeModal.tsx @@ -17,7 +17,7 @@ export function JoinDePrizeModal({ handleJoinWithTeam, }: JoinDePrizeModalProps) { return ( - +
diff --git a/ui/components/nance/JoinDePrizeModal.tsx b/ui/components/nance/JoinDePrizeModal.tsx index 0919812f..40c62449 100644 --- a/ui/components/nance/JoinDePrizeModal.tsx +++ b/ui/components/nance/JoinDePrizeModal.tsx @@ -35,18 +35,16 @@ export function JoinDePrizeModal({

Your Teams

{userTeams.map((team: any) => ( - <> - - + ))}
diff --git a/ui/const/config.ts b/ui/const/config.ts index 2ffc3ab3..c752231b 100644 --- a/ui/const/config.ts +++ b/ui/const/config.ts @@ -107,20 +107,11 @@ export const DISTRIBUTION_TABLE_ADDRESSES: Index = { 'arbitrum-sepolia': '0xd1D57F18252D06a6b28DE96B6cbF7F4283A4F205', } -export const DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES: Index = { - sepolia: '0xdd8144346390194EBa35F9551a13283a9143f21F', -} -// TODO don't hard code, pull from d-prize contract -export const PRIZE_TOKEN_ADDRESSES: Index = { - sepolia: '0xf2a29F67fb5e6d7B9682591c0fD100d357dA85A7', -} export const REVNET_ADDRESSES: Index = { sepolia: '0x25bc5d5a708c2e426ef3a5196cc18de6b2d5a3d1', } -export const PRIZE_REVNET_ID = 50 export const DEPRIZE_ID = 1 -export const PRIZE_DECIMALS = 18 export const CITIZEN_WHITELIST_ADDRESSES: Index = { arbitrum: '0xd594DBF360D666c94615Fb186AF3cB1018Be1616', diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index caefb1d6..635e15b2 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -8,6 +8,9 @@ import { TABLELAND_ENDPOINT, } from 'const/config' import { useRouter } from 'next/router' +import { useContext } from 'react' +import ChainContext from '@/lib/thirdweb/chain-context' +import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' import { initSDK } from '@/lib/thirdweb/thirdweb' import { DePrize, DePrizeProps } from '../components/nance/DePrize' @@ -27,16 +30,17 @@ export default function DePrizePage({ export async function getStaticProps() { // TODO enable mainnet - const chain = Sepolia - const sdk = initSDK(chain) + useChainDefault() + const { selectedChain } = useContext(ChainContext) + const sdk = initSDK(selectedChain) const competitorTableContract = await sdk.getContract( - COMPETITOR_TABLE_ADDRESSES[chain.slug], + COMPETITOR_TABLE_ADDRESSES[selectedChain.slug], CompetitorABI ) const distributionTableContract = await sdk.getContract( - DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug], + DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[selectedChain.slug], DePrizeDistributionTableABI ) From 3cb85e00f51a54847e88b9856a6caca914d36e55 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Mon, 16 Dec 2024 12:46:56 -0800 Subject: [PATCH 41/44] fix error: --- ui/pages/deprize.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 635e15b2..7777bbf7 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -1,4 +1,4 @@ -import { Sepolia } from '@thirdweb-dev/chains' +import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import CompetitorABI from 'const/abis/Competitor.json' import DePrizeDistributionTableABI from 'const/abis/DePrizeDistribution.json' import { @@ -19,6 +19,7 @@ export default function DePrizePage({ distributions, }: DePrizeProps) { const router = useRouter() + useChainDefault() return ( Date: Mon, 16 Dec 2024 15:57:02 -0800 Subject: [PATCH 42/44] removes distribution table --- ui/components/nance/DePrize.tsx | 13 +----------- ui/pages/deprize.tsx | 35 ++------------------------------- 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index c01fd2d3..ae755e0c 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -33,24 +33,13 @@ export type Competitor = { teamId: number metadata: Metadata } -export type Distribution = { - deprize: number - address: string - timestamp: number - distribution: { [key: string]: number } -} export type DePrizeProps = { competitors: Competitor[] - distributions: Distribution[] refreshRewards: () => void } -export function DePrize({ - competitors, - distributions, - refreshRewards, -}: DePrizeProps) { +export function DePrize({ competitors, refreshRewards }: DePrizeProps) { const { selectedChain } = useContext(ChainContext) const [joinModalOpen, setJoinModalOpen] = useState(false) const userAddress = useAddress() diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 7777bbf7..6b2d743f 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -1,9 +1,7 @@ import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import CompetitorABI from 'const/abis/Competitor.json' -import DePrizeDistributionTableABI from 'const/abis/DePrizeDistribution.json' import { COMPETITOR_TABLE_ADDRESSES, - DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES, DEPRIZE_ID, TABLELAND_ENDPOINT, } from 'const/config' @@ -14,62 +12,33 @@ import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' import { initSDK } from '@/lib/thirdweb/thirdweb' import { DePrize, DePrizeProps } from '../components/nance/DePrize' -export default function DePrizePage({ - competitors, - distributions, -}: DePrizeProps) { +export default function DePrizePage({ competitors }: DePrizeProps) { const router = useRouter() useChainDefault() return ( - router.reload()} - /> + router.reload()} /> ) } export async function getStaticProps() { - // TODO enable mainnet const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const sdk = initSDK(chain) - const competitorTableContract = await sdk.getContract( COMPETITOR_TABLE_ADDRESSES[chain.slug], CompetitorABI ) - - const distributionTableContract = await sdk.getContract( - DEPRIZE_DISTRIBUTION_TABLE_ADDRESSES[chain.slug], - DePrizeDistributionTableABI - ) - const competitorBoardTableName = await competitorTableContract.call( 'getTableName' ) - const distributionTableName = await distributionTableContract.call( - 'getTableName' - ) - - // TODO don't hardcode - const currentYear = new Date().getFullYear() - const currentQuarter = Math.floor((new Date().getMonth() + 3) / 3) - 1 const competitorStatement = `SELECT * FROM ${competitorBoardTableName} WHERE deprize = ${DEPRIZE_ID}` const competitorsRes = await fetch( `${TABLELAND_ENDPOINT}?statement=${competitorStatement}` ) const competitors = await competitorsRes.json() - const distributionStatement = `SELECT * FROM ${distributionTableName} WHERE deprize = ${DEPRIZE_ID}` - const distributionsRes = await fetch( - `${TABLELAND_ENDPOINT}?statement=${distributionStatement}` - ) - const distributions = await distributionsRes.json() - return { props: { competitors, - distributions, }, revalidate: 60, } From 35eb0519a5a7c36f9cdbc8087508745dd44375ed Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Mon, 16 Dec 2024 16:01:32 -0800 Subject: [PATCH 43/44] only testnet --- ui/pages/deprize.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/pages/deprize.tsx b/ui/pages/deprize.tsx index 6b2d743f..01d71062 100644 --- a/ui/pages/deprize.tsx +++ b/ui/pages/deprize.tsx @@ -21,7 +21,8 @@ export default function DePrizePage({ competitors }: DePrizeProps) { } export async function getStaticProps() { - const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia + // TODO enable mainnet + const chain = Sepolia const sdk = initSDK(chain) const competitorTableContract = await sdk.getContract( COMPETITOR_TABLE_ADDRESSES[chain.slug], From 63807a514e9085d5d9c4e41635476b90e3044252 Mon Sep 17 00:00:00 2001 From: jaderiverstokes Date: Mon, 16 Dec 2024 16:13:52 -0800 Subject: [PATCH 44/44] small fix --- ui/components/nance/DePrize.tsx | 1 + ui/public/sitemap-0.xml | 64 +++++++++++++++++---------------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/ui/components/nance/DePrize.tsx b/ui/components/nance/DePrize.tsx index ae755e0c..3a0dae70 100644 --- a/ui/components/nance/DePrize.tsx +++ b/ui/components/nance/DePrize.tsx @@ -123,6 +123,7 @@ export function DePrize({ competitors, refreshRewards }: DePrizeProps) {
{competitors && + competitors.length > 0 && competitors.map((competitor, i: number) => (
-moondao.com2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/about2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/almost-there2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/analytics2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/bridge2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/citizen2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/constitution2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/dude-perfect2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/events2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/get-mooney2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/governance2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/info2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/jobs2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/join2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/join-us2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/lifeship2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/linktree2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/lock2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/map2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/marketplace2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/network2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/news2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/propose2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/rewards2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/sweepstakes2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/team2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/thank-you2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/vote2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/zero-gravity2024-11-21T20:38:14.089Zdaily0.7 -moondao.com/4042024-11-21T20:38:14.089Zdaily0.7 +moondao.com2024-12-17T00:11:23.545Zdaily0.7 +moondao.com/about2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/almost-there2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/analytics2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/bridge2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/citizen2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/constitution2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/contribution2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/deprize2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/dude-perfect2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/events2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/get-mooney2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/governance2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/info2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/jobs2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/join2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/join-us2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/lifeship2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/linktree2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/lock2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/map2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/marketplace2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/network2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/news2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/propose2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/rewards2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/submission2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/sweepstakes2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/team2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/thank-you2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/vote2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/withdraw2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/zero-gravity2024-12-17T00:11:23.546Zdaily0.7 +moondao.com/4042024-12-17T00:11:23.546Zdaily0.7 \ No newline at end of file