security modal actions (#409)

This commit is contained in:
Adrian Brzeziński 2024-04-04 12:11:35 +02:00 committed by GitHub
parent cafd08189f
commit 478ac38c9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 381 additions and 12 deletions

View File

@ -0,0 +1,228 @@
import { useCallback, useState } from 'react'
import { ModalProps } from '../../types/modal'
import Modal from '../shared/Modal'
import mangoStore from '@store/mangoStore'
import { useWallet } from '@solana/wallet-adapter-react'
import { Bank, Group } from '@blockworks-foundation/mango-v4'
import { AccountMeta, PublicKey, Transaction } from '@solana/web3.js'
import {
MANGO_DAO_SECURITY_WALLET_GOVERNANCE,
MANGO_GOVERNANCE_PROGRAM,
MANGO_SECURITY_COUNCIL_MINT,
MANGO_SECURITY_COUNCIL_WALLET,
MANGO_SECURITY_REALM_PK,
} from 'utils/governance/constants'
import { createProposal } from 'utils/governance/instructions/createProposal'
import { notify } from 'utils/notifications'
import Button from '@components/shared/Button'
import { getAllProposals } from '@solana/spl-governance'
import useSecurityCouncilDao from 'hooks/useSecurityCouncilDao'
import Loading from '@components/shared/Loading'
import Select from '@components/forms/Select'
import { abbreviateAddress } from 'utils/formatting'
const SecurityCouncilModal = ({
isOpen,
onClose,
bank,
group,
}: ModalProps & {
bank: Bank
group: Group
}) => {
const client = mangoStore((s) => s.client)
//do not deconstruct wallet is used for anchor to sign
const wallet = useWallet()
const connection = mangoStore((s) => s.connection)
const fee = mangoStore((s) => s.priorityFee)
const { queries, currentDelegate, setDelegate, currentVoter } =
useSecurityCouncilDao()
const [proposing, setProposing] = useState(false)
//0 no borrows
//1 no borrows no deposits
const proposeReduceOnly = useCallback(
async (bank: Bank, mode: number) => {
if (!currentVoter) {
notify({
type: 'error',
description: 'No vote record found',
title: 'No vote record found',
})
}
const mintInfo = group!.mintInfosMapByTokenIndex.get(bank.tokenIndex)!
setProposing(true)
const proposalTx = []
const walletSigner = wallet as never
const ix = await client!.program.methods
.tokenEdit(
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
false,
false,
mode,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
false,
false,
null,
null,
null,
null,
null,
null,
)
.accounts({
group: group!.publicKey,
oracle: bank.oracle,
admin: MANGO_SECURITY_COUNCIL_WALLET,
mintInfo: mintInfo.publicKey,
fallbackOracle: bank.fallbackOracle,
})
.remainingAccounts([
{
pubkey: bank.publicKey,
isWritable: true,
isSigner: false,
} as AccountMeta,
])
.instruction()
proposalTx.push(ix)
try {
setProposing(true)
const simTransaction = new Transaction({ feePayer: wallet.publicKey })
simTransaction.add(...proposalTx)
const simulation = await connection.simulateTransaction(simTransaction)
if (!simulation.value.err) {
const proposals = await getAllProposals(
connection,
MANGO_GOVERNANCE_PROGRAM,
MANGO_SECURITY_REALM_PK,
)
const index = proposals ? Object.values(proposals).length : 0
const proposalAddress = await createProposal(
connection,
client,
walletSigner,
MANGO_DAO_SECURITY_WALLET_GOVERNANCE,
currentVoter!,
`Edit ${bank.name} token`,
'',
index,
proposalTx,
null,
fee,
MANGO_SECURITY_COUNCIL_MINT,
MANGO_SECURITY_REALM_PK,
)
window.open(
`https://dao.mango.markets/dao/AQbsV8b3Yv3UHUmd62hw9vFmNTHgBTdiLfaWzwmVfXB2/proposal/${proposalAddress.toBase58()}`,
'_blank',
)
} else {
throw simulation.value.logs
}
} catch (e) {
console.log(e)
notify({
title: 'Error during proposal creation',
description: `${e}`,
type: 'error',
})
}
setProposing(false)
},
[client, connection, currentVoter, fee, group, wallet],
)
return (
<Modal
panelClassNames="!max-w-[300px] min-h-[300px]"
isOpen={isOpen}
onClose={onClose}
>
<h3 className="mb-6">Token: {bank.name}</h3>
{queries.data?.delegatedAccounts.length ? (
<div className="flex items-start">
<p className="mr-2">delegate</p>
<Select
value={
currentDelegate
? abbreviateAddress(
new PublicKey(
queries.data.delegatedAccounts.find(
(x) => x.pubkey.toBase58() === currentDelegate,
)!.account.governingTokenOwner!,
),
)
: 'none'
}
onChange={(selected) => {
setDelegate(selected)
}}
className="w-full"
>
<Select.Option value={''}>
<div className="flex w-full items-center justify-between">
none
</div>
</Select.Option>
{queries.data.delegatedAccounts.map((x) => (
<Select.Option
key={x.pubkey.toBase58()}
value={x.pubkey.toBase58()}
>
<div className="flex w-full items-center justify-between">
{abbreviateAddress(x.account.governingTokenOwner)}
</div>
</Select.Option>
))}
</Select>
</div>
) : null}
<div className="mt-6 space-y-3">
<Button onClick={() => proposeReduceOnly(bank, 1)} disabled={proposing}>
{proposing ? (
<Loading className="w-5"></Loading>
) : (
'No borrows no deposits'
)}
</Button>
<Button onClick={() => proposeReduceOnly(bank, 2)} disabled={proposing}>
{proposing ? <Loading className="w-5"></Loading> : 'No borrows'}
</Button>
</div>
</Modal>
)
}
export default SecurityCouncilModal

View File

@ -0,0 +1,98 @@
import { PublicKey } from '@metaplex-foundation/js'
import {
ProgramAccount,
TokenOwnerRecord,
getGovernanceAccounts,
getTokenOwnerRecord,
getTokenOwnerRecordAddress,
pubkeyFilter,
} from '@solana/spl-governance'
import { useWallet } from '@solana/wallet-adapter-react'
import { Connection } from '@solana/web3.js'
import mangoStore from '@store/mangoStore'
import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import {
MANGO_GOVERNANCE_PROGRAM,
MANGO_SECURITY_COUNCIL_MINT,
MANGO_SECURITY_REALM_PK,
} from 'utils/governance/constants'
export default function useSecurityCouncilDao() {
const wallet = useWallet()
const connection = mangoStore((s) => s.connection)
const [currentDelegate, setDelegate] = useState<string>('')
const [currentVoter, setVoter] =
useState<ProgramAccount<TokenOwnerRecord> | null>(null)
const query = useQuery(
['security-council', wallet.publicKey, connection.rpcEndpoint],
async () => {
const walletPk = wallet.publicKey!
const delegatedAccounts = await fetchDelegatedAccounts(
walletPk,
connection,
MANGO_GOVERNANCE_PROGRAM,
)
const tokenOwnerRecordPk = await getTokenOwnerRecordAddress(
MANGO_GOVERNANCE_PROGRAM,
MANGO_SECURITY_REALM_PK,
MANGO_SECURITY_COUNCIL_MINT,
walletPk,
)
return {
delegatedAccounts: delegatedAccounts.filter((x) =>
x.account.realm.equals(MANGO_SECURITY_REALM_PK),
),
tokenOwnerRecordPk,
}
},
{
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 2,
refetchOnWindowFocus: false,
enabled: !!wallet.publicKey,
},
)
useEffect(() => {
const updateVoter = async () => {
setDelegate(currentDelegate)
try {
const tokenOwnerRecord = await getTokenOwnerRecord(
connection,
currentDelegate
? new PublicKey(currentDelegate)
: query.data!.tokenOwnerRecordPk,
)
setVoter(tokenOwnerRecord)
// eslint-disable-next-line no-empty
} catch (e) {}
}
if (query.data?.tokenOwnerRecordPk || currentDelegate) {
updateVoter()
}
}, [query.data?.tokenOwnerRecordPk, currentDelegate])
return {
currentDelegate,
currentVoter,
setDelegate,
queries: query,
}
}
const fetchDelegatedAccounts = async (
wallet: PublicKey,
connection: Connection,
programId: PublicKey,
) => {
const governanceDelegateOffset = 122
const accounts = await getGovernanceAccounts(
connection,
programId,
TokenOwnerRecord,
[pubkeyFilter(governanceDelegateOffset, wallet)!],
)
return accounts
}

View File

@ -47,6 +47,7 @@ import {
getProposedKey,
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools'
import Tooltip from '@components/shared/Tooltip'
import SecurityCouncilModal from '@components/modals/SecurityCouncilModal'
dayjs.extend(relativeTime)
@ -703,6 +704,9 @@ const BankDisclosure = ({
const [openedSuggestedModal, setOpenedSuggestedModal] = useState<
string | null
>(null)
const [securityModalOpen, setSecurityModalOpen] = useState<string | null>(
null,
)
const { currentTier, suggestedTierKey } = getSuggestedAndCurrentTier(
bank,
@ -956,6 +960,25 @@ const BankDisclosure = ({
</Button>
</div>
) : null}
<div className="my-3 flex">
<Button
className="ml-auto"
onClick={() => setSecurityModalOpen(bank.mint.toBase58())}
size="small"
>
Security council
{securityModalOpen === bank.mint.toBase58() && (
<SecurityCouncilModal
group={group}
bank={bank}
isOpen={securityModalOpen === bank.mint.toBase58()}
onClose={() => setSecurityModalOpen(null)}
></SecurityCouncilModal>
)}
</Button>
</div>
<KeyValuePair
label="Mint"
value={<ExplorerLink address={bank.mint.toString()} />}

View File

@ -3,6 +3,17 @@ import { PublicKey } from '@solana/web3.js'
export const MANGO_REALM_PK = new PublicKey(
'DPiH3H3c7t47BMxqTxLsuPQpEC6Kne8GA9VXbxpnZxFE',
)
export const MANGO_SECURITY_REALM_PK = new PublicKey(
'AQbsV8b3Yv3UHUmd62hw9vFmNTHgBTdiLfaWzwmVfXB2',
)
export const MANGO_SECURITY_COUNCIL_MINT = new PublicKey(
'HCZ4rgExnFkACvdbtnuMKFRn5ZELuQYyLKWyPuwRaCGS',
)
export const MANGO_SECURITY_COUNCIL_WALLET = new PublicKey(
'B89yKk4eaQqxSFnDQStTpmCqSdxofqk2RaaacCPVxuvD',
)
export const MANGO_GOVERNANCE_PROGRAM = new PublicKey(
'GqTPL6qRf5aUuqscLh8Rg2HTxPUXfhhAXDptTLhp1t2J',
)
@ -20,6 +31,9 @@ export const MANGO_MINT = new PublicKey(
export const MANGO_DAO_WALLET_GOVERNANCE = new PublicKey(
'7zGXUAeUkY9pEGfApsY26amibvqsf2dmty1cbtxHdfaQ',
)
export const MANGO_DAO_SECURITY_WALLET_GOVERNANCE = new PublicKey(
'GChgcmMjyPH7B3sHY6ZoHGxXJhW2u5TkwQLxeDoU5JeF',
)
export const MANGO_DAO_FAST_LISTING_GOVERNANCE = new PublicKey(
'7D6tGmaMyC8i73Q8X2Fec2S1Zb5rkyai6pctdMqHpHWT',

View File

@ -39,14 +39,17 @@ export const createProposal = async (
descriptionLink: string,
proposalIndex: number,
proposalInstructions: TransactionInstruction[],
client: VsrClient,
client: VsrClient | null,
fee: number,
mint?: PublicKey,
realm?: PublicKey,
) => {
const instructions: TransactionInstruction[] = []
const walletPk = wallet.publicKey!
const governanceAuthority = walletPk
const signatory = walletPk
const payer = walletPk
let vsrVoterWeightPk: PublicKey | undefined = undefined
// Changed this because it is misbehaving on my local validator setup.
const programVersion = await getGovernanceProgramVersion(
@ -59,30 +62,33 @@ export const createProposal = async (
const options = ['Approve']
const useDenyOption = true
const { updateVoterWeightRecordIx, voterWeightPk } =
await updateVoterWeightRecord(
client,
tokenOwnerRecord.account.governingTokenOwner,
)
instructions.push(updateVoterWeightRecordIx)
if (client) {
const { updateVoterWeightRecordIx, voterWeightPk } =
await updateVoterWeightRecord(
client,
tokenOwnerRecord.account.governingTokenOwner,
)
vsrVoterWeightPk = voterWeightPk
instructions.push(updateVoterWeightRecordIx)
}
const proposalAddress = await withCreateProposal(
instructions,
MANGO_GOVERNANCE_PROGRAM,
programVersion,
MANGO_REALM_PK,
realm || MANGO_REALM_PK,
governance,
tokenOwnerRecord.pubkey,
name,
descriptionLink,
new PublicKey(MANGO_MINT),
mint || new PublicKey(MANGO_MINT),
governanceAuthority,
proposalIndex,
voteType,
options,
useDenyOption,
payer,
voterWeightPk,
vsrVoterWeightPk,
)
await withAddSignatory(
@ -125,7 +131,7 @@ export const createProposal = async (
insertInstructions, // SingOff proposal needs to be executed after inserting instructions hence we add it to insertInstructions
MANGO_GOVERNANCE_PROGRAM,
programVersion,
MANGO_REALM_PK,
realm || MANGO_REALM_PK,
governance,
proposalAddress,
signatory,
@ -137,6 +143,7 @@ export const createProposal = async (
const transactions: Transaction[] = []
const latestBlockhash = await connection.getLatestBlockhash('processed')
for (const chunk of txChunks) {
const tx = new Transaction()
tx.add(createComputeBudgetIx(fee))
@ -146,7 +153,6 @@ export const createProposal = async (
tx.feePayer = payer
transactions.push(tx)
}
const signedTransactions = await wallet.signAllTransactions(transactions)
for (const tx of signedTransactions) {
await sendTxAndConfirm(