From 03bbacd98a35b46ea2ace083e288223548c4b274 Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Mon, 21 Apr 2025 16:14:47 -0400 Subject: [PATCH 1/8] feat: wip spell proposal --- .../components/proposal-type-selection.tsx | 2 + .../propose-governance-spell-31-03-2025.tsx | 219 ++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx diff --git a/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx b/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx index b0333cc20..bc0439073 100644 --- a/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx +++ b/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx @@ -11,6 +11,7 @@ import { } from 'lucide-react' import { Link } from 'react-router-dom' import ProposeIndexUpgrade from './propose-index-upgrade' +import ProposeGovernanceSpell31032025 from './propose-governance-spell-31-03-2025' const proposalTypes = [ { @@ -92,6 +93,7 @@ const ProposalTypeSelection = () => {
+
diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx new file mode 100644 index 000000000..d23450947 --- /dev/null +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -0,0 +1,219 @@ +import dtfIndexAbi from '@/abis/dtf-index-abi' +import dtfAdminAbi from '@/abis/dtf-admin-abi' +import stakingVaultAbi from '@/abis/dtf-index-staking-vault' +import DTFIndexGovernance from '@/abis/dtf-index-governance' +import { Button } from '@/components/ui/button' +import { chainIdAtom } from '@/state/atoms' +import { indexDTFAtom } from '@/state/dtf/atoms' +import { ROUTES } from '@/utils/constants' +import { useAtomValue } from 'jotai' +import { AlertCircle, Loader2 } from 'lucide-react' +import { useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { + encodeFunctionData, + keccak256, + parseAbi, + toBytes, + zeroAddress, +} from 'viem' +import { + useReadContract, + useWaitForTransactionReceipt, + useWriteContract, +} from 'wagmi' +import { useIsProposeAllowed } from '../../../hooks/use-is-propose-allowed' +import { ChainId } from '@/utils/chains' +import { getCurrentTime } from '@/utils' + +const spellAbi = parseAbi([ + 'function upgradeStakingVaultGovernance(address stakingVault, address oldGovernor, address[] calldata guardians, bytes32 deploymentNonce) external returns (address newGovernor)', + 'function upgradeFolioGovernance(address folio, address proxyAdmin, address oldOwnerGovernor, address oldTradingGovernor, address[] calldata ownerGuardians, address[] calldata tradingGuardians, bytes32 deploymentNonce) external returns (address newOwnerGovernor, address newTradingGovernor)', +]) + +const spellAddress = { + [ChainId.Mainnet]: zeroAddress, + [ChainId.Base]: zeroAddress, +} + +const queryParams = { + staleTime: 5 * 60 * 1000, + refetchInterval: 5 * 60 * 1000, +} as const + +const ProposeGovernanceSpell31032025 = () => { + const navigate = useNavigate() + const dtf = useAtomValue(indexDTFAtom) + const chainId = useAtomValue(chainIdAtom) + const spell = spellAddress[chainId] + + const { isProposeAllowed, isLoading } = useIsProposeAllowed() + const { data: qdOwnerGov } = useReadContract({ + abi: DTFIndexGovernance, + address: dtf?.ownerGovernance?.id, + functionName: 'quorumDenominator', + chainId, + query: queryParams, + }) + + const { data: qdStakingVaultGov } = useReadContract({ + abi: DTFIndexGovernance, + address: dtf?.stToken?.governance?.id, + functionName: 'quorumDenominator', + chainId, + query: queryParams, + }) + + const { + writeContract: wc0, + data: data0, + isPending: isPending0, + } = useWriteContract() + const { + writeContract: wc1, + data: data1, + isPending: isPending1, + } = useWriteContract() + + const { isSuccess: isSuccess0 } = useWaitForTransactionReceipt({ + hash: data0, + chainId, + }) + const { isSuccess: isSuccess1 } = useWaitForTransactionReceipt({ + hash: data1, + chainId, + }) + + const proposalAvailable = + !isLoading && + isProposeAllowed && + !!spell && + spell !== zeroAddress && + (qdOwnerGov === 100n || qdStakingVaultGov === 100n) + const isReady = dtf?.id && dtf?.proxyAdmin && dtf?.ownerGovernance?.id + + const handlePropose = () => { + if (!dtf || !spell || spell === zeroAddress) return + + if (dtf.ownerGovernance && dtf.tradingGovernance && qdOwnerGov === 100n) { + wc0({ + address: dtf.ownerGovernance.id, + abi: DTFIndexGovernance, + functionName: 'propose', + args: [ + [dtf.id, dtf.proxyAdmin, spell], + [0n, 0n, 0n], + [ + encodeFunctionData({ + abi: dtfIndexAbi, + functionName: 'grantRole', + args: ['0x00', spell], + }), + encodeFunctionData({ + abi: dtfAdminAbi, + functionName: 'transferOwnership', + args: [spell], + }), + encodeFunctionData({ + abi: spellAbi, + functionName: 'upgradeFolioGovernance', + args: [ + dtf.id, + dtf.proxyAdmin, + dtf.ownerGovernance.id, + dtf.tradingGovernance.id, + dtf.ownerGovernance.timelock.guardians, + dtf.tradingGovernance.timelock.guardians, + keccak256(toBytes(getCurrentTime())), + ], + }), + ], + 'Execute Governance Spell on Folio Governance', + ], + }) + } + + if ( + dtf.stToken && + dtf.stToken.governance?.id && + qdStakingVaultGov === 100n + ) { + wc1({ + address: dtf.stToken.governance.id, + abi: DTFIndexGovernance, + functionName: 'propose', + args: [ + [dtf.stToken.id, spell], + [0n, 0n], + [ + encodeFunctionData({ + abi: stakingVaultAbi, + functionName: 'transferOwnership', + args: [spell], + }), + encodeFunctionData({ + abi: spellAbi, + functionName: 'upgradeStakingVaultGovernance', + args: [ + dtf.stToken.id, + dtf.stToken.governance.id, + dtf.stToken.governance.timelock.guardians, + keccak256(toBytes(getCurrentTime())), + ], + }), + ], + 'Execute Governance Spell on Staking Vault', + ], + }) + } + } + + useEffect(() => { + if (isSuccess0 && isSuccess1) { + // Give some time for the proposal to be created on the subgraph + setTimeout(() => { + navigate(`../${ROUTES.GOVERNANCE}`) + }, 20000) // TODO: who knows if this works well!!! they can just refresh the page + } + }, [isSuccess0, isSuccess1]) + + if (!proposalAvailable) { + return null + } + + return ( +
+
+ +
+

Upgrade spell available

+

+ This upgrade spell fixes the proposal-threshold on all deployed + FolioGovernor contracts. Once this spell is approved and executed, + the proposal-threshold will be the correct % that was initially + intended at deployment (100x lower than what it currently is). +

+
+
+ +
+ ) +} + +export default ProposeGovernanceSpell31032025 From adf4fd20fa0881d3a8145ed4c8351f6df2f95b8b Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Tue, 22 Apr 2025 11:36:55 -0400 Subject: [PATCH 2/8] feat: add deployments --- .../components/propose-governance-spell-31-03-2025.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx index d23450947..736c57e60 100644 --- a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -12,6 +12,7 @@ import { useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { encodeFunctionData, + getAddress, keccak256, parseAbi, toBytes, @@ -32,8 +33,8 @@ const spellAbi = parseAbi([ ]) const spellAddress = { - [ChainId.Mainnet]: zeroAddress, - [ChainId.Base]: zeroAddress, + [ChainId.Mainnet]: getAddress('0x880F6ef00d13bAf60f3B99099451432F502EdA15'), + [ChainId.Base]: getAddress('0xE7FAa62c3F71f743F3a2Fc442393182F6B64f156'), } const queryParams = { From 04f3b627b02523d4f69d83379a37912805a9d970 Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Tue, 22 Apr 2025 14:59:50 -0400 Subject: [PATCH 3/8] feat: split buttons --- .../components/proposal-type-selection.tsx | 1 - .../propose-governance-spell-31-03-2025.tsx | 348 +++++++++++------- 2 files changed, 225 insertions(+), 124 deletions(-) diff --git a/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx b/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx index bc0439073..c309fa94d 100644 --- a/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx +++ b/src/views/index-dtf/governance/views/propose/components/proposal-type-selection.tsx @@ -94,7 +94,6 @@ const ProposalTypeSelection = () => {
-
diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx index 736c57e60..48a58140f 100644 --- a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -3,12 +3,12 @@ import dtfAdminAbi from '@/abis/dtf-admin-abi' import stakingVaultAbi from '@/abis/dtf-index-staking-vault' import DTFIndexGovernance from '@/abis/dtf-index-governance' import { Button } from '@/components/ui/button' -import { chainIdAtom } from '@/state/atoms' +import { chainIdAtom, INDEX_DTF_SUBGRAPH_URL } from '@/state/atoms' import { indexDTFAtom } from '@/state/dtf/atoms' -import { ROUTES } from '@/utils/constants' -import { useAtomValue } from 'jotai' +import { PROPOSAL_STATES, ROUTES } from '@/utils/constants' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { AlertCircle, Loader2 } from 'lucide-react' -import { useEffect } from 'react' +import { useCallback, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { encodeFunctionData, @@ -16,7 +16,7 @@ import { keccak256, parseAbi, toBytes, - zeroAddress, + toHex, } from 'viem' import { useReadContract, @@ -26,6 +26,10 @@ import { import { useIsProposeAllowed } from '../../../hooks/use-is-propose-allowed' import { ChainId } from '@/utils/chains' import { getCurrentTime } from '@/utils' +import { useQuery } from '@tanstack/react-query' +import request, { gql } from 'graphql-request' +import { governanceProposalsAtom, refetchTokenAtom } from '../../../atoms' +import { PartialProposal } from '@/lib/governance' const spellAbi = parseAbi([ 'function upgradeStakingVaultGovernance(address stakingVault, address oldGovernor, address[] calldata guardians, bytes32 deploymentNonce) external returns (address newGovernor)', @@ -37,18 +41,25 @@ const spellAddress = { [ChainId.Base]: getAddress('0xE7FAa62c3F71f743F3a2Fc442393182F6B64f156'), } +const UPGRADE_FOLIO_MESSAGE = 'Upgrade Folio Governance' +const UPGRADE_FOLIO_DAO_MESSAGE = 'Upgrade Folio DAO Governor' + const queryParams = { staleTime: 5 * 60 * 1000, refetchInterval: 5 * 60 * 1000, } as const -const ProposeGovernanceSpell31032025 = () => { - const navigate = useNavigate() +type SpellUpgradeProps = { + refetch: () => void +} + +const ProposeGovernanceSpell31032025Folio = ({ + refetch, +}: SpellUpgradeProps) => { const dtf = useAtomValue(indexDTFAtom) const chainId = useAtomValue(chainIdAtom) const spell = spellAddress[chainId] - const { isProposeAllowed, isLoading } = useIsProposeAllowed() const { data: qdOwnerGov } = useReadContract({ abi: DTFIndexGovernance, address: dtf?.ownerGovernance?.id, @@ -57,6 +68,114 @@ const ProposeGovernanceSpell31032025 = () => { query: queryParams, }) + const { writeContract, data, isPending } = useWriteContract() + + const { isSuccess } = useWaitForTransactionReceipt({ + hash: data, + chainId, + }) + + const isReady = + dtf?.id && + dtf?.proxyAdmin && + dtf?.ownerGovernance?.id && + dtf?.tradingGovernance?.id + const proposalAvailable = !!spell && qdOwnerGov === 100n + + const handlePropose = () => { + if (!dtf || !spell) return + if (!dtf.ownerGovernance || !dtf.tradingGovernance || qdOwnerGov !== 100n) + return + + writeContract({ + address: dtf.ownerGovernance.id, + abi: DTFIndexGovernance, + functionName: 'propose', + args: [ + [dtf.id, dtf.proxyAdmin, spell], + [0n, 0n, 0n], + [ + encodeFunctionData({ + abi: dtfIndexAbi, + functionName: 'grantRole', + args: [toHex('0x00', { size: 32 }), spell], + }), + encodeFunctionData({ + abi: dtfAdminAbi, + functionName: 'transferOwnership', + args: [spell], + }), + encodeFunctionData({ + abi: spellAbi, + functionName: 'upgradeFolioGovernance', + args: [ + dtf.id, + dtf.proxyAdmin, + dtf.ownerGovernance.id, + dtf.tradingGovernance.id, + dtf.ownerGovernance.timelock.guardians, + dtf.tradingGovernance.timelock.guardians, + keccak256(toBytes(getCurrentTime())), + ], + }), + ], + UPGRADE_FOLIO_MESSAGE, + ], + }) + } + + useEffect(() => { + if (isSuccess) { + // Give some time for the proposal to be created on the subgraph + setTimeout(() => { + refetch() + }, 10000) + } + }, [isSuccess]) + + if (!proposalAvailable) { + return null + } + + return ( +
+
+ +
+

+ Upgrade Folio Governance (1/2): +

+

+ This upgrade spell fixes the proposal-threshold on the Admin and + Basket FolioGovernor contracts that manage this DTF. Once this spell + is approved and executed, the proposal-threshold will be the correct + % that was initially intended at deployment. +

+
+
+ +
+ ) +} + +const ProposeGovernanceSpell31032025StakingVault = ({ + refetch, +}: SpellUpgradeProps) => { + const dtf = useAtomValue(indexDTFAtom) + const chainId = useAtomValue(chainIdAtom) + const spell = spellAddress[chainId] + const { data: qdStakingVaultGov } = useReadContract({ abi: DTFIndexGovernance, address: dtf?.stToken?.governance?.id, @@ -65,118 +184,57 @@ const ProposeGovernanceSpell31032025 = () => { query: queryParams, }) - const { - writeContract: wc0, - data: data0, - isPending: isPending0, - } = useWriteContract() - const { - writeContract: wc1, - data: data1, - isPending: isPending1, - } = useWriteContract() - - const { isSuccess: isSuccess0 } = useWaitForTransactionReceipt({ - hash: data0, - chainId, - }) - const { isSuccess: isSuccess1 } = useWaitForTransactionReceipt({ - hash: data1, + const { writeContract, data, isPending } = useWriteContract() + + const { isSuccess } = useWaitForTransactionReceipt({ + hash: data, chainId, }) - const proposalAvailable = - !isLoading && - isProposeAllowed && - !!spell && - spell !== zeroAddress && - (qdOwnerGov === 100n || qdStakingVaultGov === 100n) - const isReady = dtf?.id && dtf?.proxyAdmin && dtf?.ownerGovernance?.id + const isReady = dtf?.id && dtf?.proxyAdmin && dtf?.stToken?.governance?.id + const proposalAvailable = !!spell && qdStakingVaultGov === 100n const handlePropose = () => { - if (!dtf || !spell || spell === zeroAddress) return - - if (dtf.ownerGovernance && dtf.tradingGovernance && qdOwnerGov === 100n) { - wc0({ - address: dtf.ownerGovernance.id, - abi: DTFIndexGovernance, - functionName: 'propose', - args: [ - [dtf.id, dtf.proxyAdmin, spell], - [0n, 0n, 0n], - [ - encodeFunctionData({ - abi: dtfIndexAbi, - functionName: 'grantRole', - args: ['0x00', spell], - }), - encodeFunctionData({ - abi: dtfAdminAbi, - functionName: 'transferOwnership', - args: [spell], - }), - encodeFunctionData({ - abi: spellAbi, - functionName: 'upgradeFolioGovernance', - args: [ - dtf.id, - dtf.proxyAdmin, - dtf.ownerGovernance.id, - dtf.tradingGovernance.id, - dtf.ownerGovernance.timelock.guardians, - dtf.tradingGovernance.timelock.guardians, - keccak256(toBytes(getCurrentTime())), - ], - }), - ], - 'Execute Governance Spell on Folio Governance', - ], - }) - } + if (!dtf || !spell) return + if (!dtf?.stToken?.governance || qdStakingVaultGov !== 100n) return - if ( - dtf.stToken && - dtf.stToken.governance?.id && - qdStakingVaultGov === 100n - ) { - wc1({ - address: dtf.stToken.governance.id, - abi: DTFIndexGovernance, - functionName: 'propose', - args: [ - [dtf.stToken.id, spell], - [0n, 0n], - [ - encodeFunctionData({ - abi: stakingVaultAbi, - functionName: 'transferOwnership', - args: [spell], - }), - encodeFunctionData({ - abi: spellAbi, - functionName: 'upgradeStakingVaultGovernance', - args: [ - dtf.stToken.id, - dtf.stToken.governance.id, - dtf.stToken.governance.timelock.guardians, - keccak256(toBytes(getCurrentTime())), - ], - }), - ], - 'Execute Governance Spell on Staking Vault', + writeContract({ + address: dtf.stToken.governance.id, + abi: DTFIndexGovernance, + functionName: 'propose', + args: [ + [dtf.stToken.id, spell], + [0n, 0n], + [ + encodeFunctionData({ + abi: stakingVaultAbi, + functionName: 'transferOwnership', + args: [spell], + }), + encodeFunctionData({ + abi: spellAbi, + functionName: 'upgradeStakingVaultGovernance', + args: [ + dtf.stToken.id, + dtf.stToken.governance.id, + dtf.stToken.governance.timelock.guardians, + keccak256(toBytes(getCurrentTime())), + ], + }), ], - }) - } + UPGRADE_FOLIO_DAO_MESSAGE, + ], + }) } useEffect(() => { - if (isSuccess0 && isSuccess1) { + if (isSuccess) { // Give some time for the proposal to be created on the subgraph setTimeout(() => { - navigate(`../${ROUTES.GOVERNANCE}`) - }, 20000) // TODO: who knows if this works well!!! they can just refresh the page + refetch() + }, 10000) } - }, [isSuccess0, isSuccess1]) + }, [isSuccess]) if (!proposalAvailable) { return null @@ -187,34 +245,78 @@ const ProposeGovernanceSpell31032025 = () => {
-

Upgrade spell available

+

+ Upgrade Folio DAO Governor (2/2): +

- This upgrade spell fixes the proposal-threshold on all deployed - FolioGovernor contracts. Once this spell is approved and executed, - the proposal-threshold will be the correct % that was initially - intended at deployment (100x lower than what it currently is). + This upgrade spell fixes the proposal-threshold on the FolioGovernor + contract that administers the DAO (StakingVault) relevant to this + DTF. Once this spell is approved and executed, the + proposal-threshold will be the correct % that was initially intended + at deployment.

) } -export default ProposeGovernanceSpell31032025 +const validProposalExists = ( + proposals: PartialProposal[], + description: string +): boolean => { + const states = [ + PROPOSAL_STATES.PENDING, + PROPOSAL_STATES.ACTIVE, + PROPOSAL_STATES.SUCCEEDED, + PROPOSAL_STATES.QUEUED, + PROPOSAL_STATES.EXECUTED, + ] + return proposals.some( + (p) => p.description == description && states.includes(p.state) + ) +} + +export default function ProposeGovernanceSpell31032025() { + const { isProposeAllowed } = useIsProposeAllowed() + const proposals = useAtomValue(governanceProposalsAtom) + const setRefetchToken = useSetAtom(refetchTokenAtom) + + const refetch = useCallback(() => { + setRefetchToken(getCurrentTime()) + }, [setRefetchToken]) + + if (!isProposeAllowed || !proposals) return null + + const existsFolioUpgrade = validProposalExists( + proposals, + UPGRADE_FOLIO_MESSAGE + ) + const existsFolioDaoUpgrade = validProposalExists( + proposals, + UPGRADE_FOLIO_DAO_MESSAGE + ) + + return ( + <> + {!existsFolioUpgrade && ( + + )} + {!existsFolioDaoUpgrade && ( + + )} + + ) +} From db76bacb6511089772f5b54435631f15372e29a0 Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Tue, 22 Apr 2025 16:36:24 -0400 Subject: [PATCH 4/8] feat: fix spell --- .../confirm-manual-deploy-button.tsx | 10 ++-- .../propose-governance-spell-31-03-2025.tsx | 46 +++++++++++++++---- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/views/index-dtf/deploy/steps/confirm-deploy/manual/components/confirm-manual-deploy-button.tsx b/src/views/index-dtf/deploy/steps/confirm-deploy/manual/components/confirm-manual-deploy-button.tsx index 57e0212e4..775a06854 100644 --- a/src/views/index-dtf/deploy/steps/confirm-deploy/manual/components/confirm-manual-deploy-button.tsx +++ b/src/views/index-dtf/deploy/steps/confirm-deploy/manual/components/confirm-manual-deploy-button.tsx @@ -174,9 +174,8 @@ const txAtom = atom< const ownerGovernanceConfig: GovernanceConfig = { votingDelay: Math.floor((formData.governanceVotingDelay || 0) * 86400), votingPeriod: Math.floor((formData.governanceVotingPeriod || 0) * 86400), - proposalThreshold: parseEther( - (formData.governanceVotingThreshold || 0).toString() - ), + proposalThreshold: + parseEther((formData.governanceVotingThreshold || 0).toString()) / 100n, quorumPercent: BigInt(Math.floor(formData.governanceVotingQuorum || 0)), timelockDelay: BigInt( Math.floor((formData.governanceExecutionDelay || 0) * 86400) @@ -187,9 +186,8 @@ const txAtom = atom< const tradingGovernanceConfig: GovernanceConfig = { votingDelay: Math.floor((formData.basketVotingDelay || 0) * 3600), votingPeriod: Math.floor((formData.basketVotingPeriod || 0) * 3600), - proposalThreshold: parseEther( - (formData.basketVotingThreshold || 0).toString() - ), + proposalThreshold: + parseEther((formData.basketVotingThreshold || 0).toString()) / 100n, quorumPercent: BigInt(Math.floor(formData.basketVotingQuorum || 0)), timelockDelay: BigInt( Math.floor((formData.basketExecutionDelay || 0) * 3600) diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx index 48a58140f..a857596bc 100644 --- a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -13,6 +13,7 @@ import { useNavigate } from 'react-router-dom' import { encodeFunctionData, getAddress, + isAddressEqual, keccak256, parseAbi, toBytes, @@ -29,7 +30,7 @@ import { getCurrentTime } from '@/utils' import { useQuery } from '@tanstack/react-query' import request, { gql } from 'graphql-request' import { governanceProposalsAtom, refetchTokenAtom } from '../../../atoms' -import { PartialProposal } from '@/lib/governance' +import { getProposalState, PartialProposal } from '@/lib/governance' const spellAbi = parseAbi([ 'function upgradeStakingVaultGovernance(address stakingVault, address oldGovernor, address[] calldata guardians, bytes32 deploymentNonce) external returns (address newGovernor)', @@ -87,6 +88,16 @@ const ProposeGovernanceSpell31032025Folio = ({ if (!dtf.ownerGovernance || !dtf.tradingGovernance || qdOwnerGov !== 100n) return + const oldOwnerGovernor = dtf.ownerGovernance.id + const oldTradingGovernor = dtf.tradingGovernance.id + + const ownerGuardians = dtf.ownerGovernance.timelock.guardians.filter( + (guardian) => !isAddressEqual(guardian, oldOwnerGovernor) + ) + const tradingGuardians = dtf.ownerGovernance.timelock.guardians.filter( + (guardian) => !isAddressEqual(guardian, oldTradingGovernor) + ) + writeContract({ address: dtf.ownerGovernance.id, abi: DTFIndexGovernance, @@ -111,10 +122,10 @@ const ProposeGovernanceSpell31032025Folio = ({ args: [ dtf.id, dtf.proxyAdmin, - dtf.ownerGovernance.id, - dtf.tradingGovernance.id, - dtf.ownerGovernance.timelock.guardians, - dtf.tradingGovernance.timelock.guardians, + oldOwnerGovernor, + oldTradingGovernor, + ownerGuardians, + tradingGuardians, keccak256(toBytes(getCurrentTime())), ], }), @@ -198,6 +209,11 @@ const ProposeGovernanceSpell31032025StakingVault = ({ if (!dtf || !spell) return if (!dtf?.stToken?.governance || qdStakingVaultGov !== 100n) return + const oldGovernor = dtf.stToken.governance.id + const guardians = dtf.stToken.governance.timelock.guardians.filter( + (guardian) => !isAddressEqual(guardian, oldGovernor) + ) + writeContract({ address: dtf.stToken.governance.id, abi: DTFIndexGovernance, @@ -216,8 +232,8 @@ const ProposeGovernanceSpell31032025StakingVault = ({ functionName: 'upgradeStakingVaultGovernance', args: [ dtf.stToken.id, - dtf.stToken.governance.id, - dtf.stToken.governance.timelock.guardians, + oldGovernor, + guardians, keccak256(toBytes(getCurrentTime())), ], }), @@ -284,9 +300,19 @@ const validProposalExists = ( PROPOSAL_STATES.QUEUED, PROPOSAL_STATES.EXECUTED, ] - return proposals.some( - (p) => p.description == description && states.includes(p.state) - ) + return proposals.some((p) => { + if (p.description !== description) { + return false + } + + const pState = getProposalState(p) + + if (pState.state === PROPOSAL_STATES.EXPIRED) { + return false + } + + return states.includes(pState.state) + }) } export default function ProposeGovernanceSpell31032025() { From 6a8a370993230225d42adedc9184a4354b5900de Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Tue, 22 Apr 2025 17:26:50 -0400 Subject: [PATCH 5/8] fix: role --- .../propose-governance-spell-31-03-2025.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx index a857596bc..cc8be4be7 100644 --- a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -3,21 +3,20 @@ import dtfAdminAbi from '@/abis/dtf-admin-abi' import stakingVaultAbi from '@/abis/dtf-index-staking-vault' import DTFIndexGovernance from '@/abis/dtf-index-governance' import { Button } from '@/components/ui/button' -import { chainIdAtom, INDEX_DTF_SUBGRAPH_URL } from '@/state/atoms' +import { chainIdAtom } from '@/state/atoms' import { indexDTFAtom } from '@/state/dtf/atoms' -import { PROPOSAL_STATES, ROUTES } from '@/utils/constants' -import { useAtom, useAtomValue, useSetAtom } from 'jotai' +import { PROPOSAL_STATES } from '@/utils/constants' +import { useAtomValue, useSetAtom } from 'jotai' import { AlertCircle, Loader2 } from 'lucide-react' import { useCallback, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' import { encodeFunctionData, getAddress, isAddressEqual, keccak256, + pad, parseAbi, toBytes, - toHex, } from 'viem' import { useReadContract, @@ -27,8 +26,6 @@ import { import { useIsProposeAllowed } from '../../../hooks/use-is-propose-allowed' import { ChainId } from '@/utils/chains' import { getCurrentTime } from '@/utils' -import { useQuery } from '@tanstack/react-query' -import request, { gql } from 'graphql-request' import { governanceProposalsAtom, refetchTokenAtom } from '../../../atoms' import { getProposalState, PartialProposal } from '@/lib/governance' @@ -109,7 +106,7 @@ const ProposeGovernanceSpell31032025Folio = ({ encodeFunctionData({ abi: dtfIndexAbi, functionName: 'grantRole', - args: [toHex('0x00', { size: 32 }), spell], + args: [pad('0x0', { size: 32 }), spell], }), encodeFunctionData({ abi: dtfAdminAbi, From eea2693856b47ed9120ee27094134dc1239847f3 Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Tue, 22 Apr 2025 17:53:32 -0400 Subject: [PATCH 6/8] feat: add proposal decoding --- .../components/governance-proposal-preview.tsx | 17 +++++++++++++++++ .../propose-governance-spell-31-03-2025.tsx | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/views/index-dtf/governance/components/governance-proposal-preview.tsx b/src/views/index-dtf/governance/components/governance-proposal-preview.tsx index 42ccad15f..608b0475e 100644 --- a/src/views/index-dtf/governance/components/governance-proposal-preview.tsx +++ b/src/views/index-dtf/governance/components/governance-proposal-preview.tsx @@ -5,6 +5,7 @@ import { useAtomValue } from 'jotai' import { Address, decodeFunctionData, getAbiItem } from 'viem' import dtfIndexAbi from '@/abis/dtf-index-abi' +import dtfAdminAbi from '@/abis/dtf-admin-abi' import dtfIndexGovernance from '@/abis/dtf-index-governance' import dtfIndexStakingVault from '@/abis/dtf-index-staking-vault' import { Button } from '@/components/ui/button' @@ -26,6 +27,10 @@ import { Abi, Hex } from 'viem' import BasketProposalPreview from '../views/propose/basket/components/proposal-basket-preview' import RawCallPreview from './proposal-preview/raw-call-preview' import TokenRewardPreview from './proposal-preview/token-reward-preview' +import { + spellAbi as governanceSpell_31_03_2025Abi, + spellAddress as governanceSpell_31_03_2025Address, +} from '../views/propose/components/propose-governance-spell-31-03-2025' const dtfAbiMapppingAtom = atom((get) => { const dtf = get(indexDTFAtom) @@ -34,6 +39,7 @@ const dtfAbiMapppingAtom = atom((get) => { const abiMapping: Record = { [dtf.id.toLowerCase()]: dtfIndexAbi, + [dtf.proxyAdmin.toLowerCase()]: dtfAdminAbi, } if (dtf.ownerGovernance) { @@ -52,6 +58,11 @@ const dtfAbiMapppingAtom = atom((get) => { } } + if (governanceSpell_31_03_2025Address[dtf.chainId]) { + abiMapping[governanceSpell_31_03_2025Address[dtf.chainId].toLowerCase()] = + governanceSpell_31_03_2025Abi + } + return abiMapping }) @@ -62,6 +73,7 @@ const dtfContractAliasAtom = atom((get) => { const aliasMapping: Record = { [dtf.id.toLowerCase()]: 'Folio', + [dtf.proxyAdmin.toLowerCase()]: 'ProxyAdmin', } if (dtf.ownerGovernance) { @@ -80,6 +92,11 @@ const dtfContractAliasAtom = atom((get) => { } } + if (governanceSpell_31_03_2025Address[dtf.chainId]) { + aliasMapping[governanceSpell_31_03_2025Address[dtf.chainId].toLowerCase()] = + 'GovernanceSpell_31_03_2025' + } + return aliasMapping }) diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx index cc8be4be7..6ff174cb6 100644 --- a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -29,12 +29,12 @@ import { getCurrentTime } from '@/utils' import { governanceProposalsAtom, refetchTokenAtom } from '../../../atoms' import { getProposalState, PartialProposal } from '@/lib/governance' -const spellAbi = parseAbi([ +export const spellAbi = parseAbi([ 'function upgradeStakingVaultGovernance(address stakingVault, address oldGovernor, address[] calldata guardians, bytes32 deploymentNonce) external returns (address newGovernor)', 'function upgradeFolioGovernance(address folio, address proxyAdmin, address oldOwnerGovernor, address oldTradingGovernor, address[] calldata ownerGuardians, address[] calldata tradingGuardians, bytes32 deploymentNonce) external returns (address newOwnerGovernor, address newTradingGovernor)', ]) -const spellAddress = { +export const spellAddress = { [ChainId.Mainnet]: getAddress('0x880F6ef00d13bAf60f3B99099451432F502EdA15'), [ChainId.Base]: getAddress('0xE7FAa62c3F71f743F3a2Fc442393182F6B64f156'), } @@ -91,7 +91,7 @@ const ProposeGovernanceSpell31032025Folio = ({ const ownerGuardians = dtf.ownerGovernance.timelock.guardians.filter( (guardian) => !isAddressEqual(guardian, oldOwnerGovernor) ) - const tradingGuardians = dtf.ownerGovernance.timelock.guardians.filter( + const tradingGuardians = dtf.tradingGovernance.timelock.guardians.filter( (guardian) => !isAddressEqual(guardian, oldTradingGovernor) ) From 37d8ecd2ebd9f6e399ae747e1ec50c200ffb12f3 Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Mon, 28 Apr 2025 13:32:47 -0400 Subject: [PATCH 7/8] feat: disable sv spell --- .../propose/components/propose-governance-spell-31-03-2025.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx index 6ff174cb6..d1d78b7e2 100644 --- a/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx +++ b/src/views/index-dtf/governance/views/propose/components/propose-governance-spell-31-03-2025.tsx @@ -312,6 +312,7 @@ const validProposalExists = ( }) } +// TODO(jg): Enable Staking Vault spell when DAO gov is fixed export default function ProposeGovernanceSpell31032025() { const { isProposeAllowed } = useIsProposeAllowed() const proposals = useAtomValue(governanceProposalsAtom) @@ -337,7 +338,7 @@ export default function ProposeGovernanceSpell31032025() { {!existsFolioUpgrade && ( )} - {!existsFolioDaoUpgrade && ( + {!existsFolioDaoUpgrade && false && ( )} From a1a819f3977a388cb7e0159600842a872ef5f4f3 Mon Sep 17 00:00:00 2001 From: Jorge Galat Date: Mon, 28 Apr 2025 17:38:26 -0400 Subject: [PATCH 8/8] feat: track legacy gov proposals (#686) * feat: get legacy gov proposals * fix: build --- src/hooks/useIndexDTF.ts | 6 ++ src/types/index.ts | 3 + src/views/index-dtf/governance/updater.tsx | 86 +++++-------------- .../portfolio/sidebar/components/actions.tsx | 1 + 4 files changed, 33 insertions(+), 63 deletions(-) diff --git a/src/hooks/useIndexDTF.ts b/src/hooks/useIndexDTF.ts index 91a347bcf..c6d78c6b8 100644 --- a/src/hooks/useIndexDTF.ts +++ b/src/hooks/useIndexDTF.ts @@ -33,6 +33,7 @@ type DTFQueryResponse = { executionDelay: number } } + legacyAdmins: Address[] tradingGovernance?: { id: Address votingDelay: number @@ -45,6 +46,7 @@ type DTFQueryResponse = { executionDelay: number } } + legacyAuctionApprovers: Address[] token: { id: Address name: string @@ -78,6 +80,7 @@ type DTFQueryResponse = { executionDelay: number } } + legacyGovernance: Address[] rewards: { rewardToken: { address: Address @@ -128,6 +131,7 @@ const dtfQuery = gql` executionDelay } } + legacyAdmins tradingGovernance { id votingDelay @@ -140,6 +144,7 @@ const dtfQuery = gql` executionDelay } } + legacyAuctionApprovers token { id name @@ -173,6 +178,7 @@ const dtfQuery = gql` executionDelay } } + legacyGovernance rewards(where: { active: true }) { rewardToken { address diff --git a/src/types/index.ts b/src/types/index.ts index ceb0e38ee..9b169c139 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -292,6 +292,7 @@ export type IndexDTF = { executionDelay: number } } + legacyAdmins: Address[] tradingGovernance?: { id: Address votingDelay: number @@ -304,6 +305,7 @@ export type IndexDTF = { executionDelay: number } } + legacyAuctionApprovers: Address[] token: { id: Address name: string @@ -337,6 +339,7 @@ export type IndexDTF = { executionDelay: number } } + legacyGovernance: Address[] rewardTokens: Token[] } totalRevenue: number diff --git a/src/views/index-dtf/governance/updater.tsx b/src/views/index-dtf/governance/updater.tsx index 50c4d674a..a11b2b9b7 100644 --- a/src/views/index-dtf/governance/updater.tsx +++ b/src/views/index-dtf/governance/updater.tsx @@ -9,6 +9,10 @@ import { Address, formatEther } from 'viem' import { indexGovernanceOverviewAtom, refetchTokenAtom } from './atoms' type Response = { + governances: { + proposals: PartialProposal[] + proposalCount: number + }[] ownerGovernance: { proposals: PartialProposal[] proposalCount: number @@ -36,57 +40,9 @@ type Response = { } const query = gql` - query getGovernanceStats( - $ownerGovernance: String! - $tradingGovernance: String! - $vaultGovernance: String! - $stToken: String! - ) { - ownerGovernance: governance(id: $ownerGovernance) { - proposals { - id - description - creationTime - state - forWeightedVotes - abstainWeightedVotes - againstWeightedVotes - executionETA - quorumVotes - voteStart - voteEnd - executionBlock - executionTime - creationBlock - proposer { - address - } - } - proposalCount - } - tradingGovernance: governance(id: $tradingGovernance) { - proposals { - id - description - creationTime - state - forWeightedVotes - abstainWeightedVotes - againstWeightedVotes - executionETA - executionTime - quorumVotes - voteStart - voteEnd - executionBlock - creationBlock - proposer { - address - } - } - proposalCount - } - vaultGovernance: governance(id: $vaultGovernance) { + query getGovernanceStats($governanceIds: [String!]!, $stToken: String!) { + governances(where: { id_in: $governanceIds }) { + id proposals { id description @@ -109,6 +65,7 @@ const query = gql` proposalCount } stakingToken(id: $stToken) { + id totalDelegates token { totalSupply @@ -142,23 +99,26 @@ const Updater = () => { INDEX_DTF_SUBGRAPH_URL[chainId], query, { - ownerGovernance: dtf?.ownerGovernance?.id ?? '', - tradingGovernance: dtf?.tradingGovernance?.id ?? '', - vaultGovernance: dtf?.stToken?.governance?.id ?? '', + governanceIds: [ + dtf?.ownerGovernance?.id, + ...(dtf?.legacyAdmins || []), + dtf?.tradingGovernance?.id, + ...(dtf?.legacyAuctionApprovers || []), + dtf?.stToken?.governance?.id, + ...(dtf?.stToken?.legacyGovernance || []), + ], stToken: dtf?.stToken?.id ?? '', } ) return { - proposals: [ - ...(data.ownerGovernance.proposals ?? []), - ...(data.tradingGovernance?.proposals ?? []), - ...(data.vaultGovernance?.proposals ?? []), - ].sort((a, b) => b.creationTime - a.creationTime), - proposalCount: - +data.ownerGovernance.proposalCount + - +(data.tradingGovernance?.proposalCount ?? 0) + - +(data.vaultGovernance?.proposalCount ?? 0), + proposals: data.governances + .flatMap((g) => g.proposals) + .sort((a, b) => b.creationTime - a.creationTime), + proposalCount: data.governances.reduce( + (x, y) => x + Number(y.proposalCount), + 0 + ), delegates: data.stakingToken?.delegates ?? [], delegatesCount: +(data.stakingToken?.totalDelegates ?? 0), voteSupply: +formatEther(data.stakingToken?.token.totalSupply ?? 0n), diff --git a/src/views/portfolio/sidebar/components/actions.tsx b/src/views/portfolio/sidebar/components/actions.tsx index 83dfb31c6..c6c10edee 100644 --- a/src/views/portfolio/sidebar/components/actions.tsx +++ b/src/views/portfolio/sidebar/components/actions.tsx @@ -265,6 +265,7 @@ export const ModifyLockAction = ({ stToken }: { stToken: StakingToken }) => { address: stToken.underlying.address, decimals: stToken.underlying.decimals, }, + legacyGovernance: [], chainId: stToken.chainId, }