Proxy implementation (#216)

* Proxy implementation

* Support one1 addresses

* Add Read as proxy and Write as proxy features

* Improve verification UI

* Fix input width

* Minor lifecycle fix
pull/225/head
Artem 2 years ago committed by GitHub
parent a27920c894
commit 43fb25e464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      src/Routes.tsx
  2. 21
      src/api/client.ts
  3. 9
      src/components/appHeader/ToolsButton.tsx
  4. 30
      src/pages/AddressPage/ContractDetails/helpers.ts
  5. 159
      src/pages/AddressPage/ContractDetails/index.tsx
  6. 87
      src/pages/AddressPage/index.tsx
  7. 196
      src/pages/VerifyProxyContract/VerifyProxyContract.tsx
  8. 1
      src/types/blockchain.ts

@ -15,6 +15,7 @@ import { ExportData } from "./pages/ExportData";
import { InventoryDetailsPage } from "./pages/InventoryDetailsPage/InventoryDetailsPage";
import { ApprovalPage } from "./pages/ApprovalPage";
import { CheckHRC } from "./pages/tools/CheckHRC";
import {VerifyProxyContract} from "./pages/VerifyProxyContract/VerifyProxyContract";
import { ChartsPage } from "./pages/ChartsPage";
export function Routes() {
@ -95,6 +96,10 @@ export function Routes() {
<VerifyContract />
</Route>
<Route path="/proxyContractChecker">
<VerifyProxyContract />
</Route>
<Route path="/exportData">
<ExportData />
</Route>

@ -1,11 +1,12 @@
import { transport } from "./explorer";
import {
Block,
InternalTransaction,
RPCStakingTransactionHarmony,
RPCTransactionHarmony,
RelatedTransaction,
Log, LogDetailed, AddressDetails, MetricsType, MetricsDailyItem
Block,
InternalTransaction,
RPCStakingTransactionHarmony,
RPCTransactionHarmony,
RelatedTransaction,
Log, LogDetailed, AddressDetails,
ShardID, MetricsType, MetricsDailyItem
} from "src/types";
import {
IHoldersInfo,
@ -215,3 +216,11 @@ export async function getTokenERC1155AssetDetails(address: string, tokenID: stri
// todo fix on backend
return res && res[0]
}
export function getProxyImplementation(params: [ShardID, string]) {
return transport("getProxyImplementation", params) as Promise<any>;
}
export function assignProxyImplementation(params: [ShardID, string, string]) {
return transport("assignProxyImplementation", params) as Promise<any>;
}

@ -52,6 +52,15 @@ export function ToolsButton() {
>
Check HRC
</Anchor>
<Anchor
style={{ textDecoration: "underline" }}
onClick={(e) => {
setIsOpen(false);
history.push("/proxyContractChecker");
}}
>
Proxy verification
</Anchor>
<Anchor
style={{ textDecoration: "underline" }}
onClick={(e) => {

@ -1,5 +1,7 @@
import { AbiInput } from "web3-utils";
import { getAddress } from "src/utils";
import {config} from "../../../config";
import {getContractsByField} from "../../../api/client";
export const convertInputs = (inputs: string[], abiInputs: AbiInput[]) => {
return inputs.map((value, idx) => {
@ -12,3 +14,31 @@ export const convertInputs = (inputs: string[], abiInputs: AbiInput[]) => {
}
});
};
export const getContractInAllShards = async (contractId: string) => {
const { availableShards } = config
let contract = null
let shardId = null
for(let i = 0; i < availableShards.length; i++) {
try {
const sId = availableShards[i]
contract = await getContractsByField([sId, "address", contractId]);
if (contract) {
shardId = sId
break
}
// Temp optimization to reduce number of requests
if(sId === 1) {
break
}
} catch (_) {}
}
return {
contract,
shardId
}
}

@ -15,13 +15,23 @@ const StyledTextArea = styled(TextArea)`
font-weight: normal;
`;
const LabelSuccess = styled(Box)`
width: fit-content;
padding: 0 4px;
text-transform: uppercase;
border-radius: 4px;
font-size: 8px;
font-weight: bold;
`
export const ContractDetails = (props: {
address: string;
contracts?: AddressDetails | null;
sourceCode?: ISourceCode | null;
implementation?: AddressDetails | null;
implementationSourceCode?: ISourceCode | null;
shard?: ShardID;
}) => {
// console.log(111, appendABI(abi, props.address));
if (!!props.sourceCode) {
return (
@ -30,6 +40,8 @@ export const ContractDetails = (props: {
contracts={props.contracts}
address={props.address}
shard={props.shard || 0}
implementation={props.implementation}
implementationSourceCode={props.implementationSourceCode}
/>
);
}
@ -174,7 +186,7 @@ export const NoVerifiedContractDetails = (props: {
color="brand"
>
Verify and Publish
</Text>{" "}
</Text>
your contract source code today!
</Box>
@ -218,15 +230,19 @@ const TabBox = styled(Box) <{ selected: boolean }>`
`;
const TabButton = (props: {
text: string;
text?: string;
onClick: () => void;
selected: boolean;
children?: any;
}) => {
return (
<TabBox onClick={props.onClick} selected={props.selected}>
<Text size="small" color={"minorText"}>
{props.text}
</Text>
{props.text &&
<Text size="small" color={"minorText"}>
{props.text}
</Text>
}
{props.children && props.children}
</TabBox>
);
};
@ -236,6 +252,8 @@ export const VerifiedContractDetails = (props: {
address: string;
contracts?: AddressDetails | null;
shard: number;
implementation?: AddressDetails | null;
implementationSourceCode?: ISourceCode | null;
}) => {
const [tab, setTab] = useState<V_TABS>(V_TABS.CODE);
const [metamaskAddress, setMetamask] = useState("");
@ -277,19 +295,39 @@ export const VerifiedContractDetails = (props: {
/>
</>
) : null}
{props.sourceCode.proxyAddress && props.sourceCode.proxy ? (
<>
<TabButton
text={V_TABS.READ_PROXY + "(new)"}
onClick={() => setTab(V_TABS.READ_PROXY)}
selected={tab === V_TABS.READ_PROXY}
/>
<TabButton
text={V_TABS.WRITE_PROXY + "(new)"}
onClick={() => setTab(V_TABS.WRITE_PROXY)}
selected={tab === V_TABS.WRITE_PROXY}
/>
</>
{/*{props.sourceCode.proxyAddress && props.sourceCode.proxy ? (*/}
{/* <>*/}
{/* <TabButton*/}
{/* text={V_TABS.READ_PROXY + "(new)"}*/}
{/* onClick={() => setTab(V_TABS.READ_PROXY)}*/}
{/* selected={tab === V_TABS.READ_PROXY}*/}
{/* />*/}
{/* <TabButton*/}
{/* text={V_TABS.WRITE_PROXY + "(new)"}*/}
{/* onClick={() => setTab(V_TABS.WRITE_PROXY)}*/}
{/* selected={tab === V_TABS.WRITE_PROXY}*/}
{/* />*/}
{/* </>*/}
{/*) : null}*/}
{props.implementation && props.implementationSourceCode ? (
<>
<TabButton
onClick={() => setTab(V_TABS.READ_PROXY)}
selected={tab === V_TABS.READ_PROXY}>
<Box direction={'row'} gap={'4px'}>
<Text size={'small'} color={'minorText'}>{V_TABS.READ_PROXY}</Text>
<LabelSuccess color={'text'} background={'backgroundSuccess'}>new</LabelSuccess>
</Box>
</TabButton>
<TabButton
onClick={() => setTab(V_TABS.WRITE_PROXY)}
selected={tab === V_TABS.WRITE_PROXY}>
<Box direction={'row'} gap={'4px'}>
<Text size={'small'} color={'minorText'}>{V_TABS.WRITE_PROXY}</Text>
<LabelSuccess color={'text'} background={'backgroundSuccess'}>new</LabelSuccess>
</Box></TabButton>
</>
) : null}
</Box>
@ -412,36 +450,65 @@ export const VerifiedContractDetails = (props: {
</Box>
) : null}
{tab === V_TABS.READ_PROXY && props.sourceCode.proxy ? (
<Box style={{ padding: "10px" }}>
<ProxyContractDetails address={props.sourceCode.proxyAddress || ""} proxy={props.sourceCode.proxyDetails}></ProxyContractDetails>
<AbiMethods
abi={props.sourceCode.proxy.result.abi.filter(
(a: { stateMutability: string; type: string; }) => a.stateMutability === "view" && a.type === "function"
)}
address={props.address || ""}
isRead={V_TABS.READ_PROXY === tab}
/>
</Box>
{tab === V_TABS.READ_PROXY && props.implementation?.address && props.implementationSourceCode?.abi ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<AbiMethods
abi={props.implementationSourceCode?.abi.filter(
(a) => a.stateMutability === "view" && a.type === "function"
)}
address={props.implementation?.address}
isRead={V_TABS.READ_PROXY === tab}
/>
</Box>
) : null}
{tab === V_TABS.WRITE_PROXY && props.sourceCode.proxy ? (
<Box style={{ padding: "10px" }}>
<Wallet onSetMetamask={setMetamask} onSetChainId={setChainId} />
<ProxyContractDetails address={props.sourceCode.proxyAddress || ""} proxy={props.sourceCode.proxyDetails}></ProxyContractDetails>
<AbiMethods
abi={props.sourceCode.proxy.result.abi.filter(
(a: { stateMutability: string; name: any; type: string; }) =>
a.stateMutability !== "view" &&
!!a.name &&
a.type === "function"
)}
address={props.address || ""}
metamaskAddress={metamaskAddress}
validChainId={validChainId}
/>
</Box>
{tab === V_TABS.WRITE_PROXY && props.implementation?.address && props.implementationSourceCode?.abi ? (
<Box style={{ padding: "10px" }} margin={{ top: "medium" }}>
<Wallet onSetMetamask={setMetamask} onSetChainId={setChainId} />
<AbiMethods
abi={props.implementationSourceCode?.abi.filter(
(a) =>
a.stateMutability !== "view" &&
!!a.name &&
a.type === "function"
)}
address={props.implementation?.address}
metamaskAddress={metamaskAddress}
validChainId={validChainId}
/>
</Box>
) : null}
{/*{tab === V_TABS.READ_PROXY && props.sourceCode.proxy ? (*/}
{/* <Box style={{ padding: "10px" }}>*/}
{/* <ProxyContractDetails address={props.sourceCode.proxyAddress || ""} proxy={props.sourceCode.proxyDetails}></ProxyContractDetails>*/}
{/* <AbiMethods*/}
{/* abi={props.sourceCode.proxy.result.abi.filter(*/}
{/* (a: { stateMutability: string; type: string; }) => a.stateMutability === "view" && a.type === "function"*/}
{/* )}*/}
{/* address={props.address || ""}*/}
{/* isRead={V_TABS.READ_PROXY === tab}*/}
{/* />*/}
{/* </Box>*/}
{/*) : null}*/}
{/*{tab === V_TABS.WRITE_PROXY && props.sourceCode.proxy ? (*/}
{/* <Box style={{ padding: "10px" }}>*/}
{/* <Wallet onSetMetamask={setMetamask} onSetChainId={setChainId} />*/}
{/* <ProxyContractDetails address={props.sourceCode.proxyAddress || ""} proxy={props.sourceCode.proxyDetails}></ProxyContractDetails>*/}
{/* <AbiMethods*/}
{/* abi={props.sourceCode.proxy.result.abi.filter(*/}
{/* (a: { stateMutability: string; name: any; type: string; }) =>*/}
{/* a.stateMutability !== "view" &&*/}
{/* !!a.name &&*/}
{/* a.type === "function"*/}
{/* )}*/}
{/* address={props.address || ""}*/}
{/* metamaskAddress={metamaskAddress}*/}
{/* validChainId={validChainId}*/}
{/* />*/}
{/* </Box>*/}
{/*) : null}*/}
</Box>
);
};

@ -3,7 +3,6 @@ import { Text, Tabs, Tab, Box } from "grommet";
import { BasePage, BaseContainer } from "src/components/ui";
import { AddressDetailsDisplay, getType } from "./AddressDetails";
import {
getContractsByField,
getUserERC20Balances,
getUserERC721Assets,
getTokenERC721Assets,
@ -32,8 +31,8 @@ import { HoldersTab } from "./tabs/holders/HoldersTab";
import { parseHexToText } from "../../web3/parseHex";
import { EventsTab } from "./tabs/events/Events";
import { ToolsTab } from "./tabs/tools";
import { config } from "../../config";
import useQuery from "../../hooks/useQuery";
import {getContractInAllShards} from "./ContractDetails/helpers";
export function AddressPage() {
const history = useHistory();
@ -46,6 +45,8 @@ export function AddressPage() {
const [balance, setBalance] = useState<any>([]);
const [delegations, setDelegations] = useState<StakingDelegation[]>([]);
const [addressDescription, setAddressDescription] = useState('')
const [implementation, setImplementation] = useState<AddressDetails | null>(null)
const [implementationSourceCode, setImplementationSourceCode] = useState<ISourceCode | null>(null)
const [tokens, setTokens] = useState<any>(null);
const [inventory, setInventory] = useState<IUserERC721Assets[]>([]);
@ -93,49 +94,34 @@ export function AddressPage() {
getBal();
}, [id]);
useEffect(() => {
// contract defined and contract address same as id
// note: when we toggle there is scenarios where the id are not the same
// @ts-ignore
if (!!contracts && contracts?.address === id && contractShardId !== null) {
loadSourceCode(id, contractShardId)
.then((res) => setSourceCode(res))
.catch((except) => {
console.log(except);
setSourceCode(null)
});
}
}, [id, contracts, contractShardId]);
const getContractInAllShards = async (contractId: string) => {
const { availableShards } = config
// useEffect(() => {
// const loadCode = async () => {
// try {
// const data = await loadSourceCode(id, contractShardId || 0)
// setSourceCode(data)
// } catch (e) {
// setSourceCode(null)
// console.log('Error on loading source code:', e);
// }
// }
// // contract defined and contract address same as id
// // note: when we toggle there is scenarios where the id are not the same
// // @ts-ignore
// if (!!contracts && contracts?.address === id && contractShardId !== null) {
// loadCode()
// }
// }, [id, contracts, contractShardId]);
let contract = null
let shardId = null
for(let i = 0; i < availableShards.length; i++) {
useEffect(() => {
const getContractCode = async (id: string, shardId: ShardID) => {
try {
const sId = availableShards[i]
contract = await getContractsByField([sId, "address", contractId]);
if (contract) {
shardId = sId
break
}
// Temp optimization to reduce number of requests
if(sId === 1) {
break
}
} catch (_) {}
}
return {
contract,
shardId
const data = await loadSourceCode(id, shardId)
return data
} catch (e) {
return null
}
}
}
useEffect(() => {
const getContracts = async () => {
try {
let { contract, shardId } = await getContractInAllShards(id);
@ -143,12 +129,29 @@ export function AddressPage() {
const mergedContracts: any = erc721Map[contract.address]
? { ...contracts, ...erc721Map[contract.address] }
: contract;
const code = await getContractCode(contract.address, shardId || 0)
setContracts(mergedContracts);
setContractShardId(shardId)
setSourceCode(code)
if(contract.implementationAddress) {
let { contract: contractData, shardId } = await getContractInAllShards(contract.implementationAddress);
if (contractData) {
const implCode = await getContractCode(contractData.address, shardId || 0)
console.log('Implementation contract loaded:', contractData)
setImplementation(contractData)
setImplementationSourceCode(implCode)
// setSourceCode(implCode)
}
} else {
setImplementation(null)
setImplementationSourceCode(null)
}
}
} catch (err) {
setContracts(null);
console.error('Error on loading contract:', JSON.stringify(err))
}
};
getContracts();
@ -376,6 +379,8 @@ export function AddressPage() {
contracts={contracts}
sourceCode={sourceCode}
shard={contractShardId || 0}
implementation={implementation}
implementationSourceCode={implementationSourceCode}
/>
</Tab>
) : null}

@ -0,0 +1,196 @@
import React, { useEffect, useState } from "react";
import {Box, Heading, Select, Spinner, Text, TextArea, TextInput, Tip} from "grommet";
import { Alert, StatusGood } from "grommet-icons";
import { useHistory } from "react-router-dom";
import useQuery from "../../hooks/useQuery";
import {
assignProxyImplementation,
getProxyImplementation
} from "../../api/client";
import {Address, BaseContainer, BasePage, Button} from "../../components/ui";
import styled from "styled-components";
import {getAddress} from "../../utils";
import {ISourceCode, loadSourceCode} from "../../api/explorerV1";
export const ActionButton = styled(Button)`
font-size: 14px;
padding: 8px 8px 5px 8px;
font-weight: 500;
`;
export function VerifyProxyContract() {
const query = useQuery();
const history = useHistory();
const queryAddress = query.get('a') || '';
const [isLoading, setIsLoading] = useState(false)
const [contractAddress, setContractAddress] = useState(queryAddress)
const [implementationAddress, setImplementationAddress] = useState('')
const [implementationCode, setImplementationCode] = useState<ISourceCode>()
const [contractCode, setContractCode] = useState<ISourceCode>()
const [verificationError, setVerificationError] = useState('')
const [isVerified, setIsVerified] = useState(false)
const enrichAddress = (address: string) => {
let a = address.trim().toLowerCase()
if(address.startsWith('one1')) { // convert one1 to 0x before send request to backend
try {
a = getAddress(address).basicHex
} catch (e) {}
}
return a
}
const setDefaultState = () => {
setImplementationAddress('')
setVerificationError('')
setIsVerified(false)
}
const validateAddress = (address: string) => {
if(address.length === 0) {
return ''
} else {
if(!address.startsWith('0x') && !address.startsWith('one1')) {
return 'Address should start with 0x or one1'
}
if(address.length != 42) {
return 'Address must be 42 characters long'
}
}
return ''
}
const verifyContract = async () => {
setImplementationAddress('')
const err = validateAddress(contractAddress)
if(err) {
setVerificationError(err)
} else {
try {
setIsLoading(true)
setVerificationError('')
const formattedContractAddress = enrichAddress(contractAddress)
const implContract = await getProxyImplementation([0, formattedContractAddress]);
if(implContract) {
try {
const code = await loadSourceCode(formattedContractAddress, 0)
const implCode = await loadSourceCode(implContract.address, 0)
setContractCode(code)
setImplementationCode(implCode)
} catch (e) {
setVerificationError('Proxy or implementation contract source code is not verified. Please verify and publish the contract source before proceeding with this proxy verification.')
}
setImplementationAddress(implContract.address)
} else {
setImplementationAddress('')
setVerificationError('This contract does not look like it contains any delegatecall opcode sequence.')
}
} catch (e) {
console.error('Unable to verify contract', e)
setVerificationError('Cannot verify contract address, try again later')
} finally {
setIsLoading(false)
}
}
}
const applyImplementationAddress = async () => {
const contract = await assignProxyImplementation([0, enrichAddress(contractAddress), implementationAddress])
setDefaultState()
setIsVerified(true)
}
const onChangeInput = (value: string) => {
setContractAddress(value)
setDefaultState()
}
return (
<BaseContainer pad={{ horizontal: "0" }}>
<Heading size="small" margin={{ bottom: "medium", top: "0" }}>
Proxy Contract Verification
</Heading>
<BasePage pad={"small"} style={{ overflow: "inherit" }}>
<Box pad={'16px'} gap={'16px'} width={'600px'}>
<Box direction="row" justify={'start'} wrap>
<Box margin={'0'}>
<Text size={'small'}>Please enter the Proxy Contract Address you would like to verify</Text>
<Box direction="row">
<Box width={'600px'}>
<TextInput
placeholder={"Contract address"}
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => onChangeInput(evt.currentTarget.value)}
value={contractAddress}
disabled={false}
/>
</Box>
</Box>
</Box>
</Box>
{!implementationAddress &&
<Box direction={'column'} gap={'16px'}>
{verificationError &&
<Box round={'8px'} background={'backgroundError'} pad={'16px'}>
<Text color={'text'} size={'small'}>
{verificationError}
</Text>
</Box>
}
{isVerified &&
<Box round={'8px'} background={'backgroundSuccess'} pad={'16px'}>
<Text color={'successText'} size={'small'}>
Successfully saved. Feel free to return to the address <Address address={contractAddress} hideCopyBtn={true} /> to view updates.
</Text>
</Box>
}
<Box direction={'row'} gap={'16px'}>
<Box width={'small'}>
<ActionButton
disabled={!contractAddress || isLoading}
onClick={verifyContract}>
Verify
</ActionButton>
</Box>
<Box width={'small'}>
<ActionButton
disabled={!contractAddress}
onClick={() => {
setDefaultState()
setContractAddress('')
}
}>Clear</ActionButton>
</Box>
</Box>
</Box>
}
{implementationAddress &&
<Box direction={'column'} gap={'16px'}>
<Box>
<Text size={'small'}>The proxy's implementation contract is found at:</Text>
<Text size={'small'} weight={'bold'} color={'successText'}>
<Address address={implementationAddress} />
</Text>
</Box>
{verificationError &&
<Box round={'8px'} background={'backgroundError'} pad={'16px'}>
<Text color={'text'} size={'small'}>
{verificationError}
</Text>
</Box>
}
<Box direction={'row'} gap={'16px'}>
<Box width={'small'}>
<ActionButton
disabled={isLoading || !implementationCode || !contractCode}
onClick={applyImplementationAddress}>Save</ActionButton>
</Box>
<Box width={'small'}><ActionButton onClick={setDefaultState}>Clear</ActionButton></Box>
</Box>
</Box>
}
</Box>
</BasePage>
</BaseContainer>
)
}

@ -287,6 +287,7 @@ export interface AddressDetails {
meta?: { name?: string; image?: string };
solidityVersion: string
transactionHash: TransactionHash
implementationAddress?: string
}
export interface IHexSignature {

Loading…
Cancel
Save