ghost-dao-interface/src/containers/Governance/components/functions/index.jsx
Uncle Fatso 7457926952
view parsed calldata as modal window
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-02-13 13:17:16 +03:00

284 lines
9.6 KiB
JavaScript

import { useRef, useMemo } from "react";
import { Box, Typography, Link, TableCell, TableRow, useTheme } from "@mui/material";
import { decodeFunctionData } from 'viem';
import { StyledInputBase } from "../../../../components/Swap/SwapCard";
import { TertiaryButton } from "../../../../components/Button";
import InfoTooltip from "../../../../components/Tooltip/InfoTooltip";
import { abi as TreasuryAbi } from "../../../../abi/GhostTreasury.json";
import { abi as DistributorAbi } from "../../../../abi/GhostDistributor.json";
import { abi as DepositoryAbi } from "../../../../abi/GhostBondDepository.json";
import { config } from "../../../../config";
import { shorten, formatCurrency } from "../../../../helpers";
import { prepareAuditReservesDescription, prepareAuditReservesCalldata, AuditReservesSteps, AuditReservesParsed } from "./AuditReserves";
import { prepareSetAdjustmentDescription, prepareSetAdjustmentCalldata, SetAdjustmentSteps, SetAdjustmentParsed } from "./SetAdjustment";
import { prepareCreateBondDescription, prepareCreateBondCalldata, CreateBondSteps, CreateBondParsed } from "./CreateBond";
const DEFAULT_DESCRIPTION = "Please select the function to include in your proposal. Multi-functional proposals are allowed, but each included function should be clearly specified."
export const allPossibleFunctions = [
{ value: "auditReserves", label: "Audit Reserves" },
{ value: "setAdjustment", label: "Change APY" },
{ value: "createBond", label: "Create Bond" },
];
const allAbis = [TreasuryAbi, DistributorAbi, DepositoryAbi];
const identifyAction = (calldata) => {
let decoded = { functionName: "Unknown", args: [] };
for (const abi of allAbis) {
try {
decoded = decodeFunctionData({
abi: abi,
data: calldata,
});
return decoded;
} catch (err) {
continue;
}
}
return decoded;
}
export const parseFunctionCalldata = (metadata, index, chainId, nativeCoin, removeCalldata) => {
const { label, calldata, target, value } = metadata;
const { functionName, args } = identifyAction(calldata);
const remove = () => removeCalldata(index);
console.log(`function arguments for ${label}: ${args}`);
switch (functionName) {
case "auditReserves":
return <AuditReservesParsed
isTable
key={index}
label={label}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
case "setAdjustment":
return <SetAdjustmentParsed
isTable
args={args}
key={index}
label={label}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
case "create":
return <CreateBondParsed
isTable
args={args}
key={index}
label={label}
chainId={chainId}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
default:
return <ParsedCell
isTable
key={index}
label={label}
remove={remove}
nativeCoin={nativeCoin}
value={value}
target={target}
id={index}
/>;
}
}
export const getFunctionArguments = (functionName) => {
switch (functionName) {
case "auditReserves":
return null;
case "setAdjustment":
return SetAdjustmentSteps;
case "createBond":
return CreateBondSteps;
default:
return null;
}
}
export const getFunctionCalldata = (functionName, chainId) => {
switch (functionName) {
case "auditReserves":
return prepareAuditReservesCalldata(chainId);
case "setAdjustment":
return prepareSetAdjustmentCalldata(chainId);
case "createBond":
return prepareCreateBondCalldata(chainId);
default:
return null;
}
}
export const getFunctionDescription = (functionName) => {
switch (functionName) {
case "auditReserves":
return prepareAuditReservesDescription;
case "setAdjustment":
return prepareSetAdjustmentDescription;
case "createBond":
return prepareCreateBondDescription;
default:
return DEFAULT_DESCRIPTION;
}
}
export const BooleanValue = ({ left, text, isSelected, setSelected }) => {
const theme = useTheme();
return (
<Box
onClick={() => setSelected()}
display="flex"
justifyContent="center"
alignItems="center"
sx={{
cursor: "pointer",
borderRadius: left ? "12px 0 0 12px" : "0 12px 12px 0",
flex: 1,
border: "2px solid #fff",
borderLeft: left ? "2px solid #fff" : "none",
borderRight: left ? "none" : "2px solid #fff",
background: `${isSelected ? "#fff" : theme.colors.gray[600] }`
}}
>
<Typography
color={isSelected ? theme.colors.gray[600] : "#fff"}
align="center"
>
{text}
</Typography>
</Box>
)
}
export const ArgumentsWrapper = ({ label, tooltip, children }) => {
return (
<Box sx={{ marginBottom: "18px" }}>
<Box sx={{ marginLeft: "5px", marginBottom: "5px" }} display="flex" flexDirection="row" alignItems="center">
<Typography>{label}</Typography>
<InfoTooltip message={tooltip} />
</Box>
{children}
</Box>
)
}
export const BooleanTrigger = ({ value, label, tooltip, leftText, rightText, setLeftValue, setRightValue }) => {
return (
<ArgumentsWrapper label={label} tooltip={tooltip}>
<Box display="flex" width="100%" height="40px">
<BooleanValue setSelected={setLeftValue} left isSelected={value} text={leftText} />
<BooleanValue setSelected={setRightValue} isSelected={!value} text={rightText} />
</Box>
</ArgumentsWrapper>
)
}
export const ArgumentInput = ({
endString,
label,
tooltip,
value,
setValue,
disabled,
inputType = "number",
placeholder = "0",
maxWidth = "100%"
}) => {
const theme = useTheme();
const ref = useRef(null);
return (
<Box sx={{ marginBottom: "15px" }}>
<Box sx={{ marginLeft: "5px", marginBottom: "5px" }} display="flex" flexDirection="row" alignItems="center">
<Typography>{label}</Typography>
<InfoTooltip message={tooltip} />
</Box>
<Box
display="flex"
flexDirection="column"
maxWidth={maxWidth}
sx={{ backgroundColor: theme.colors.gray[750] }}
borderRadius="12px"
padding="6px"
onClick={() => {
ref.current && ref.current.focus();
}}
>
<Box width="100%" display="flex" flexDirection="row" justifyContent="space-between" alignItems="center">
<StyledInputBase
disabled={disabled}
placeholder={placeholder}
type={inputType}
fontSize="20px"
value={value}
onChange={(e) => setValue(e.target.value)}
sx={{ flex: 1 }}
/>
{endString && (
<Box sx={{ paddingRight: "10px", color: theme.colors.gray[500] }} fontSize="12px" lineHeight="15px">
{endString}
</Box>
)}
</Box>
</Box>
</Box>
)
}
export const ParsedCell = (props) => {
const etherscanLink = useMemo(() => {
const client = config.getClient();
let url = client?.chain?.blockExplorers?.default?.url;
if (url) {
url = url + `/address/${props.target}`;
}
return url;
}, [props]);
return (
<TableRow id={props.id + `--proposalFunction`} data-testid={props.id + `--proposalFunction`}>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{props.label}</Typography>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Link href={etherscanLink} target="_blank" rel="noopener noreferrer">
<Typography>{shorten(props.target)}</Typography>
</Link>
</TableCell>
<TableCell align="center" style={{ padding: "8px 0" }}>
<Typography>{formatCurrency(props.value, 2, props.nativeCoin)}</Typography>
</TableCell>
<TableCell>
<div style={{ display: 'flex', gap: '8px' }}>
{props.args && <TertiaryButton fullWidth onClick={() => props.setIsOpened(!props.isOpened)}>View</TertiaryButton>}
{props.remove && <TertiaryButton fullWidth onClick={() => props.remove()}>Delete</TertiaryButton>}
</div>
</TableCell>
</TableRow>
)
}