Skip to content

Commit

Permalink
Display init parameters (#52)
Browse files Browse the repository at this point in the history
* (fix) When the user gives you a long block number, don't express it in mantissa/exponent notation, get confused and error out.
(feat) When the user gives you a solidity 0x00.. prefixed constant that looks like an address, treat it as an address.
(feat) Display Scilla contract state and init parameters.

* (fix) Make the type system happy

* (fix) Formatting
  • Loading branch information
rrw-zilliqa authored Aug 28, 2024
1 parent 8d55854 commit 8b76e86
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 14 deletions.
29 changes: 29 additions & 0 deletions src/components/SwitchTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Tab } from "@headlessui/react";
import React, { PropsWithChildren } from "react";

type SwitchTabProps = {
disabled?: boolean | undefined;
children: any;
};

const SwitchTab: React.FC<PropsWithChildren<SwitchTabProps>> = ({
disabled,
children,
}) => (
<Tab
className={({ selected }) =>
`${
disabled
? "cursor-default border-gray-100 text-gray-300"
: selected
? "border-link-blue text-link-blue"
: "border-transparent text-gray-500"
} border-b-2 px-3 py-3 text-sm font-bold hover:text-link-blue`
}
disabled={disabled}
>
{children}
</Tab>
);

export default SwitchTab;
4 changes: 3 additions & 1 deletion src/execution/address/Contracts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ const Contracts: React.FC<ContractsProps> = ({ checksummedAddress, match }) => {
)}
<div className="py-5">
{code === undefined && <span>Getting contract bytecode...</span>}
{scillaCode && <ScillaContract content={scillaCode} />}
{scillaCode && (
<ScillaContract address={checksummedAddress} content={scillaCode} />
)}
{!scillaCode && code && (
<>
<div className="pb-2">Contract Bytecode</div>
Expand Down
53 changes: 43 additions & 10 deletions src/execution/address/ScillaContract.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
import { Tab } from "@headlessui/react";
import React from "react";
import SwitchTab from "../../components/SwitchTab";
import { SyntaxHighlighter, docco } from "../../highlight-init";
import { ScillaInitParams } from "./ScillaInitParams";
import { ScillaState } from "./ScillaState";

type ContractProps = {
address: string;
content: any;
};

const ScillaContract: React.FC<ContractProps> = ({ content }) => (
<SyntaxHighlighter
className="h-full w-full border font-code text-base"
language="scilla"
style={docco}
showLineNumbers
>
{content ?? ""}
</SyntaxHighlighter>
);
const ScillaContract: React.FC<ContractProps> = ({ address, content }) => {
let [loadContractState, setLoadContractState] = React.useState<boolean>();

return (
<div>
<Tab.Group>
<Tab.List>
<SwitchTab>Code</SwitchTab>
<SwitchTab>Init Params</SwitchTab>
<SwitchTab>State</SwitchTab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<SyntaxHighlighter
className="mt-4 h-full w-full border font-code text-base"
language="scilla"
style={docco}
showLineNumbers
>
{content ?? ""}
</SyntaxHighlighter>
</Tab.Panel>
<Tab.Panel>
{" "}
<ScillaInitParams address={address} />
</Tab.Panel>
<Tab.Panel>
<ScillaState
address={address}
loadContractState={loadContractState}
setLoadContractState={setLoadContractState}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</div>
);
};

export default React.memo(ScillaContract);
69 changes: 69 additions & 0 deletions src/execution/address/ScillaInitParams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { FC, useContext } from "react";
import { RuntimeContext } from "../../useRuntime";
import { useSmartContractInit } from "../../useZilliqaHooks";

type ScillaInitParamsProps = {
address: string;
};

type ScillaInitParamRowProps = {
name: string;
valueType: string;
value: string;
};

const ScillaInitParamRow: FC<ScillaInitParamRowProps> = ({
name,
valueType,
value,
}) => {
return (
<>
<tr className="grid grid-cols-12 gap-x-2 py-2 hover:bg-gray-100">
<td className="col-span-3 pl-1">
<span className="text-gray-600">{name}</span>
</td>
<td className="col-span-1 text-gray-500">{valueType}</td>
<td className="col-span-8 text-gray-500">{value}</td>
</tr>
</>
);
};

export const ScillaInitParams: FC<ScillaInitParamsProps> = ({ address }) => {
const { zilliqa } = useContext(RuntimeContext);
const { data, isLoading } = useSmartContractInit(zilliqa, address);
if (isLoading) {
return (
<div className="mt-6">
Loading (or cannot retrieve) contract init parameters
</div>
);
} else {
return (
<div className="mt-6">
<table className="w-ful border">
<thead>
<tr className="grid grid-cols-12 gap-x-2 bg-gray-100 py-2 text-left">
<th className="col-span-3 pl-1">name</th>
<th className="col-span-1 pl-1">type</th>
<th className="col-span-8 pr-1">value</th>
</tr>
</thead>
<tbody className="divide-y">
{data
? data.map((val) => (
<ScillaInitParamRow
key={val.vname}
name={val.vname}
valueType={val.type}
value={val.value}
/>
))
: undefined}
</tbody>
</table>
</div>
);
}
};
91 changes: 91 additions & 0 deletions src/execution/address/ScillaState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { FC, useContext, useState } from "react";
import { RuntimeContext } from "../../useRuntime";
import { ContractState, useSmartContractState } from "../../useZilliqaHooks";

type ScillaStateProps = {
address: string;
loadContractState: boolean | undefined;
setLoadContractState: (arg0: boolean) => void;
};

type ScillaStateRowProps = {
name: string;
value: string;
};

const ScillaStateParamRow: FC<ScillaStateRowProps> = ({ name, value }) => {
return (
<>
<tr className="grid grid-cols-12 gap-x-2 py-2 hover:bg-gray-100">
<td className="col-span-3 pl-1">
<span className="text-gray-600">{name}</span>
</td>
<td className="col-span-8 text-gray-500">{value}</td>
</tr>
</>
);
};

const formatJsonValue = (value: any): string => {
return JSON.stringify(value, null, 2);
};

export const ScillaState: FC<ScillaStateProps> = ({
address,
loadContractState,
setLoadContractState,
}) => {
const { zilliqa } = useContext(RuntimeContext);
const [contractState, setContractState] = useState<ContractState | null>(
null,
);

const { data, isLoading } = useSmartContractState(
loadContractState ? zilliqa : undefined,
address,
);
if (data && contractState == null) {
setContractState(data);
}

if (!loadContractState && !contractState) {
return (
<div className="mt-6">
<button
className="text-link-blue hover:text-link-blue-hover"
onClick={() => setLoadContractState(true)}
>
Load Contract State
</button>
</div>
);
}

if (!contractState) {
return <div className="mt-6"> Loading contract state </div>;
}

return (
<div className="mt-6">
<table className="w-ful border">
<thead>
<tr className="grid grid-cols-12 gap-x-2 bg-gray-100 py-2 text-left">
<th className="col-span-3 pl-1">name</th>
<th className="col-span-8 pr-1">value</th>
</tr>
</thead>
<tbody className="divide-y">
{contractState
? Object.keys(contractState).map((val) => (
<ScillaStateParamRow
key={val}
name={val}
value={formatJsonValue(contractState[val])}
/>
))
: undefined}
</tbody>
</table>
</div>
);
};
27 changes: 24 additions & 3 deletions src/search/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,23 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
maybeIndex = q.substring(sepIndex + 1);
}

// The type checker is convinced that ethers:isAddress() will never say that a string > 40 characters
// long is not an address. I'm not sure why...
if (!isAddress(maybeAddress)) {
let typeCheckerIsWrong = maybeAddress as string;
if (typeCheckerIsWrong.length > 40) {
try {
maybeAddress =
"0x" +
typeCheckerIsWrong
.substr(typeCheckerIsWrong.length - 40)
.toLowerCase();
} catch (e) {
// Obviously not.
}
}
}

// Plain address?
if (isAddress(maybeAddress)) {
navigate(
Expand All @@ -267,10 +284,14 @@ const doSearch = async (q: string, navigate: NavigateFunction) => {
}

// Block number?
const blockNumber = parseInt(q);
if (!isNaN(blockNumber)) {
navigate(`/block/${blockNumber}`);
// If the number here is very large, parseInt() will return an fp number which
// will cause errors, so ..
try {
const blockNumber = BigInt(q);
navigate(`/block/${blockNumber.toString()}`);
return;
} catch (e) {
// Obviously not!
}

// DS Block number?
Expand Down
60 changes: 60 additions & 0 deletions src/useZilliqaHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import { Fetcher } from "swr";
import useSWRImmutable from "swr/immutable";
import useSWRInfinite from "swr/infinite";

export type InitValue = {
vname: string;
type: string;
value: any;
};

export type ContractInitData = Array<InitValue>;

export type ContractState = {
[key: string]: object;
};

const dsBlockDataFetcher: Fetcher<
DsBlockObj | null,
[Zilliqa, number]
Expand Down Expand Up @@ -102,3 +114,51 @@ export const useBlockChainInfo = (
}
return { data, isLoading };
};

export const smartContractInitFetcher: Fetcher<
ContractInitData,
[Zilliqa, string, string]
> = async ([zilliqa, methodName, address]) => {
const contract = zilliqa.contracts.at(address);
const initParams = await contract.getInit();
return initParams as ContractInitData;
};

export const useSmartContractInit = (
zilliqa: Zilliqa | undefined,
address: string,
): { data: ContractInitData | undefined; isLoading: boolean } => {
const { data, error, isLoading } = useSWRImmutable(
zilliqa !== undefined ? [zilliqa, "useSmartContractInit", address] : null,
smartContractInitFetcher,
{ keepPreviousData: true },
);
if (error) {
return { data: undefined, isLoading: false };
}
return { data, isLoading };
};

export const smartContractStateFetcher: Fetcher<
any,
[Zilliqa, string, string]
> = async ([zilliqa, methodName, address]) => {
const contract = zilliqa.contracts.at(address);
const initParams = await contract.getState();
return initParams as any;
};

export const useSmartContractState = (
zilliqa: Zilliqa | undefined,
address: string,
): { data: ContractState | undefined; isLoading: boolean } => {
const { data, error, isLoading } = useSWRImmutable(
zilliqa !== undefined ? [zilliqa, "useSmartContractState", address] : null,
smartContractStateFetcher,
{ keepPreviousData: true },
);
if (error) {
return { data: undefined, isLoading: false };
}
return { data, isLoading };
};

0 comments on commit 8b76e86

Please sign in to comment.