Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import ProposalExecuteForm from "./ProposalExecuteForm"
import { Expr, MichelsonData } from "@taquito/michel-codec"
import { getContract } from "services/contracts/baseDAO"
import { TezosToolkit } from "@taquito/taquito"
import { BigNumber } from "bignumber.js"
import { useDAO } from "services/services/dao/hooks/useDAO"
import { useDAOID } from "../pages/DAO/router"
import AppConfig from "config"
import { Link } from "react-router-dom"
import { Button } from "components/ui/Button"
import { ResponsiveDialog } from "./ResponsiveDialog"
import { useNotification } from "modules/common/hooks/useNotification"

// Base ACI Lambda
Expand Down Expand Up @@ -250,13 +250,22 @@ const ContractInteractionForm = ({
}

if (!isAciDeployerDeployed && !isLoading) {
// the DAO has no aci_executor handler yet: explain and point to the add-function proposal (one dialog, closable)
return (
<div style={{ display: "flex", flexDirection: "column", gap: 4, marginTop: "20px" }}>
<Typography>We need to deploy the ACI Deployer Contract</Typography>
<Link to={`/explorer/dao/${daoId}/proposals?type=add-function`} color="secondary">
<Typography style={{ color: "white", textDecoration: "underline" }}>Deploy ACI Deployer Contract</Typography>
</Link>
</div>
<Grid container direction="column" style={{ gap: 16, marginTop: 20 }}>
<Typography color="textPrimary">
In order to use open-ended Contract Calls, the DAO contract must be amended.
</Typography>
<Typography color="textPrimary">
If you have the minimum amount of tokens for the proposal fee, click Submit to create a proposal for adding
the Contract Call capability. Once that proposal is executed, this form becomes available.
</Typography>
<Grid item>
<Link to={`/explorer/dao/${daoId}/proposals?type=add-function`}>
<Button>Submit</Button>
</Link>
</Grid>
</Grid>
)
}

Expand Down Expand Up @@ -474,11 +483,16 @@ const ContractInteractionForm = ({
})

console.log("Lambda Param", param)
// the form takes tez, the lambda pushes mutez
const amountMutez = new BigNumber(values.amount || 0)
.multipliedBy(1_000_000)
.integerValue(BigNumber.ROUND_DOWN)
.toNumber()
const lambda = generateExecuteContractMichelson("1.0.0", {
address: values.destination_contract_address,
entrypoint,
type,
amount: values.amount,
amount: amountMutez,
param
})

Expand Down Expand Up @@ -534,7 +548,6 @@ export const ArbitraryContractInteractionForm: React.FC<{
showHeader: (state: boolean) => void
}> = ({ daoLambdas, showHeader }) => {
const daoId = useDAOID()
const [aciProposalOpen, setAciProposalOpen] = useState(false)
const isInvalidKtOrTzAddress = (address: string) => validateContractAddress(address) !== 3

const initialValue: ACIValues = {
Expand Down Expand Up @@ -578,8 +591,6 @@ export const ArbitraryContractInteractionForm: React.FC<{
console.log({ daoLambdas })
}, [daoLambdas])

const isAciDeployerDeployed = daoLambdas?.find((lambda: any) => lambda.key === AppConfig.ACI.EXECUTOR_FUNCTION_NAME)

return (
<Formik
validateOnChange={true}
Expand Down Expand Up @@ -614,23 +625,6 @@ export const ArbitraryContractInteractionForm: React.FC<{
showHeader={showHeader}
daoLambdas={daoLambdas}
/>
{daoLambdas !== undefined && (
<ResponsiveDialog
open={!isAciDeployerDeployed}
onClose={() => setAciProposalOpen(false)}
title={"Proposal to Enable Arbitrary Contract Interaction"}
template="sm"
>
<Typography>In order to use open-ended Contract Calls, the DAO contract must be amended.</Typography>
<Typography>
If you have the minimum amount of tokens for the proposal fee, click Submit to create a proposal for
adding the ACI capability.
</Typography>
<Link to={`/explorer/dao/${daoId}/proposals?type=add-function`}>
<Button>Submit</Button>
</Link>
</ResponsiveDialog>
)}
</Form>
)
}}
Expand Down
29 changes: 28 additions & 1 deletion src/modules/explorer/components/VoteDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo } from "react"
import { Grid, Typography, TextField, Button, useTheme, styled } from "@mui/material"
import { useParams } from "react-router-dom"
import { Link, useParams } from "react-router-dom"
import { useVote } from "services/contracts/baseDAO/hooks/useVote"
import BigNumber from "bignumber.js"
import { useDAO } from "services/services/dao/hooks/useDAO"
Expand All @@ -13,6 +13,8 @@ import { ProposalFormInput } from "./ProposalFormInput"
import { getUserTokenBalance } from "services/bakingBad/tokenBalances"
import { useTezos } from "services/beacon/hooks/useTezos"
import { useTezosBalance } from "services/contracts/baseDAO/hooks/useTezosBalance"
import { useProposals } from "services/services/dao/hooks/useProposals"
import { ProposalStatus } from "services/services/dao/mappers/proposal/types"

const CustomLabelsContainer = styled(Grid)({
marginBottom: 12
Expand All @@ -31,6 +33,14 @@ const CustomMaxLabel = styled(Typography)({
marginLeft: 12
})

const HintText = styled(Typography)(({ theme }) => ({
"fontSize": 14,
"marginTop": 12,
"color": theme.palette.text.primary,
"opacity": 0.75,
"& a": { color: "inherit", textDecoration: "underline" }
}))

const ErrorText = styled(Typography)({
fontSize: 13,
marginTop: 8,
Expand Down Expand Up @@ -82,6 +92,17 @@ export const VoteDialog: React.FC<{
}, [amount, dao, mutate, onClose, proposalId, support, validate])

const { account } = useTezos()
// votes stay staked after a proposal is executed or dropped until the holder releases them
const { data: executedProposals } = useProposals(daoId, ProposalStatus.EXECUTED)
const { data: droppedProposals } = useProposals(daoId, ProposalStatus.DROPPED)
const releasableStake = useMemo(
() =>
(executedProposals ?? []).concat(droppedProposals ?? []).reduce((acc, finishedProposal) => {
const vote = finishedProposal.voters.find(voter => voter.address.toLowerCase() === account?.toLowerCase())
return vote && vote.staked ? acc.plus(vote.value) : acc
}, new BigNumber(0)),
[executedProposals, droppedProposals, account]
)

useMemo(() => {
const found = ledger?.find(l => l.holder.address.toLowerCase() === account.toLowerCase())
Expand Down Expand Up @@ -157,6 +178,12 @@ export const VoteDialog: React.FC<{
/>
</ProposalFormInput>
{error ? <ErrorText>Amount cannot be greater than max available</ErrorText> : null}
{releasableStake.gt(0) ? (
<HintText>
{releasableStake.toFormat()} {dao?.data.token.symbol} are still staked on finished proposals. Release
them with Unstake on the <Link to={`/explorer/dao/${daoId}/user`}>User page</Link> to vote with them.
</HintText>
) : null}
</Grid>
</Grid>

Expand Down
15 changes: 14 additions & 1 deletion src/modules/explorer/pages/ProposalDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ const formatConfig = {
trimMantissa: true
}

const proposalKindLabel = (proposal: Proposal | undefined): string => {
if (!proposal || !(proposal instanceof LambdaProposal)) {
return "Treasury Proposal"
}
const { lambdaType, lambdaHandler } = proposal.metadata
if (lambdaType === "add_handler") return "Add Function Proposal"
if (lambdaType === "remove_handler") return "Remove Function Proposal"
if (lambdaType === "execute_handler") {
return lambdaHandler?.handler_name === "aci_executor" ? "Contract Call Proposal" : "Execute Function Proposal"
}
return "Proposal"
}

export const ProposalDetails: React.FC = () => {
const { proposalId } = useParams<{
proposalId: string
Expand Down Expand Up @@ -333,7 +346,7 @@ export const ProposalDetails: React.FC = () => {
</Grid>
<Grid container alignItems="center" justifyContent="space-between">
<Grid item container direction="row" alignItems="center" xs={12} md={8}>
<DescriptionText variant="body1">Treasury Proposal • Created by</DescriptionText>
<DescriptionText variant="body1">{proposalKindLabel(proposal)} • Created by</DescriptionText>
<Grid style={{ marginLeft: 8 }}>
{proposal && cycleInfo && (
<UserBadge
Expand Down
45 changes: 21 additions & 24 deletions src/services/services/dao/hooks/useProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,37 @@ import { BaseDAO, CycleInfo } from "services/contracts/baseDAO"
import { getProposals } from "services/services/dao/services"
import { ProposalStatus } from "services/services/dao/mappers/proposal/types"
import { useDAO } from "./useDAO"
import { useEffect, useState } from "react"
import { useMemo } from "react"

/**
* Proposals of a DAO, optionally narrowed to one status.
* The list is fetched once per DAO and the status filter is applied at render time with the current cycle info,
* so it follows the chain instead of freezing at whatever level was known when the query first ran.
*/
export const useProposals = (contractAddress: string, status?: ProposalStatus) => {
const [isLoading, setIsLoading] = useState(true)
const [proposalData, setProposalData] = useState<any[]>([])
const { data: daoData, isLoading: isLoadingDAO, error, cycleInfo } = useDAO(contractAddress)
const { data: daoData, error, cycleInfo } = useDAO(contractAddress)

const queryResults = useQuery({
queryKey: ["proposals", contractAddress, status],
queryFn: async () => {
const dao = daoData as BaseDAO
const proposals = await getProposals(dao)

if (!status) {
return proposals
}

return proposals.filter(
proposalData => proposalData.getStatus((cycleInfo as CycleInfo).currentLevel).status === status
)
},
queryKey: ["proposals", contractAddress],
queryFn: async () => getProposals(daoData as BaseDAO),
refetchInterval: 30000,
enabled: !!daoData && !!cycleInfo
enabled: !!daoData
})

useEffect(() => {
if (queryResults.data) {
setProposalData(queryResults.data)
const data = useMemo(() => {
const proposals = queryResults.data ?? []
if (!status) {
return proposals
}
if (!cycleInfo) {
return []
}
}, [queryResults.data])
return proposals.filter(proposal => proposal.getStatus((cycleInfo as CycleInfo).currentLevel).status === status)
}, [queryResults.data, status, cycleInfo])

return {
data: proposalData,
isLoading: isLoading || queryResults.isLoading,
data,
isLoading: queryResults.isLoading,
error: error || queryResults.error
}
}
Loading