From 6046933cc4aa27eac2d9455f9cb35113f1a53e9b Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Fri, 16 Oct 2020 18:15:15 +0300 Subject: [PATCH 01/41] Multi-call generic scheme init --- .../Proposal/Create/CreateProposal.scss | 35 ++ .../Proposal/Create/SchemeForms/ABIService.ts | 152 ++++++ .../CreateGenericMultiCallProposal.tsx | 501 ++++++++++++++++++ .../Proposal/Create/SchemeForms/Validators.ts | 49 ++ 4 files changed, 737 insertions(+) create mode 100644 src/components/Proposal/Create/SchemeForms/ABIService.ts create mode 100644 src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx create mode 100644 src/components/Proposal/Create/SchemeForms/Validators.ts diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 15f131d81..730147398 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -7,6 +7,28 @@ display: none !important; } +fieldset { + margin: 30px 0px; +} + +.removeFieldSet { + float: right; + border: none; + color: $accent-2; + background: none; +} + +.addFieldSet { + border: none; + background: none; + color: $sky; +} + +.removeFieldSet:hover, +.addFieldSet:hover { + opacity: 0.7; +} + .createProposalWrapper { border-radius: 15px 15px 0 0; background-color: $white; @@ -58,6 +80,7 @@ } form { + select, input { width: 100%; margin-top: 3px; @@ -114,6 +137,18 @@ } } + .addContract { + position: relative; + } + + .encodedData { + overflow-wrap: break-word; + margin: 5px 0 5px 0px; + border: 1px solid; + padding: 20px; + width: 490px; // TEMP - SHOULD BE DYNAMIC + } + .proposerIsAdminCheckbox { label { display: inline-block; diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts new file mode 100644 index 000000000..a67fb9caf --- /dev/null +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -0,0 +1,152 @@ +import { AbiItem, isHex } from "web3-utils"; +import { SortService } from "lib/sortService"; +const Web3 = require("web3"); +import axios from "axios"; +import { isAddress, targetedNetwork } from "lib/util"; + +export interface IAllowedAbiItem extends AbiItem { + name: string + type: "function" +} + +export interface IAbiItemExtended extends IAllowedAbiItem { + action: string + methodSignature: string +} + +/** + * Given a contract address returns the URL to fetch the ABI data accroding the current network + * @param {string} contractAddress + * @returns {string} URL + */ +const getUrl = (contractAddress: string): string => { + const network = targetedNetwork(); + if (network === "xdai"){ + return `https://blockscout.com/poa/xdai/api?module=contract&action=getabi&address=${contractAddress}`; + } + else { + const prefix = (network === "main" || network === "ganache") ? "" : `-${network}`; // we consider 'ganache' as 'main' + return `https://api${prefix}.etherscan.io/api?module=contract&action=getabi&address=${contractAddress}&apikey=${process.env.ETHERSCAN_API_KEY}`; + } +}; + +const getSignatureHash = (signature: string): string => { + return Web3.utils.keccak256(signature).toString(); +}; + +const getMethodSignature = ({ inputs, name }: AbiItem): string => { + const params = inputs?.map((x) => x.type).join(","); + return `${name}(${params})`; +}; + +const getMethodAction = ({ stateMutability }: AbiItem): "read" | "write" => { + if (!stateMutability) { + return "write"; + } + + return ["view", "pure"].includes(stateMutability) ? "read" : "write"; +}; + +const getMethodSignatureAndSignatureHash = (method: AbiItem,): { methodSignature: string; signatureHash: string } => { + const methodSignature = getMethodSignature(method); + const signatureHash = getSignatureHash(methodSignature); + return { methodSignature, signatureHash }; +}; + +const isAllowedABIMethod = ({ name, type }: AbiItem): boolean => { + return type === "function" && !!name; +}; + +/** + * Given valid ABI returns write functions with all thier data. + * @param {AbiItem[]} abi + */ +export const extractABIMethods = (abi: AbiItem[]): IAbiItemExtended[] => { + const allowedAbiItems = abi.filter(isAllowedABIMethod) as IAllowedAbiItem[]; + + return allowedAbiItems.map((method): IAbiItemExtended => ({ + action: getMethodAction(method), + ...getMethodSignatureAndSignatureHash(method), + ...method, + })) + .filter((method) => method.action === "write") + .sort(({ name: a }, { name: b }) => SortService.evaluateString(a, b, 1)); +}; + +/** + * Given array of ABI parameters objects, returns true if all values are valid. + * Data example: + * [{ type: address, value: "0x25112235dDA2F775c81f0AA37a2BaeA21B470f65" }] + * @param {array} data + * @returns {boolean} + */ +export const validateABIInputs = (data: Array): boolean => { + for (const input of data) { + switch (true) { + case input.type.includes("address"): + if (!isAddress(input.value)) { + return false; + } + break; + case input.type.includes("byte"): + if (!isHex(input.value)) { + return false; + } + break; + case input.type.includes("uint"): + if (/^\d+$/.test(input.value) === false) { + return false; + } + break; + } + } + return true; +}; + +/** + * Given contract address returns it's ABI data. + * @param {string} contractAddress + */ +export const getABIByContract = async (contractAddress: string): Promise> => { + const url = getUrl(contractAddress); + try { + const response = await axios({ url: url, method: "GET" }).then(res => { return res.data; }); + if (response.status === "0") { + return []; + } + return JSON.parse(response.result); + } catch (e) { + // eslint-disable-next-line no-console + console.error("Failed to retrieve ABI", e); + return []; + } +}; + +/** + * Given ABI, function name and it's parameters values returns the encoded data as string. + * @param {array} abi ABI methods array + * @param {string} name Method name + * @param {array} data array of ABI parameters objects. Example: [{ type: address, value: "0x25112235dDA2F775c81f0AA37a2BaeA21B470f65" }] + * @returns {string} The encoded data + */ +export const encodeABI = (abi: Array, name: string, data: any[]): string => { + const web3 = new Web3; + const contract = new web3.eth.Contract(abi); + const interfaceABI = contract.options.jsonInterface; + + if (validateABIInputs(data)) { + const values = []; + for (const input of data) { + values.push(input.value); + } + let methodToSend; + for (const method of interfaceABI){ + if (method.name === name) { + methodToSend = method; + } + } + return web3.eth.abi.encodeFunctionCall(methodToSend, values); + } + + return ""; +}; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx new file mode 100644 index 000000000..1636e8d7b --- /dev/null +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -0,0 +1,501 @@ +import { ISchemeState } from "@daostack/arc.js"; +import { createProposal } from "actions/arcActions"; +import { enableWalletProvider } from "arc"; +import { ErrorMessage, Field, Form, Formik, FormikProps, FieldArray } from "formik"; +import Analytics from "lib/analytics"; +import * as React from "react"; +import { connect } from "react-redux"; +import { showNotification, NotificationStatus } from "reducers/notifications"; +import { baseTokenName, isValidUrl, isAddress, linkToEtherScan } from "lib/util"; +import { exportUrl, importUrlValues } from "lib/proposalUtils"; +import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; +import TrainingTooltip from "components/Shared/TrainingTooltip"; +import * as css from "../CreateProposal.scss"; +import MarkdownField from "./MarkdownField"; +import HelpButton from "components/Shared/HelpButton"; +import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; +import { requireValue, validateParam } from "./Validators"; + +const whitelistedContracts = [ + "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf", + "0x5C5cbaC45b18F990AbcC4b890Bf98d82e9ee58A0", + "0x24832a7A5408B2b18e71136547d308FCF60B6e71", + "0x4E073a7E4a2429eCdfEb1324a472dd8e82031F34", +]; + +interface IExternalProps { + daoAvatarAddress: string; + handleClose: () => any; + scheme: ISchemeState; +} + +interface IDispatchProps { + createProposal: typeof createProposal; + showNotification: typeof showNotification; +} + +interface IAddContractStatus { + error: string + message: string +} + +interface IStateProps { + loading: boolean + tags: Array; + addContractStatus: IAddContractStatus + whitelistedContracts: Array + userContracts: Array +} + +type IProps = IExternalProps & IDispatchProps; + +const mapDispatchToProps = { + createProposal, + showNotification, +}; + +interface IContract { + address: string // Contract address + value: number // Token to send with the proposal + abi: any // Contract ABI data + methods: any // ABI write methods + method: string // Selected method + params: any // Method params + values: any // Params values + callData: string // The encoded data +} + +interface IFormValues { + description: string; + title: string; + url: string; + contracts: Array + [key: string]: any; +} + +class CreateGenericMultiCallScheme extends React.Component { + + initialFormValues: IFormValues; + + constructor(props: IProps) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + this.initialFormValues = importUrlValues({ + description: "", + title: "", + url: "", + tags: [], + userContracts: [], + contracts: [ + { + address: "", + value: 0, + abi: [] as any, + methods: [] as any, + method: "", + params: [] as any, + values: [] as any, + callData: "", + }, + ], + }); + this.state = { + loading: false, + tags: this.initialFormValues.tags, + addContractStatus: { error: "", message: "" }, + whitelistedContracts: whitelistedContracts, + userContracts: [] + }; + } + + public async handleSubmit(formValues: IFormValues, { setSubmitting }: any): Promise { + if (!await enableWalletProvider({ showNotification: this.props.showNotification })) { return; } + + const contractsToCall = []; + const callsData = []; + const values = []; + + for (const contract of formValues.contracts) { + contractsToCall.push(contract.address); + callsData.push(contract.callData); + values.push(contract.value); + } + + const proposalValues = { + title: formValues.title, + description: formValues.description, + contractsToCall: contractsToCall, + callsData: callsData, + values: values, + url: formValues.url, + dao: this.props.daoAvatarAddress, + scheme: this.props.scheme.address, + tags: this.state.tags, + }; + + setSubmitting(false); + await this.props.createProposal(proposalValues); + + Analytics.track("Submit Proposal", { + "DAO Address": this.props.daoAvatarAddress, + "Proposal Title": formValues.title, + "Scheme Address": this.props.scheme.address, + "Scheme Name": this.props.scheme.name, + }); + + this.props.handleClose(); + } + + // Exports data from form to a shareable url. + public exportFormValues(values: IFormValues) { + exportUrl({ ...values, ...this.state }); + this.props.showNotification(NotificationStatus.Success, "Exportable url is now in clipboard :)"); + } + + private onTagsChange = (tags: any[]): void => { + this.setState({ tags }); + } + + /** + * Given a contract address, checks whether it's valid, not exists in the current contract list and that the contract is verified with valid ABI data and write methods. + * If all checks are okay, pushes the contract address to the contract lists, otherwise returns an appropriate message. + * @param {string} contractToCall + */ + private verifyContract = async (contractToCall: string) => { + const addContractStatus = { + error: "", + message: "", + }; + + if (!isAddress(contractToCall)) { + addContractStatus.error = "NOT_VALID_ADDRESS"; + addContractStatus.message = "Please enter a valid address"; + } else if (this.state.whitelistedContracts.includes(contractToCall) || this.state.userContracts.includes(contractToCall)) { + addContractStatus.error = "CONTRACT_EXIST"; + addContractStatus.message = "Contract already exist!"; + } else { + this.setState({ loading: true }); + const abiData = await getABIByContract(contractToCall); + const abiMethods = extractABIMethods(abiData); + if (abiMethods.length > 0) { + this.state.userContracts.push(contractToCall); + addContractStatus.error = ""; + addContractStatus.message = "Contract added successfully!"; + } else { + addContractStatus.error = "ABI_DATA_ERROR"; + addContractStatus.message = abiData.length === 0 ? "No ABI found for target contract, please verify the " : "No write methods found for target "; + } + } + this.setState({ loading: false, addContractStatus: addContractStatus }); + } + + private getContractABI = async (contractToCall: string, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.method`, ""); // reset + setFieldValue(`contracts.${index}.callData`, ""); // reset + const abiData = await getABIByContract(contractToCall); + const abiMethods = extractABIMethods(abiData); + setFieldValue(`contracts.${index}.abi`, abiData); + setFieldValue(`contracts.${index}.methods`, abiMethods); + } + + private getMethodInputs = (abi: any, methods: any[], methodName: any, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.callData`, ""); // reset + const selectedMethod = methods.filter(method => method.methodSignature === methodName); + const abiParams = selectedMethod[0].inputs.map((input: any, index: number) => { + return { + id: index, + name: input.name, + type: input.type, + placeholder: `${input.name} (${input.type})`, + methodSignature: input.methodSignature, + }; + }); + setFieldValue(`contracts.${index}.params`, abiParams); + if (abiParams.length === 0) { // If no params, generate the encoded data + setFieldValue(`contracts.${index}.callData`, encodeABI(abi, selectedMethod[0].name, [])); + } + } + + private abiInputChange = (abi: any, values: any, name: string, params: any, setFieldValue: any, index: number) => { + const abiValues = []; + for (const [i, abiInput] of params.entries()) { + abiValues.push({ type: abiInput.type, value: values[i] }); + } + + const encodedData = encodeABI(abi, name, abiValues); + setFieldValue(`contracts.${index}.callData`, encodedData); + } + + public render(): RenderOutput { + const { handleClose } = this.props; + const { loading, addContractStatus, userContracts } = this.state; + + const whitelistedContractsOptions = whitelistedContracts.map((address, index) => { + return ; + }); + + const userContractsOptions = userContracts.map((address, index) => { + return ; + }); + + const fnDescription = () => (Short description of the proposal.
  • What are you proposing to do?
  • Why is it important?
  • How much will it cost the DAO?
  • When do you plan to deliver the work?
); + + return ( +
+ { + const errors: any = {}; + + const require = (name: string) => { + if (!(values as any)[name]) { + errors[name] = "Required"; + } + }; + + if (values.title.length > 120) { + errors.title = "Title is too long (max 120 characters)"; + } + + if (!isValidUrl(values.url)) { + errors.url = "Invalid URL"; + } + + require("title"); + require("description"); + + return errors; + }} + onSubmit={this.handleSubmit} + // eslint-disable-next-line react/jsx-no-bind + render={({ + errors, + touched, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handleSubmit, + isSubmitting, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setFieldTouched, + setFieldValue, + handleBlur, + values, + }: FormikProps) => +
+ + + + + + + + + { setFieldValue("description", value); }} + id="descriptionInput" + placeholder="Describe your proposal in greater detail" + name="description" + className={touched.description && errors.description ? css.error : null} + /> + + + + + +
+ +
+ + + + + + +
+ + { setFieldValue("addContract", e.target.value); this.verifyContract(e.target.value); }} + disabled={loading ? true : false} + /> + {loading ? "Loading..." : addContractStatus.error === "ABI_DATA_ERROR" ? +
+ {addContractStatus.message} + contract +
: addContractStatus.message} +
+ + + {({ insert, remove, push }) => ( // eslint-disable-line @typescript-eslint/no-unused-vars +
+ { + values.contracts.length > 0 && values.contracts.map((contract: any, index: any) => ( +
+ {/* eslint-disable-next-line react/jsx-no-bind */} + {values.contracts.length > 1 && } + +
+ + +
+ +
+ + { setFieldValue(`contracts.${index}.address`, e.target.value); await this.getContractABI(e.target.value, setFieldValue, index); }} + component="select" + name={`contracts.${index}.address`} + type="text" + validate={requireValue} + > + + + {whitelistedContractsOptions} + + {userContractsOptions.length > 0 && + + {userContractsOptions} + + } + +
+ + { + values.contracts[index].address !== "" && +
+ + {values.contracts[index]?.methods?.length === 0 ? "Loading..." : + { setFieldValue(`contracts.${index}.method`, e.target.value); this.getMethodInputs(values.contracts[index].abi, values.contracts[index]?.methods, e.target.value, setFieldValue, index); }} + component="select" + name={`contracts.${index}.method`} + type="text" + validate={requireValue} + > + + {values.contracts[index]?.methods?.map((method: any, j: any) => ( + + ))} + } +
+ } + + { + values.contracts[index].method !== "" && +
+ {values.contracts[index].params.map((param: any, i: number) => ( + + + { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split('(')[0], values.contracts[index].params, setFieldValue, index); }} + // eslint-disable-next-line react/jsx-no-bind + validate={(e: any) => validateParam(param.type, e)} + /> + + ))} +
+ } + + +
{values.contracts[index].callData}
+
+ )) + } + +
+ ) + } +
+ +
+ + + + + + + +
+ + } + /> +
+ ); + } +} + +export default connect(null, mapDispatchToProps)(CreateGenericMultiCallScheme); diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts new file mode 100644 index 000000000..8696d4d84 --- /dev/null +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -0,0 +1,49 @@ +import { isHex } from "web3-utils"; +import { isAddress } from "lib/util"; + +/** + * Given a value returns error message in case value is less than 0 or no value provided + * @param {any} value + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const requireValue = (value: any): string => { + let error; + if (value === "") { + error = "Required"; + } else if (value < 0) { + error = "Please enter a non-negative value"; + } + return error; +}; + +/** + * Given ABI method param type (address, byets32, unit256, ...) and it's value, returns error message in case validation fails or no value provided + * @param {string} type + * @param {string} value + */ +export const validateParam = (type: string, value: string): string => { + let error; + if (!value) { + error = "Required"; + } + else { + switch (true) { + case type.includes("address"): + if (!isAddress(value)) { + error = "Please enter a valid address"; + } + break; + case type.includes("byte"): + if (!isHex(value)) { + error = "Must be a hex value"; + } + break; + case type.includes("uint"): + if (/^\d+$/.test(value) === false) { + error = "Must contain only digits"; + } + break; + } + } + return error; +}; \ No newline at end of file From e105708160ee9c3bc1f88d135d1e4585ba93d746 Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Fri, 16 Oct 2020 18:25:32 +0300 Subject: [PATCH 02/41] Update Validators.ts Add empty line to EOF --- src/components/Proposal/Create/SchemeForms/Validators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index 8696d4d84..d922296eb 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -46,4 +46,4 @@ export const validateParam = (type: string, value: string): string => { } } return error; -}; \ No newline at end of file +}; From b8cac3b592b98d9f950f0584387444c8f7a7b807 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Sat, 17 Oct 2020 16:36:03 +0300 Subject: [PATCH 03/41] fix lint errors --- .../Proposal/Create/SchemeForms/ABIService.ts | 8 ++-- .../CreateGenericMultiCallProposal.tsx | 42 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts index a67fb9caf..ffbbff68a 100644 --- a/src/components/Proposal/Create/SchemeForms/ABIService.ts +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -5,13 +5,13 @@ import axios from "axios"; import { isAddress, targetedNetwork } from "lib/util"; export interface IAllowedAbiItem extends AbiItem { - name: string - type: "function" + name: string; + type: "function"; } export interface IAbiItemExtended extends IAllowedAbiItem { - action: string - methodSignature: string + action: string; + methodSignature: string; } /** diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 1636e8d7b..11020b5fb 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -14,7 +14,7 @@ import * as css from "../CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; -import { requireValue, validateParam } from "./Validators"; +import { requireValue, validateParam } from "./Validators"; const whitelistedContracts = [ "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf", @@ -35,16 +35,16 @@ interface IDispatchProps { } interface IAddContractStatus { - error: string - message: string + error: string; + message: string; } interface IStateProps { - loading: boolean + loading: boolean; tags: Array; - addContractStatus: IAddContractStatus - whitelistedContracts: Array - userContracts: Array + addContractStatus: IAddContractStatus; + whitelistedContracts: Array; + userContracts: Array; } type IProps = IExternalProps & IDispatchProps; @@ -55,21 +55,21 @@ const mapDispatchToProps = { }; interface IContract { - address: string // Contract address - value: number // Token to send with the proposal - abi: any // Contract ABI data - methods: any // ABI write methods - method: string // Selected method - params: any // Method params - values: any // Params values - callData: string // The encoded data + address: string; // Contract address + value: number; // Token to send with the proposal + abi: any; // Contract ABI data + methods: any; // ABI write methods + method: string; // Selected method + params: any; // Method params + values: any; // Params values + callData: string; // The encoded data } interface IFormValues { description: string; title: string; url: string; - contracts: Array + contracts: Array; [key: string]: any; } @@ -105,7 +105,7 @@ class CreateGenericMultiCallScheme extends React.Component tags: this.initialFormValues.tags, addContractStatus: { error: "", message: "" }, whitelistedContracts: whitelistedContracts, - userContracts: [] + userContracts: [], }; } @@ -376,7 +376,7 @@ class CreateGenericMultiCallScheme extends React.Component { setFieldValue(`contracts.${index}.address`, e.target.value); await this.getContractABI(e.target.value, setFieldValue, index); }} @@ -417,7 +417,7 @@ class CreateGenericMultiCallScheme extends React.Component {values.contracts[index]?.methods?.length === 0 ? "Loading..." : name={`contracts.${index}.values.${i}`} placeholder={param.placeholder} // eslint-disable-next-line react/jsx-no-bind - onBlur={(e: any) => { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split('(')[0], values.contracts[index].params, setFieldValue, index); }} + onBlur={(e: any) => { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split("(")[0], values.contracts[index].params, setFieldValue, index); }} // eslint-disable-next-line react/jsx-no-bind validate={(e: any) => validateParam(param.type, e)} /> From dd4a9325fc6dfeb7d7431d4f175c72ead363e255 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Sun, 18 Oct 2020 11:18:08 +0300 Subject: [PATCH 04/41] better validations handling ; added interfaces and some minor code improvments ; using isHexStrict instead of isHex --- .../Proposal/Create/SchemeForms/ABIService.ts | 59 ++++--------------- .../CreateGenericMultiCallProposal.tsx | 31 +++++----- .../Proposal/Create/SchemeForms/Validators.ts | 4 +- 3 files changed, 26 insertions(+), 68 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts index ffbbff68a..3bfb3c5ad 100644 --- a/src/components/Proposal/Create/SchemeForms/ABIService.ts +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -1,8 +1,8 @@ -import { AbiItem, isHex } from "web3-utils"; +import { AbiItem } from "web3-utils"; import { SortService } from "lib/sortService"; const Web3 = require("web3"); import axios from "axios"; -import { isAddress, targetedNetwork } from "lib/util"; +import { targetedNetwork } from "lib/util"; export interface IAllowedAbiItem extends AbiItem { name: string; @@ -73,36 +73,6 @@ export const extractABIMethods = (abi: AbiItem[]): IAbiItemExtended[] => { .sort(({ name: a }, { name: b }) => SortService.evaluateString(a, b, 1)); }; -/** - * Given array of ABI parameters objects, returns true if all values are valid. - * Data example: - * [{ type: address, value: "0x25112235dDA2F775c81f0AA37a2BaeA21B470f65" }] - * @param {array} data - * @returns {boolean} - */ -export const validateABIInputs = (data: Array): boolean => { - for (const input of data) { - switch (true) { - case input.type.includes("address"): - if (!isAddress(input.value)) { - return false; - } - break; - case input.type.includes("byte"): - if (!isHex(input.value)) { - return false; - } - break; - case input.type.includes("uint"): - if (/^\d+$/.test(input.value) === false) { - return false; - } - break; - } - } - return true; -}; - /** * Given contract address returns it's ABI data. * @param {string} contractAddress @@ -123,30 +93,21 @@ export const getABIByContract = async (contractAddress: string): Promise, name: string, data: any[]): string => { +export const encodeABI = (abi: Array, name: string, values: Array): string => { const web3 = new Web3; const contract = new web3.eth.Contract(abi); const interfaceABI = contract.options.jsonInterface; - if (validateABIInputs(data)) { - const values = []; - for (const input of data) { - values.push(input.value); - } - let methodToSend; - for (const method of interfaceABI){ - if (method.name === name) { - methodToSend = method; - } - } - return web3.eth.abi.encodeFunctionCall(methodToSend, values); + try { + const methodToSend = interfaceABI.filter((method: any) => method.name === name && method.inputs.length === values.length); + return web3.eth.abi.encodeFunctionCall(methodToSend[0], values); + } catch (error) { + return error.reason; } - - return ""; }; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 11020b5fb..36650d592 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -34,8 +34,10 @@ interface IDispatchProps { showNotification: typeof showNotification; } +type AddContractError = "NOT_VALID_ADDRESS" | "CONTRACT_EXIST" | "ABI_DATA_ERROR" | ""; + interface IAddContractStatus { - error: string; + error: AddContractError; message: string; } @@ -86,16 +88,17 @@ class CreateGenericMultiCallScheme extends React.Component title: "", url: "", tags: [], + addContract: "", userContracts: [], contracts: [ { address: "", value: 0, - abi: [] as any, - methods: [] as any, + abi: [], + methods: [], method: "", - params: [] as any, - values: [] as any, + params: [], + values: [], callData: "", }, ], @@ -163,7 +166,7 @@ class CreateGenericMultiCallScheme extends React.Component * @param {string} contractToCall */ private verifyContract = async (contractToCall: string) => { - const addContractStatus = { + const addContractStatus: IAddContractStatus = { error: "", message: "", }; @@ -217,13 +220,8 @@ class CreateGenericMultiCallScheme extends React.Component } } - private abiInputChange = (abi: any, values: any, name: string, params: any, setFieldValue: any, index: number) => { - const abiValues = []; - for (const [i, abiInput] of params.entries()) { - abiValues.push({ type: abiInput.type, value: values[i] }); - } - - const encodedData = encodeABI(abi, name, abiValues); + private abiInputChange = (abi: any, values: any, name: string, setFieldValue: any, index: number) => { + const encodedData = encodeABI(abi, name, values); setFieldValue(`contracts.${index}.callData`, encodedData); } @@ -437,21 +435,20 @@ class CreateGenericMultiCallScheme extends React.Component { values.contracts[index].method !== "" && -
+
{values.contracts[index].params.map((param: any, i: number) => ( - + { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split("(")[0], values.contracts[index].params, setFieldValue, index); }} + onBlur={(e: any) => { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split("(")[0], setFieldValue, index); }} // eslint-disable-next-line react/jsx-no-bind validate={(e: any) => validateParam(param.type, e)} /> diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index d922296eb..67416f80a 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -1,4 +1,4 @@ -import { isHex } from "web3-utils"; +import { isHexStrict } from "web3-utils"; import { isAddress } from "lib/util"; /** @@ -34,7 +34,7 @@ export const validateParam = (type: string, value: string): string => { } break; case type.includes("byte"): - if (!isHex(value)) { + if (!isHexStrict(value)) { error = "Must be a hex value"; } break; From 03300bf217a2df2150ed1784cf3506554928ecbc Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 21 Oct 2020 18:31:19 +0300 Subject: [PATCH 05/41] Update migration version to support generic multi-call ; Fix scroll to container with no sidebar ; Added Generic Multi Call scheme to the known plugins list ; Better UI to the add contract field --- docker-compose.yml | 6 +-- package-lock.json | 52 ++++++++----------- package.json | 2 +- .../Proposal/Create/CreateProposal.scss | 3 +- .../Proposal/Create/CreateProposalPage.tsx | 3 ++ .../CreateGenericMultiCallProposal.tsx | 15 +++--- src/lib/schemeUtils.ts | 2 + 7 files changed, 41 insertions(+), 42 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index df8e4fbb6..a57ad4721 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,18 +47,18 @@ services: GRAPH_MAX_IPFS_FILE_BYTES: 900000 ipfs: - image: daostack/test-env-ipfs:3.0.38 + image: daostack/test-env-ipfs:3.0.39 ports: - 5001:5001 postgres: - image: daostack/test-env-postgres:3.0.38 + image: daostack/test-env-postgres:3.0.39 ports: - 9432:5432 environment: POSTGRES_PASSWORD: 'letmein' ganache: - image: daostack/test-env-ganache:3.0.38 + image: daostack/test-env-ganache:3.0.39 ports: - 8545:8545 diff --git a/package-lock.json b/package-lock.json index 6ca1d76fb..523aafeb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1800,12 +1800,12 @@ } }, "@daostack/arc": { - "version": "0.0.1-rc.41", - "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.1-rc.41.tgz", - "integrity": "sha512-U/M+ZhEV/4f/+zwZOjYNv67bbP386qSvas8LUB31bV0nzxQKhjG+8JzKKmxpCqz0ixLuzMG7/pJcn3dG2MF5Kg==", + "version": "0.0.1-rc.47", + "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.1-rc.47.tgz", + "integrity": "sha512-ElzdZPmZUMgQS+RdzgQc+MTjl3w3C55i50YhYZC41hiqQX1d/w6LhSUHg59L8UCPnEdmOlhoDpoE/91M1Hc5pg==", "dev": true, "requires": { - "@daostack/infra": "0.0.1-rc.15", + "@daostack/infra": "0.0.1-rc.19", "math": "0.0.3", "openzeppelin-solidity": "2.4.0", "truffle-flattener": "^1.4.2" @@ -1853,30 +1853,22 @@ } }, "@daostack/infra": { - "version": "0.0.1-rc.15", - "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.1-rc.15.tgz", - "integrity": "sha512-th/nb1okI7qDNxMCILDdX8I+31zIDvgfDJlPhe/dTPUAC3Zr55WCcrUa8oS7cOb6G1ki7OdGycDCbfnu2ndWgg==", + "version": "0.0.1-rc.19", + "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.1-rc.19.tgz", + "integrity": "sha512-Izvhc+WSzo6FAqPpuO7trrpd0Eeawf2TvqIAuCcCHoAbQ4IYYdtcF127IMhdFWJqXYadIYsDnqTPdYzX0xPC0w==", "dev": true, "requires": { "ethereumjs-abi": "^0.6.5", - "openzeppelin-solidity": "2.3.0" - }, - "dependencies": { - "openzeppelin-solidity": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", - "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==", - "dev": true - } + "openzeppelin-solidity": "2.4.0" } }, "@daostack/migration": { - "version": "0.0.1-rc.41-v4", - "resolved": "https://registry.npmjs.org/@daostack/migration/-/migration-0.0.1-rc.41-v4.tgz", - "integrity": "sha512-96dq8A8oUSAIcFRTZzxvDEZj8S57UClnG5R088Lj9ORGgdp3xQH6+KRi+lwdTsySSOzvdGzgeG7o2FIAwu75Ig==", + "version": "0.0.1-rc.47-v0", + "resolved": "https://registry.npmjs.org/@daostack/migration/-/migration-0.0.1-rc.47-v0.tgz", + "integrity": "sha512-hiCM2Yc6NMPyMAVCGK4d+C+Bt7G9DUENMRsj9q0K2xOO79UUIs11KRye5CtMrzrN8efLgzPIsrrZIvI9H40dRw==", "dev": true, "requires": { - "@daostack/arc": "0.0.1-rc.41", + "@daostack/arc": "0.0.1-rc.47", "@daostack/arc-hive": "0.0.1-rc.4", "ethereumjs-wallet": "^0.6.3", "fstream": "^1.0.12", @@ -2009,9 +2001,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.32.tgz", - "integrity": "sha512-EUq+cjH/3KCzQHikGnNbWAGe548IFLSm93Vl8xA7EuYEEATiyOVDyEVuGkowL7c9V69FF/RiZSAOCFPApMs/ig==", + "version": "10.17.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.40.tgz", + "integrity": "sha512-3hZT2z2/531A5pc8hYhn1gU5Qb1SIRSgMLQ6zuHA5xtt16lWAxUGprtr8lJuc9zNJMXEIIBWfSnzqBP/4mglpA==", "dev": true } } @@ -3912,9 +3904,9 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, "@solidity-parser/parser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.6.2.tgz", - "integrity": "sha512-kUVUvrqttndeprLoXjI5arWHeiP3uh4XODAKbG+ZaWHCVQeelxCbnXBeWxZ2BPHdXgH0xR9dU1b916JhDhbgAA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.8.1.tgz", + "integrity": "sha512-DF7H6T8I4lo2IZOE2NZwt3631T8j1gjpQLjmvY2xBNK50c4ltslR4XPKwT6RkeSd4+xCAK0GHC/k7sbRDBE4Yw==", "dev": true }, "@stablelib/utf8": { @@ -58781,13 +58773,13 @@ } }, "truffle-flattener": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.4.4.tgz", - "integrity": "sha512-S/WmvubzlUj1mn56wEI6yo1bmPpKDNdEe5rtyVC1C5iNfZWobD/V69pAYI15IBDJrDqUyh+iXgpTkzov50zpQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.5.0.tgz", + "integrity": "sha512-vmzWG/L5OXoNruMV6u2l2IaheI091e+t+fFCOR9sl46EE3epkSRIwGCmIP/EYDtPsFBIG7e6exttC9/GlfmxEQ==", "dev": true, "requires": { "@resolver-engine/imports-fs": "^0.2.2", - "@solidity-parser/parser": "^0.6.0", + "@solidity-parser/parser": "^0.8.0", "find-up": "^2.1.0", "mkdirp": "^1.0.4", "tsort": "0.0.1" diff --git a/package.json b/package.json index a1a2ff264..a2f66591b 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ }, "devDependencies": { "@babel/plugin-syntax-dynamic-import": "^7.7.4", - "@daostack/migration": "0.0.1-rc.41-v4", + "@daostack/migration": "0.0.1-rc.47-v0", "@storybook/addon-info": "^5.0.10", "@storybook/react": "^5.0.10", "@types/chai": "^4.1.7", diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 730147398..f540761dd 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -37,7 +37,6 @@ fieldset { left: 50%; transform: translate(-50%, -50%); box-shadow: $shadow-3; - height: calc(100% - 100px); .header { background-color: $navy; @@ -406,7 +405,7 @@ fieldset { .containerNoSidebar { width: 562px; overflow-y: auto; - height: calc(100% - 90px); + max-height: calc(100vh - 200px); padding: 20px 30px 30px 30px; } diff --git a/src/components/Proposal/Create/CreateProposalPage.tsx b/src/components/Proposal/Create/CreateProposalPage.tsx index 35cc46d80..8f343248c 100644 --- a/src/components/Proposal/Create/CreateProposalPage.tsx +++ b/src/components/Proposal/Create/CreateProposalPage.tsx @@ -3,6 +3,7 @@ import { getArc } from "arc"; import CreateKnownGenericSchemeProposal from "components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal"; import CreateSchemeRegistrarProposal from "components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal"; import CreateUnknownGenericSchemeProposal from "components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal"; +import CreateGenericMultiCallProposal from "components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal"; import Loading from "components/Shared/Loading"; import withSubscription, { ISubscriptionProps } from "components/Shared/withSubscription"; import { GenericSchemeRegistry } from "genericSchemeRegistry"; @@ -138,6 +139,8 @@ class CreateProposalPage extends React.Component { } else { createSchemeComponent = ; } + } else if (scheme.name === "GenericSchemeMultiCall") { + createSchemeComponent = ; } return ( diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 36650d592..cb2fe17ba 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -18,9 +18,9 @@ import { requireValue, validateParam } from "./Validators"; const whitelistedContracts = [ "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf", - "0x5C5cbaC45b18F990AbcC4b890Bf98d82e9ee58A0", - "0x24832a7A5408B2b18e71136547d308FCF60B6e71", - "0x4E073a7E4a2429eCdfEb1324a472dd8e82031F34", + "0x2c2c1B134F735B3C49936f7a46CF7658458039cc", + "0x437C909C44fc8d47dB20Ff4bead90A59905Ce451", + "0xb5f6221ea3c0Cb3FC7a3D0d36420311dc69B2f3c", ]; interface IExternalProps { @@ -171,7 +171,10 @@ class CreateGenericMultiCallScheme extends React.Component message: "", }; - if (!isAddress(contractToCall)) { + if (contractToCall === "") { + addContractStatus.error = ""; + addContractStatus.message = ""; + } else if (!isAddress(contractToCall)) { addContractStatus.error = "NOT_VALID_ADDRESS"; addContractStatus.message = "Please enter a valid address"; } else if (this.state.whitelistedContracts.includes(contractToCall) || this.state.userContracts.includes(contractToCall)) { @@ -344,10 +347,10 @@ class CreateGenericMultiCallScheme extends React.Component
Date: Thu, 22 Oct 2020 13:35:28 +0300 Subject: [PATCH 06/41] Fix tests ; unique encoded data scss class name for multi call ; better text for add custom contract --- src/components/Proposal/Create/CreateProposal.scss | 2 +- .../Create/SchemeForms/CreateGenericMultiCallProposal.tsx | 6 +++--- src/genericSchemeRegistry/schemes/ENSRegistry.json | 3 ++- src/genericSchemeRegistry/schemes/RegistryLookup.json | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index f540761dd..2b912fb62 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -140,7 +140,7 @@ fieldset { position: relative; } - .encodedData { + .encodedDataMultiCall { overflow-wrap: break-word; margin: 5px 0 5px 0px; border: 1px solid; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index cb2fe17ba..62b48fc01 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -347,7 +347,7 @@ class CreateGenericMultiCallScheme extends React.Component
{whitelistedContractsOptions} {userContractsOptions.length > 0 && - + {userContractsOptions} } @@ -461,7 +461,7 @@ class CreateGenericMultiCallScheme extends React.Component } -
{values.contracts[index].callData}
+
{values.contracts[index].callData}
)) } diff --git a/src/genericSchemeRegistry/schemes/ENSRegistry.json b/src/genericSchemeRegistry/schemes/ENSRegistry.json index 88270ca3b..d45f955dc 100644 --- a/src/genericSchemeRegistry/schemes/ENSRegistry.json +++ b/src/genericSchemeRegistry/schemes/ENSRegistry.json @@ -11,7 +11,8 @@ ], "private": [ "0x6b85757e0a41f837dbe0b6b4b5157858b62d9a66", - "0xd931b5ef6afb7995d01be71f69a7fcbbebe4f41f" + "0xd931b5ef6afb7995d01be71f69a7fcbbebe4f41f", + "0x99703166acf4b5c6ca9e878ca62feb0df19d7ff1" ] }, "actions": [ diff --git a/src/genericSchemeRegistry/schemes/RegistryLookup.json b/src/genericSchemeRegistry/schemes/RegistryLookup.json index 423411355..a69db86bf 100644 --- a/src/genericSchemeRegistry/schemes/RegistryLookup.json +++ b/src/genericSchemeRegistry/schemes/RegistryLookup.json @@ -13,7 +13,8 @@ ], "private": [ "0x9e32124cbecc2460e54735d356c0cb5ca8c8aa44", - "0x38d542f47b1b949146e3961ecd87872fdea49679" + "0x38d542f47b1b949146e3961ecd87872fdea49679", + "0x04197b7f05c53d81efce85af1bc31bdd21dacc8b" ] }, "actions": [ From 5f8eb1ea30fb28e6c34ebb5de4241883bc7d04a9 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Fri, 23 Oct 2020 11:37:34 +0300 Subject: [PATCH 07/41] Get the contracts whitelist from subgraph ; Fix scrolling in ContainerWithSidebar --- src/components/Proposal/Create/CreateProposal.scss | 2 +- .../Proposal/Create/CreateProposalPage.tsx | 2 +- .../SchemeForms/CreateGenericMultiCallProposal.tsx | 12 +++--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 2b912fb62..283241d8d 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -416,7 +416,7 @@ fieldset { content: ""; clear: both; overflow: auto; - height: calc(100% - 40px); + max-height: calc(100vh - 200px); .sidebar { width: 199px; diff --git a/src/components/Proposal/Create/CreateProposalPage.tsx b/src/components/Proposal/Create/CreateProposalPage.tsx index 8f343248c..645ff323b 100644 --- a/src/components/Proposal/Create/CreateProposalPage.tsx +++ b/src/components/Proposal/Create/CreateProposalPage.tsx @@ -140,7 +140,7 @@ class CreateProposalPage extends React.Component { createSchemeComponent = ; } } else if (scheme.name === "GenericSchemeMultiCall") { - createSchemeComponent = ; + createSchemeComponent = ; } return ( diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 62b48fc01..dff236a78 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -16,17 +16,11 @@ import HelpButton from "components/Shared/HelpButton"; import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; import { requireValue, validateParam } from "./Validators"; -const whitelistedContracts = [ - "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf", - "0x2c2c1B134F735B3C49936f7a46CF7658458039cc", - "0x437C909C44fc8d47dB20Ff4bead90A59905Ce451", - "0xb5f6221ea3c0Cb3FC7a3D0d36420311dc69B2f3c", -]; - interface IExternalProps { daoAvatarAddress: string; handleClose: () => any; scheme: ISchemeState; + whitelistedContracts: Array } interface IDispatchProps { @@ -107,7 +101,7 @@ class CreateGenericMultiCallScheme extends React.Component loading: false, tags: this.initialFormValues.tags, addContractStatus: { error: "", message: "" }, - whitelistedContracts: whitelistedContracts, + whitelistedContracts: this.props.whitelistedContracts, userContracts: [], }; } @@ -232,7 +226,7 @@ class CreateGenericMultiCallScheme extends React.Component const { handleClose } = this.props; const { loading, addContractStatus, userContracts } = this.state; - const whitelistedContractsOptions = whitelistedContracts.map((address, index) => { + const whitelistedContractsOptions = this.state.whitelistedContracts.map((address, index) => { return ; }); From c4b6347e083dd22e6c2dc8d4d19c4c153e92f544 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Sat, 24 Oct 2020 13:39:10 +0300 Subject: [PATCH 08/41] fix lint error --- .../Create/SchemeForms/CreateGenericMultiCallProposal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index dff236a78..8db2034e7 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -20,7 +20,7 @@ interface IExternalProps { daoAvatarAddress: string; handleClose: () => any; scheme: ISchemeState; - whitelistedContracts: Array + whitelistedContracts: Array; } interface IDispatchProps { From ad2de64601b593af47227c1833a6fb2046e3eaef Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Sun, 25 Oct 2020 17:05:32 +0200 Subject: [PATCH 09/41] added generic scheme multi call summary ; updated test env v40 ; use latest arc.js with multicall support --- docker-compose.yml | 6 +- package-lock.json | 6 +- package.json | 2 +- .../CreateGenericMultiCallProposal.tsx | 2 +- .../ProposalSummary/ProposalSummary.scss | 7 +++ .../ProposalSummary/ProposalSummary.tsx | 4 +- .../ProposalSummaryMultiCallGenericScheme.tsx | 62 +++++++++++++++++++ 7 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx diff --git a/docker-compose.yml b/docker-compose.yml index a57ad4721..ff8d28800 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,18 +47,18 @@ services: GRAPH_MAX_IPFS_FILE_BYTES: 900000 ipfs: - image: daostack/test-env-ipfs:3.0.39 + image: daostack/test-env-ipfs:3.0.40 ports: - 5001:5001 postgres: - image: daostack/test-env-postgres:3.0.39 + image: daostack/test-env-postgres:3.0.40 ports: - 9432:5432 environment: POSTGRES_PASSWORD: 'letmein' ganache: - image: daostack/test-env-ganache:3.0.39 + image: daostack/test-env-ganache:3.0.40 ports: - 8545:8545 diff --git a/package-lock.json b/package-lock.json index 523aafeb2..d4daec0b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1829,9 +1829,9 @@ } }, "@daostack/arc.js": { - "version": "0.2.74", - "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.74.tgz", - "integrity": "sha512-M3DbrmqUSYh+BlEPiuhaI1A+HZRwwjL2O/zEVOukPaxjlvImzMuKFAiH4DaXmOjlOkNrl+LJevvHc8J+3n5dAg==", + "version": "0.2.75", + "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.75.tgz", + "integrity": "sha512-mFh2rw7D1zcs+5zbJP4TfLpLBQJ1VpYPXrHRIEmXQc5i0X1lv7aEvmQckOVHJO9+zcTNQEBp2Zo0ZZCez495xg==", "requires": { "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", diff --git a/package.json b/package.json index a2f66591b..d4ddbaa4d 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "dependencies": { "3box": "1.17.1", "@burner-wallet/burner-connect-provider": "^0.1.1", - "@daostack/arc.js": "0.2.74", + "@daostack/arc.js": "0.2.75", "@dorgtech/daocreator-ui": "^1.0.13", "@fortawesome/fontawesome-svg-core": "^1.2.10", "@fortawesome/free-brands-svg-icons": "^5.6.1", diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 8db2034e7..a14611999 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -101,7 +101,7 @@ class CreateGenericMultiCallScheme extends React.Component loading: false, tags: this.initialFormValues.tags, addContractStatus: { error: "", message: "" }, - whitelistedContracts: this.props.whitelistedContracts, + whitelistedContracts: this.props.whitelistedContracts ?? [], userContracts: [], }; } diff --git a/src/components/Proposal/ProposalSummary/ProposalSummary.scss b/src/components/Proposal/ProposalSummary/ProposalSummary.scss index 57762392f..c5bf1e7b9 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummary.scss +++ b/src/components/Proposal/ProposalSummary/ProposalSummary.scss @@ -149,6 +149,13 @@ height: 14px; } } + + .multiCallContractDetails { + border-bottom: 1px solid gray; + } + .multiCallContractDetails:last-child { + border-bottom: none; + } } .summaryDetails ul { diff --git a/src/components/Proposal/ProposalSummary/ProposalSummary.tsx b/src/components/Proposal/ProposalSummary/ProposalSummary.tsx index 71a4eb056..d4f41d661 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummary.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummary.tsx @@ -8,6 +8,7 @@ import ProposalSummaryContributionReward from "./ProposalSummaryContributionRewa import ProposalSummaryKnownGenericScheme from "./ProposalSummaryKnownGenericScheme"; import ProposalSummarySchemeRegistrar from "./ProposalSummarySchemeRegistrar"; import ProposalSummaryUnknownGenericScheme from "./ProposalSummaryUnknownGenericScheme"; +import ProposalSummaryMultiCallGenericScheme from "./ProposalSummaryMultiCallGenericScheme"; interface IProps { beneficiaryProfile?: IProfileState; @@ -41,7 +42,8 @@ export default class ProposalSummary extends React.Component { } else { return ; } - + } else if (proposal.type === IProposalType.GenericSchemeMultiCall) { + return ; } else { return
Unknown proposal type
; } diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx new file mode 100644 index 000000000..5ca2dd129 --- /dev/null +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -0,0 +1,62 @@ +import { IDAOState, IProposalState } from "@daostack/arc.js"; +import classNames from "classnames"; +import { linkToEtherScan, formatTokens, baseTokenName, truncateWithEllipses } from "lib/util"; +import * as React from "react"; +import { IProfileState } from "reducers/profilesReducer"; +import * as css from "./ProposalSummary.scss"; +import BN = require("bn.js"); +import CopyToClipboard from "components/Shared/CopyToClipboard"; + +interface IProps { + beneficiaryProfile?: IProfileState; + detailView?: boolean; + dao: IDAOState; + proposal: IProposalState; + transactionModal?: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface IState { +} + +export default class ProposalSummary extends React.Component { + + constructor(props: IProps) { + super(props); + } + + public render(): RenderOutput { + const { proposal, detailView, transactionModal } = this.props; + const tokenAmountToSend = new BN(proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b)))); + const proposalSummaryClass = classNames({ + [css.detailView]: detailView, + [css.transactionModal]: transactionModal, + [css.proposalSummary]: true, + [css.withDetails]: true, + }); + return ( +
+ + Generic multicall + {tokenAmountToSend.gtn(0) &&
> Sending {formatTokens(tokenAmountToSend)} {baseTokenName()} <
} +
+ {detailView && +
+ { + proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => ( +
+

Contract:

+ {
{contract}
} +

Sending to contract:

+
{formatTokens(new BN(proposal.genericSchemeMultiCall.values[index]))} {baseTokenName()}
+

Raw call data:

+
{truncateWithEllipses(proposal.genericSchemeMultiCall.callsData[index], 66)}
+
+ )) + } +
+ } +
+ ); + } +} From 15ef1982fc055b0f0ceb18a78c8ac7654e5f8d20 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 26 Oct 2020 17:18:56 +0200 Subject: [PATCH 10/41] update subgraph endpoints --- src/subgraph_endpoints.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/subgraph_endpoints.json b/src/subgraph_endpoints.json index e74b22045..d5bd15883 100644 --- a/src/subgraph_endpoints.json +++ b/src/subgraph_endpoints.json @@ -1,12 +1,12 @@ { - "http_main": "https://api.thegraph.com/subgraphs/name/daostack/v39_8", - "ws_main": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8", - "http_rinkeby": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_rinkeby", - "ws_rinkeby": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_rinkeby", - "http_kovan": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_kovan", - "ws_kovan": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_kovan", - "http_xdai": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_xdai", - "ws_xdai": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_xdai", + "http_main": "https://api.thegraph.com/subgraphs/name/daostack/v40_0", + "ws_main": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0", + "http_rinkeby": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_rinkeby", + "ws_rinkeby": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_rinkeby", + "http_kovan": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_kovan", + "ws_kovan": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_kovan", + "http_xdai": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_xdai", + "ws_xdai": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_xdai", "http_ganache": "http://127.0.0.1:8000/subgraphs/name/daostack", "ws_ganache": "ws://127.0.0.1:8001/subgraphs/name/daostack" } From b6d02e4e55673e95f94a6e45be4e0d81c9c76190 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 27 Oct 2020 10:38:22 +0200 Subject: [PATCH 11/41] remove use of local storage function in the test --- test/integration/proposal.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/proposal.ts b/test/integration/proposal.ts index 10e659e19..a7b13db15 100644 --- a/test/integration/proposal.ts +++ b/test/integration/proposal.ts @@ -1,5 +1,5 @@ import * as uuid from "uuid"; -import { getContractAddresses, hideCookieAcceptWindow, hideTrainingTooltips, gotoDaoSchemes } from "./utils"; +import { getContractAddresses, hideCookieAcceptWindow, gotoDaoSchemes } from "./utils"; describe("Proposals", () => { let daoAddress: string; @@ -8,7 +8,6 @@ describe("Proposals", () => { before(() => { addresses = getContractAddresses(); daoAddress = addresses.dao.Avatar.toLowerCase(); - hideTrainingTooltips(); }); it("Create a proposal, vote for it, stake on it", async () => { From daf26077366a75031e1fa9771f0cb784396c03c0 Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Tue, 27 Oct 2020 16:56:12 -0400 Subject: [PATCH 12/41] upgrade subgraph --- docker-compose.yml | 6 ++-- package-lock.json | 58 ++++++++++++++++--------------------- package.json | 4 +-- src/subgraph_endpoints.json | 16 +++++----- 4 files changed, 38 insertions(+), 46 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index df8e4fbb6..ff8d28800 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,18 +47,18 @@ services: GRAPH_MAX_IPFS_FILE_BYTES: 900000 ipfs: - image: daostack/test-env-ipfs:3.0.38 + image: daostack/test-env-ipfs:3.0.40 ports: - 5001:5001 postgres: - image: daostack/test-env-postgres:3.0.38 + image: daostack/test-env-postgres:3.0.40 ports: - 9432:5432 environment: POSTGRES_PASSWORD: 'letmein' ganache: - image: daostack/test-env-ganache:3.0.38 + image: daostack/test-env-ganache:3.0.40 ports: - 8545:8545 diff --git a/package-lock.json b/package-lock.json index 6ca1d76fb..11ba85a7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1800,12 +1800,12 @@ } }, "@daostack/arc": { - "version": "0.0.1-rc.41", - "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.1-rc.41.tgz", - "integrity": "sha512-U/M+ZhEV/4f/+zwZOjYNv67bbP386qSvas8LUB31bV0nzxQKhjG+8JzKKmxpCqz0ixLuzMG7/pJcn3dG2MF5Kg==", + "version": "0.0.1-rc.47", + "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.1-rc.47.tgz", + "integrity": "sha512-ElzdZPmZUMgQS+RdzgQc+MTjl3w3C55i50YhYZC41hiqQX1d/w6LhSUHg59L8UCPnEdmOlhoDpoE/91M1Hc5pg==", "dev": true, "requires": { - "@daostack/infra": "0.0.1-rc.15", + "@daostack/infra": "0.0.1-rc.19", "math": "0.0.3", "openzeppelin-solidity": "2.4.0", "truffle-flattener": "^1.4.2" @@ -1829,9 +1829,9 @@ } }, "@daostack/arc.js": { - "version": "0.2.74", - "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.74.tgz", - "integrity": "sha512-M3DbrmqUSYh+BlEPiuhaI1A+HZRwwjL2O/zEVOukPaxjlvImzMuKFAiH4DaXmOjlOkNrl+LJevvHc8J+3n5dAg==", + "version": "0.2.76", + "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.76.tgz", + "integrity": "sha512-ChOGQGGe040wCZAkWjXhBmp1q8pR5ev09dUQNpdzdVc0o7GCYhPdLzJ/T1HkFarpiRvpm4NqNxA0O+2YXltgZg==", "requires": { "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", @@ -1853,30 +1853,22 @@ } }, "@daostack/infra": { - "version": "0.0.1-rc.15", - "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.1-rc.15.tgz", - "integrity": "sha512-th/nb1okI7qDNxMCILDdX8I+31zIDvgfDJlPhe/dTPUAC3Zr55WCcrUa8oS7cOb6G1ki7OdGycDCbfnu2ndWgg==", + "version": "0.0.1-rc.19", + "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.1-rc.19.tgz", + "integrity": "sha512-Izvhc+WSzo6FAqPpuO7trrpd0Eeawf2TvqIAuCcCHoAbQ4IYYdtcF127IMhdFWJqXYadIYsDnqTPdYzX0xPC0w==", "dev": true, "requires": { "ethereumjs-abi": "^0.6.5", - "openzeppelin-solidity": "2.3.0" - }, - "dependencies": { - "openzeppelin-solidity": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", - "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==", - "dev": true - } + "openzeppelin-solidity": "2.4.0" } }, "@daostack/migration": { - "version": "0.0.1-rc.41-v4", - "resolved": "https://registry.npmjs.org/@daostack/migration/-/migration-0.0.1-rc.41-v4.tgz", - "integrity": "sha512-96dq8A8oUSAIcFRTZzxvDEZj8S57UClnG5R088Lj9ORGgdp3xQH6+KRi+lwdTsySSOzvdGzgeG7o2FIAwu75Ig==", + "version": "0.0.1-rc.47-v0", + "resolved": "https://registry.npmjs.org/@daostack/migration/-/migration-0.0.1-rc.47-v0.tgz", + "integrity": "sha512-hiCM2Yc6NMPyMAVCGK4d+C+Bt7G9DUENMRsj9q0K2xOO79UUIs11KRye5CtMrzrN8efLgzPIsrrZIvI9H40dRw==", "dev": true, "requires": { - "@daostack/arc": "0.0.1-rc.41", + "@daostack/arc": "0.0.1-rc.47", "@daostack/arc-hive": "0.0.1-rc.4", "ethereumjs-wallet": "^0.6.3", "fstream": "^1.0.12", @@ -2009,9 +2001,9 @@ }, "dependencies": { "@types/node": { - "version": "10.17.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.32.tgz", - "integrity": "sha512-EUq+cjH/3KCzQHikGnNbWAGe548IFLSm93Vl8xA7EuYEEATiyOVDyEVuGkowL7c9V69FF/RiZSAOCFPApMs/ig==", + "version": "10.17.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.43.tgz", + "integrity": "sha512-F7xV2kxZGb3seVP3UQt3msHcoDCtDi8WNO/UCzNLhRwaYVT4yJO1ndcV+vCTnY+jiAVqyLZq/VJbRE/AhwqEag==", "dev": true } } @@ -3912,9 +3904,9 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" }, "@solidity-parser/parser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.6.2.tgz", - "integrity": "sha512-kUVUvrqttndeprLoXjI5arWHeiP3uh4XODAKbG+ZaWHCVQeelxCbnXBeWxZ2BPHdXgH0xR9dU1b916JhDhbgAA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.8.1.tgz", + "integrity": "sha512-DF7H6T8I4lo2IZOE2NZwt3631T8j1gjpQLjmvY2xBNK50c4ltslR4XPKwT6RkeSd4+xCAK0GHC/k7sbRDBE4Yw==", "dev": true }, "@stablelib/utf8": { @@ -58781,13 +58773,13 @@ } }, "truffle-flattener": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.4.4.tgz", - "integrity": "sha512-S/WmvubzlUj1mn56wEI6yo1bmPpKDNdEe5rtyVC1C5iNfZWobD/V69pAYI15IBDJrDqUyh+iXgpTkzov50zpQw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.5.0.tgz", + "integrity": "sha512-vmzWG/L5OXoNruMV6u2l2IaheI091e+t+fFCOR9sl46EE3epkSRIwGCmIP/EYDtPsFBIG7e6exttC9/GlfmxEQ==", "dev": true, "requires": { "@resolver-engine/imports-fs": "^0.2.2", - "@solidity-parser/parser": "^0.6.0", + "@solidity-parser/parser": "^0.8.0", "find-up": "^2.1.0", "mkdirp": "^1.0.4", "tsort": "0.0.1" diff --git a/package.json b/package.json index a1a2ff264..81bf97705 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "dependencies": { "3box": "1.17.1", "@burner-wallet/burner-connect-provider": "^0.1.1", - "@daostack/arc.js": "0.2.74", + "@daostack/arc.js": "0.2.76", "@dorgtech/daocreator-ui": "^1.0.13", "@fortawesome/fontawesome-svg-core": "^1.2.10", "@fortawesome/free-brands-svg-icons": "^5.6.1", @@ -145,7 +145,7 @@ }, "devDependencies": { "@babel/plugin-syntax-dynamic-import": "^7.7.4", - "@daostack/migration": "0.0.1-rc.41-v4", + "@daostack/migration": "0.0.1-rc.47-v0", "@storybook/addon-info": "^5.0.10", "@storybook/react": "^5.0.10", "@types/chai": "^4.1.7", diff --git a/src/subgraph_endpoints.json b/src/subgraph_endpoints.json index e74b22045..d5bd15883 100644 --- a/src/subgraph_endpoints.json +++ b/src/subgraph_endpoints.json @@ -1,12 +1,12 @@ { - "http_main": "https://api.thegraph.com/subgraphs/name/daostack/v39_8", - "ws_main": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8", - "http_rinkeby": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_rinkeby", - "ws_rinkeby": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_rinkeby", - "http_kovan": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_kovan", - "ws_kovan": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_kovan", - "http_xdai": "https://api.thegraph.com/subgraphs/name/daostack/v39_8_xdai", - "ws_xdai": "wss://api.thegraph.com/subgraphs/name/daostack/v39_8_xdai", + "http_main": "https://api.thegraph.com/subgraphs/name/daostack/v40_0", + "ws_main": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0", + "http_rinkeby": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_rinkeby", + "ws_rinkeby": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_rinkeby", + "http_kovan": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_kovan", + "ws_kovan": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_kovan", + "http_xdai": "https://api.thegraph.com/subgraphs/name/daostack/v40_0_xdai", + "ws_xdai": "wss://api.thegraph.com/subgraphs/name/daostack/v40_0_xdai", "http_ganache": "http://127.0.0.1:8000/subgraphs/name/daostack", "ws_ganache": "ws://127.0.0.1:8001/subgraphs/name/daostack" } From 772789cf6811b95545b42219dd5e8d8b2cfe8979 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Thu, 29 Oct 2020 19:03:20 +0200 Subject: [PATCH 13/41] added ethereum-input-data-decoder package for decoding data in multi-call proposal summery ; Show contract names if available ; buf2hex util function ; getContractName util function --- package-lock.json | 273 ++++++++++++++++-- package.json | 1 + .../Proposal/Create/SchemeForms/ABIService.ts | 19 ++ .../CreateGenericMultiCallProposal.tsx | 6 +- .../ProposalSummary/ProposalSummary.scss | 6 + .../ProposalSummaryMultiCallGenericScheme.tsx | 60 +++- src/lib/util.ts | 24 ++ 7 files changed, 359 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4daec0b9..35e57f71e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8178,8 +8178,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "1.1.1", @@ -12476,7 +12475,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, "requires": { "array-find-index": "^1.0.1" } @@ -12642,6 +12640,15 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -14920,6 +14927,229 @@ } } }, + "ethereum-input-data-decoder": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ethereum-input-data-decoder/-/ethereum-input-data-decoder-0.3.1.tgz", + "integrity": "sha512-zl3QLiHeGG94Ru9uvBeX80ZLj62YXew9JVBUR2QiU2o+wJ9kkRdTAcLD3MJq+4guabj4U2H8s3pBlamrbRzpvA==", + "requires": { + "bn.js": "^4.11.8", + "buffer": "^5.2.1", + "ethereumjs-abi": "^0.6.7", + "ethers": "^4.0.27", + "is-buffer": "^2.0.3", + "meow": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "ethers": { + "version": "4.0.48", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.48.tgz", + "integrity": "sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.5.3", + "hash.js": "1.1.3", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.4", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "ethereumjs-abi": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", @@ -17907,8 +18137,7 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, "hpack.js": { "version": "2.1.6", @@ -46219,8 +46448,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -47410,7 +47638,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -47517,8 +47744,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-or-similar": { "version": "1.5.0", @@ -48226,6 +48452,15 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, "minipass": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", @@ -49247,7 +49482,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -49258,8 +49492,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -53963,6 +54196,11 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + }, "radio-symbol": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/radio-symbol/-/radio-symbol-2.0.0.tgz", @@ -57114,7 +57352,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -57123,14 +57360,12 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -57139,8 +57374,7 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "spdy": { "version": "4.0.2", @@ -59725,7 +59959,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" diff --git a/package.json b/package.json index d4ddbaa4d..8d6547c6d 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "disqus-react": "^1.0.5", "eth-ens-namehash": "^2.0.8", "ethereum-blockies-png": "^0.1.3", + "ethereum-input-data-decoder": "^0.3.1", "ethereumjs-abi": "^0.6.8", "express": "^4.16.4", "formik": "^1.5.2", diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts index 3bfb3c5ad..64655fee3 100644 --- a/src/components/Proposal/Create/SchemeForms/ABIService.ts +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -3,6 +3,7 @@ import { SortService } from "lib/sortService"; const Web3 = require("web3"); import axios from "axios"; import { targetedNetwork } from "lib/util"; +const InputDataDecoder = require("ethereum-input-data-decoder"); export interface IAllowedAbiItem extends AbiItem { name: string; @@ -14,6 +15,13 @@ export interface IAbiItemExtended extends IAllowedAbiItem { methodSignature: string; } +export interface IDecodedData { + method: string; + inputs: Array; + names: Array; + types: Array; +} + /** * Given a contract address returns the URL to fetch the ABI data accroding the current network * @param {string} contractAddress @@ -111,3 +119,14 @@ export const encodeABI = (abi: Array, name: string, values: Array): st return error.reason; } }; + +/** + * Given an ABI data and callData returns the decoded data object + * @param {Array} abi + * @param {string} callData + * @returns {IDecodedData} The decoded data + */ +export const decodeABI = (abi: Array, callData: string): IDecodedData => { + const decoder = new InputDataDecoder(abi); + return decoder.decodeData(callData); +}; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index a14611999..d6b6799db 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -6,7 +6,7 @@ import Analytics from "lib/analytics"; import * as React from "react"; import { connect } from "react-redux"; import { showNotification, NotificationStatus } from "reducers/notifications"; -import { baseTokenName, isValidUrl, isAddress, linkToEtherScan } from "lib/util"; +import { baseTokenName, isValidUrl, isAddress, linkToEtherScan, getContractName } from "lib/util"; import { exportUrl, importUrlValues } from "lib/proposalUtils"; import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; import TrainingTooltip from "components/Shared/TrainingTooltip"; @@ -227,11 +227,11 @@ class CreateGenericMultiCallScheme extends React.Component const { loading, addContractStatus, userContracts } = this.state; const whitelistedContractsOptions = this.state.whitelistedContracts.map((address, index) => { - return ; + return ; }); const userContractsOptions = userContracts.map((address, index) => { - return ; + return ; }); const fnDescription = () => (Short description of the proposal.
  • What are you proposing to do?
  • Why is it important?
  • How much will it cost the DAO?
  • When do you plan to deliver the work?
); diff --git a/src/components/Proposal/ProposalSummary/ProposalSummary.scss b/src/components/Proposal/ProposalSummary/ProposalSummary.scss index c5bf1e7b9..eadfe0ee1 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummary.scss +++ b/src/components/Proposal/ProposalSummary/ProposalSummary.scss @@ -152,6 +152,12 @@ .multiCallContractDetails { border-bottom: 1px solid gray; + .valueText { + font-family: Monaco; + } + .paramWrapper { + margin-top: 5px; + } } .multiCallContractDetails:last-child { border-bottom: none; diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 5ca2dd129..4cc45408b 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -1,11 +1,12 @@ import { IDAOState, IProposalState } from "@daostack/arc.js"; import classNames from "classnames"; -import { linkToEtherScan, formatTokens, baseTokenName, truncateWithEllipses } from "lib/util"; +import { linkToEtherScan, baseTokenName, truncateWithEllipses, buf2hex, getContractName } from "lib/util"; import * as React from "react"; import { IProfileState } from "reducers/profilesReducer"; import * as css from "./ProposalSummary.scss"; import BN = require("bn.js"); import CopyToClipboard from "components/Shared/CopyToClipboard"; +import { getABIByContract, decodeABI, IDecodedData } from "../Create/SchemeForms/ABIService"; interface IProps { beneficiaryProfile?: IProfileState; @@ -19,6 +20,52 @@ interface IProps { interface IState { } +interface IDecodedDataProps { + contract: string; + callData: string; +} + +const parseParamValue = (type: string, value: any) => { + switch (true){ + case type.includes("address"): + return `0x${value}`; + case type.includes("uint"): + return value.toString(10); + case type.includes("byte"): + return `0x${buf2hex(value)}`; + default: + return "unsupported type"; + } +}; + + +const DecodedData = (props: IDecodedDataProps) => { + const [lodaing, setLoading] = React.useState(false); + const [decodedData, setDecodedData] = React.useState({method: "", inputs: [], names: [], types: []}); + + React.useEffect(() => { + const getAbi = async () => { + setLoading(true); + const abiData = await getABIByContract(props.contract); + setDecodedData(decodeABI(abiData, props.callData)); + setLoading(false); + }; + getAbi(); + }, []); + + const methodParams = decodedData.names.map((param: string, index: number) => { + return
{param}: {parseParamValue(decodedData.types[index], decodedData.inputs[index])}
; + }); + + return ( +
+ {lodaing && "loading..."} +
Method: {decodedData.method}({decodedData.types.join(",")})
+ {methodParams} +
+ ); +}; + export default class ProposalSummary extends React.Component { constructor(props: IProps) { @@ -27,7 +74,7 @@ export default class ProposalSummary extends React.Component { public render(): RenderOutput { const { proposal, detailView, transactionModal } = this.props; - const tokenAmountToSend = new BN(proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b)))); + const tokenAmountToSend = proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b))); const proposalSummaryClass = classNames({ [css.detailView]: detailView, [css.transactionModal]: transactionModal, @@ -38,17 +85,16 @@ export default class ProposalSummary extends React.Component {
Generic multicall - {tokenAmountToSend.gtn(0) &&
> Sending {formatTokens(tokenAmountToSend)} {baseTokenName()} <
} + {tokenAmountToSend &&
> Sending {tokenAmountToSend.toString()} {baseTokenName()} <
}
{detailView &&
{ proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => (
-

Contract:

- {
{contract}
} -

Sending to contract:

-
{formatTokens(new BN(proposal.genericSchemeMultiCall.values[index]))} {baseTokenName()}
+

{`Contract #${index}:`} {{contract} {`(${getContractName(contract)})`}}

+

{baseTokenName()} value: {proposal.genericSchemeMultiCall.values[index]}

+

Raw call data:

{truncateWithEllipses(proposal.genericSchemeMultiCall.callsData[index], 66)}
diff --git a/src/lib/util.ts b/src/lib/util.ts index 37efed4e4..c3109c6cc 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -706,3 +706,27 @@ export function safeMoment(dateSpecifier: moment.Moment | Date | number | string export const standardPolling = (fetchAllData = false) => { return { polling: true, pollInterval: GRAPH_POLL_INTERVAL, fetchAllData }; }; + + +/** + * Given an ArrayBuffer returns Hex string + * @param {Array} buffer + * @returns {string} Hex string + */ +export const buf2hex = (buffer: Array): string => { // buffer is an ArrayBuffer + return Array.prototype.map.call(new Uint8Array(buffer), (x: any) => ("00" + x.toString(16)).slice(-2)).join(""); +}; + +/** + * Given a contract address returns the contract name if available. + * @param {string} address + * @returns {string} Contract name + */ +export const getContractName = (address: string): string => { + const arc = getArc(); + try { + return arc.getContractInfo(address).name; + } catch (e) { + return "unknown name"; + } +}; From 457752dc1958206b31694e3bb5048563657c69d3 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Sun, 1 Nov 2020 12:56:37 +0200 Subject: [PATCH 14/41] fix contract counting ; fix warning message when proposal sends tokes --- .../ProposalSummaryMultiCallGenericScheme.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 4cc45408b..105dcbca8 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -59,9 +59,11 @@ const DecodedData = (props: IDecodedDataProps) => { return (
- {lodaing && "loading..."} -
Method: {decodedData.method}({decodedData.types.join(",")})
- {methodParams} + {lodaing ? "loading..." : + +
Method: {decodedData.method}({decodedData.types.join(",")})
+ {methodParams} +
}
); }; @@ -74,7 +76,7 @@ export default class ProposalSummary extends React.Component { public render(): RenderOutput { const { proposal, detailView, transactionModal } = this.props; - const tokenAmountToSend = proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b))); + const tokenAmountToSend = proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b))).toString(); const proposalSummaryClass = classNames({ [css.detailView]: detailView, [css.transactionModal]: transactionModal, @@ -84,15 +86,15 @@ export default class ProposalSummary extends React.Component { return (
- Generic multicall - {tokenAmountToSend &&
> Sending {tokenAmountToSend.toString()} {baseTokenName()} <
} + Generic Multicall + {eval(tokenAmountToSend) > 0 &&
> Sending {tokenAmountToSend} {baseTokenName()} <
}
{detailView &&
{ proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => (
-

{`Contract #${index}:`} {{contract} {`(${getContractName(contract)})`}}

+

{`Contract #${index + 1}:`} {{contract} {`(${getContractName(contract)})`}}

{baseTokenName()} value: {proposal.genericSchemeMultiCall.values[index]}

Raw call data:

From 47b6e578d866f7514718618c75a3bf69c89a7444 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 2 Nov 2020 17:18:21 +0200 Subject: [PATCH 15/41] changes in jsons to fix tests to match the new migration --- src/genericSchemeRegistry/schemes/ENSRegistry.json | 3 ++- src/genericSchemeRegistry/schemes/RegistryLookup.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/genericSchemeRegistry/schemes/ENSRegistry.json b/src/genericSchemeRegistry/schemes/ENSRegistry.json index 88270ca3b..d45f955dc 100644 --- a/src/genericSchemeRegistry/schemes/ENSRegistry.json +++ b/src/genericSchemeRegistry/schemes/ENSRegistry.json @@ -11,7 +11,8 @@ ], "private": [ "0x6b85757e0a41f837dbe0b6b4b5157858b62d9a66", - "0xd931b5ef6afb7995d01be71f69a7fcbbebe4f41f" + "0xd931b5ef6afb7995d01be71f69a7fcbbebe4f41f", + "0x99703166acf4b5c6ca9e878ca62feb0df19d7ff1" ] }, "actions": [ diff --git a/src/genericSchemeRegistry/schemes/RegistryLookup.json b/src/genericSchemeRegistry/schemes/RegistryLookup.json index 423411355..a69db86bf 100644 --- a/src/genericSchemeRegistry/schemes/RegistryLookup.json +++ b/src/genericSchemeRegistry/schemes/RegistryLookup.json @@ -13,7 +13,8 @@ ], "private": [ "0x9e32124cbecc2460e54735d356c0cb5ca8c8aa44", - "0x38d542f47b1b949146e3961ecd87872fdea49679" + "0x38d542f47b1b949146e3961ecd87872fdea49679", + "0x04197b7f05c53d81efce85af1bc31bdd21dacc8b" ] }, "actions": [ From fd88afd79e4839e285bcb8533a64e4d57568850e Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 3 Nov 2020 10:21:57 +0200 Subject: [PATCH 16/41] updated test env v3.0.41 ; show custom contract option only if whitelist is empty ; some code improvments --- docker-compose.yml | 6 ++--- .../CreateGenericMultiCallProposal.tsx | 27 +++++++------------ .../ProposalSummaryMultiCallGenericScheme.tsx | 4 +-- src/lib/util.ts | 2 +- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ff8d28800..32935a064 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,18 +47,18 @@ services: GRAPH_MAX_IPFS_FILE_BYTES: 900000 ipfs: - image: daostack/test-env-ipfs:3.0.40 + image: daostack/test-env-ipfs:3.0.41 ports: - 5001:5001 postgres: - image: daostack/test-env-postgres:3.0.40 + image: daostack/test-env-postgres:3.0.41 ports: - 9432:5432 environment: POSTGRES_PASSWORD: 'letmein' ganache: - image: daostack/test-env-ganache:3.0.40 + image: daostack/test-env-ganache:3.0.41 ports: - 8545:8545 diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index d6b6799db..fc2580282 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -101,7 +101,7 @@ class CreateGenericMultiCallScheme extends React.Component loading: false, tags: this.initialFormValues.tags, addContractStatus: { error: "", message: "" }, - whitelistedContracts: this.props.whitelistedContracts ?? [], + whitelistedContracts: this.props.whitelistedContracts?.map(contract => { return contract.toLowerCase(); }) ?? [], userContracts: [], }; } @@ -224,14 +224,11 @@ class CreateGenericMultiCallScheme extends React.Component public render(): RenderOutput { const { handleClose } = this.props; - const { loading, addContractStatus, userContracts } = this.state; + const { loading, addContractStatus, userContracts, whitelistedContracts } = this.state; - const whitelistedContractsOptions = this.state.whitelistedContracts.map((address, index) => { - return ; - }); - - const userContractsOptions = userContracts.map((address, index) => { - return ; + const contracts = whitelistedContracts.length > 0 ? whitelistedContracts : userContracts; + const contractsOptions = contracts.map((address, index) => { + return ; }); const fnDescription = () => (Short description of the proposal.
  • What are you proposing to do?
  • Why is it important?
  • How much will it cost the DAO?
  • When do you plan to deliver the work?
); @@ -339,6 +336,7 @@ class CreateGenericMultiCallScheme extends React.Component className={touched.url && errors.url ? css.error : null} /> + {whitelistedContracts.length === 0 &&
: addContractStatus.message} -
+
} {({ insert, remove, push }) => ( // eslint-disable-line @typescript-eslint/no-unused-vars @@ -395,14 +393,9 @@ class CreateGenericMultiCallScheme extends React.Component validate={requireValue} > - - {whitelistedContractsOptions} + 0 ? "Whitelisted contracts" : "Custom contracts"}> + {contractsOptions} - {userContractsOptions.length > 0 && - - {userContractsOptions} - - }
diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 105dcbca8..2315ac62d 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -87,14 +87,14 @@ export default class ProposalSummary extends React.Component {
Generic Multicall - {eval(tokenAmountToSend) > 0 &&
> Sending {tokenAmountToSend} {baseTokenName()} <
} + {Number(tokenAmountToSend) > 0 &&
> Sending {tokenAmountToSend} {baseTokenName()} <
}
{detailView &&
{ proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => (
-

{`Contract #${index + 1}:`} {{contract} {`(${getContractName(contract)})`}}

+

{`Contract #${index + 1}:`} {{getContractName(contract)} {`(${contract})`}}

{baseTokenName()} value: {proposal.genericSchemeMultiCall.values[index]}

Raw call data:

diff --git a/src/lib/util.ts b/src/lib/util.ts index c3109c6cc..f8120d33e 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -725,7 +725,7 @@ export const buf2hex = (buffer: Array): string => { // buffer is an ArrayBu export const getContractName = (address: string): string => { const arc = getArc(); try { - return arc.getContractInfo(address).name; + return arc.getContractInfo(address.toLowerCase()).name; } catch (e) { return "unknown name"; } From ef90e01864d96187086746eb2d72c9217348a5d0 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 3 Nov 2020 12:07:59 +0200 Subject: [PATCH 17/41] support bool type input ; reset inputs after changing method --- .../Proposal/Create/CreateProposal.scss | 5 +++++ .../CreateGenericMultiCallProposal.tsx | 20 ++++++++++++++++--- .../Proposal/Create/SchemeForms/Validators.ts | 2 +- .../ProposalSummaryMultiCallGenericScheme.tsx | 2 ++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 283241d8d..73a56cb05 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -148,6 +148,11 @@ fieldset { width: 490px; // TEMP - SHOULD BE DYNAMIC } + input[type=checkbox] { + width: 15px; + height: auto; + } + .proposerIsAdminCheckbox { label { display: inline-block; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index fc2580282..5e8379860 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -200,11 +200,22 @@ class CreateGenericMultiCallScheme extends React.Component } private getMethodInputs = (abi: any, methods: any[], methodName: any, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.values`, ""); // reset setFieldValue(`contracts.${index}.callData`, ""); // reset const selectedMethod = methods.filter(method => method.methodSignature === methodName); - const abiParams = selectedMethod[0].inputs.map((input: any, index: number) => { + let isOnlyBooleanInputs = true; + const abiParams = selectedMethod[0].inputs.map((input: any, j: number) => { + /** + * For some reason the default value in Formik checkbox is undefined. + * This is a hack to set the default value to false. + */ + if (input.type === "bool") { + setFieldValue(`contracts.${index}.values.${j}`, false); + } else { // We have at least one non-boolean input + isOnlyBooleanInputs = false; + } return { - id: index, + id: j, name: input.name, type: input.type, placeholder: `${input.name} (${input.type})`, @@ -214,6 +225,8 @@ class CreateGenericMultiCallScheme extends React.Component setFieldValue(`contracts.${index}.params`, abiParams); if (abiParams.length === 0) { // If no params, generate the encoded data setFieldValue(`contracts.${index}.callData`, encodeABI(abi, selectedMethod[0].name, [])); + } else if (isOnlyBooleanInputs) { + setFieldValue(`contracts.${index}.callData`, encodeABI(abi, selectedMethod[0].name, new Array(abiParams.length).fill(false))); } } @@ -434,7 +447,7 @@ class CreateGenericMultiCallScheme extends React.Component {(msg) => {msg}} // eslint-disable-next-line react/jsx-no-bind validate={(e: any) => validateParam(param.type, e)} /> + {param.type === "bool" && `${values.contracts[index].values[i]}`} ))}
diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index 67416f80a..463eaf388 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -23,7 +23,7 @@ export const requireValue = (value: any): string => { */ export const validateParam = (type: string, value: string): string => { let error; - if (!value) { + if (!value && type !== "bool") { error = "Required"; } else { diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 2315ac62d..78a1ca8fd 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -33,6 +33,8 @@ const parseParamValue = (type: string, value: any) => { return value.toString(10); case type.includes("byte"): return `0x${buf2hex(value)}`; + case type.includes("bool"): + return value.toString(); default: return "unsupported type"; } From 9ce1fe42b7899e238119fe913bd5aacb8c4c3996 Mon Sep 17 00:00:00 2001 From: orenyodfat Date: Tue, 3 Nov 2020 23:04:09 +0200 Subject: [PATCH 18/41] Update README.md (#2241) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bfe5e21db..9e51fc8ed 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ https://kovan.v1.alchemy.do - alchemy v1 on kovan **Alchemy 2.0** :fire: -Alchemy v2 source code can be found under [master-2](https://github.com/daostack/alchemy/tree/master-2) branch. +Alchemy 2.0 source code can be found [here](https://github.com/daostack/alchemy_2). https://alchemy.do - alchemy 2.0 on mainnet From 5fef777d52825078b4be29924ee1868893730240 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 4 Nov 2020 15:20:08 +0200 Subject: [PATCH 19/41] fix send token value to support decimals ; better loader in proposal summary --- .../SchemeForms/CreateGenericMultiCallProposal.tsx | 4 ++-- .../Proposal/ProposalSummary/ProposalSummary.scss | 10 ++++++++++ .../ProposalSummaryMultiCallGenericScheme.tsx | 10 +++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 5e8379860..08adec7d0 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -6,7 +6,7 @@ import Analytics from "lib/analytics"; import * as React from "react"; import { connect } from "react-redux"; import { showNotification, NotificationStatus } from "reducers/notifications"; -import { baseTokenName, isValidUrl, isAddress, linkToEtherScan, getContractName } from "lib/util"; +import { baseTokenName, isValidUrl, isAddress, linkToEtherScan, getContractName, toWei } from "lib/util"; import { exportUrl, importUrlValues } from "lib/proposalUtils"; import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; import TrainingTooltip from "components/Shared/TrainingTooltip"; @@ -116,7 +116,7 @@ class CreateGenericMultiCallScheme extends React.Component for (const contract of formValues.contracts) { contractsToCall.push(contract.address); callsData.push(contract.callData); - values.push(contract.value); + values.push(toWei(Number(contract.value)).toString()); } const proposalValues = { diff --git a/src/components/Proposal/ProposalSummary/ProposalSummary.scss b/src/components/Proposal/ProposalSummary/ProposalSummary.scss index eadfe0ee1..3026a9c92 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummary.scss +++ b/src/components/Proposal/ProposalSummary/ProposalSummary.scss @@ -158,6 +158,16 @@ .paramWrapper { margin-top: 5px; } + .loadingMethodInfo { + display: flex; + align-items: center; + .loader { + background: url("../../../assets/images/Icon/buttonLoadingBlue.gif") no-repeat; + margin-right: 5px; + width: 15px; + height: 15px; + } + } } .multiCallContractDetails:last-child { border-bottom: none; diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 78a1ca8fd..3f313977b 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -1,6 +1,6 @@ import { IDAOState, IProposalState } from "@daostack/arc.js"; import classNames from "classnames"; -import { linkToEtherScan, baseTokenName, truncateWithEllipses, buf2hex, getContractName } from "lib/util"; +import { linkToEtherScan, baseTokenName, truncateWithEllipses, buf2hex, getContractName, fromWei } from "lib/util"; import * as React from "react"; import { IProfileState } from "reducers/profilesReducer"; import * as css from "./ProposalSummary.scss"; @@ -61,7 +61,7 @@ const DecodedData = (props: IDecodedDataProps) => { return (
- {lodaing ? "loading..." : + {lodaing ?
Loading method info...
:
Method: {decodedData.method}({decodedData.types.join(",")})
{methodParams} @@ -78,7 +78,7 @@ export default class ProposalSummary extends React.Component { public render(): RenderOutput { const { proposal, detailView, transactionModal } = this.props; - const tokenAmountToSend = proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b))).toString(); + const tokenAmountToSend = proposal.genericSchemeMultiCall.values.reduce((a: BN, b: BN) => new BN(a).add(new BN(b))); const proposalSummaryClass = classNames({ [css.detailView]: detailView, [css.transactionModal]: transactionModal, @@ -89,7 +89,7 @@ export default class ProposalSummary extends React.Component {
Generic Multicall - {Number(tokenAmountToSend) > 0 &&
> Sending {tokenAmountToSend} {baseTokenName()} <
} + {fromWei(tokenAmountToSend) > 0 &&
> Sending {fromWei(tokenAmountToSend)} {baseTokenName()} <
}
{detailView &&
@@ -97,7 +97,7 @@ export default class ProposalSummary extends React.Component { proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => (

{`Contract #${index + 1}:`} {{getContractName(contract)} {`(${contract})`}}

-

{baseTokenName()} value: {proposal.genericSchemeMultiCall.values[index]}

+

{baseTokenName()} value: {fromWei(proposal.genericSchemeMultiCall.values[index])}

Raw call data:

{truncateWithEllipses(proposal.genericSchemeMultiCall.callsData[index], 66)}
From f82759bf5872af7b624f23c539ba65bab68d0965 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 4 Nov 2020 16:35:27 +0200 Subject: [PATCH 20/41] remove unnecessary condition ; setAddContractStatus inner helper function ; better documentaion to validateParam function --- .../CreateGenericMultiCallProposal.tsx | 22 +++++++++---------- .../Proposal/Create/SchemeForms/Validators.ts | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 08adec7d0..d92c07738 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -165,26 +165,26 @@ class CreateGenericMultiCallScheme extends React.Component message: "", }; + const setAddContractStatus = (errorType: AddContractError, message: string) => { + addContractStatus.error = errorType; + addContractStatus.message = message; + }; + if (contractToCall === "") { - addContractStatus.error = ""; - addContractStatus.message = ""; + setAddContractStatus("", ""); } else if (!isAddress(contractToCall)) { - addContractStatus.error = "NOT_VALID_ADDRESS"; - addContractStatus.message = "Please enter a valid address"; - } else if (this.state.whitelistedContracts.includes(contractToCall) || this.state.userContracts.includes(contractToCall)) { - addContractStatus.error = "CONTRACT_EXIST"; - addContractStatus.message = "Contract already exist!"; + setAddContractStatus("NOT_VALID_ADDRESS", "Please enter a valid address"); + } else if (this.state.userContracts.includes(contractToCall)) { + setAddContractStatus("CONTRACT_EXIST", "Contract already exist!"); } else { this.setState({ loading: true }); const abiData = await getABIByContract(contractToCall); const abiMethods = extractABIMethods(abiData); if (abiMethods.length > 0) { this.state.userContracts.push(contractToCall); - addContractStatus.error = ""; - addContractStatus.message = "Contract added successfully!"; + setAddContractStatus("", "Contract added successfully!"); } else { - addContractStatus.error = "ABI_DATA_ERROR"; - addContractStatus.message = abiData.length === 0 ? "No ABI found for target contract, please verify the " : "No write methods found for target "; + setAddContractStatus("ABI_DATA_ERROR", abiData.length === 0 ? "No ABI found for target contract, please verify the " : "No write methods found for target "); } } this.setState({ loading: false, addContractStatus: addContractStatus }); diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index 463eaf388..c66d91964 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -17,9 +17,11 @@ export const requireValue = (value: any): string => { }; /** - * Given ABI method param type (address, byets32, unit256, ...) and it's value, returns error message in case validation fails or no value provided + * Given ABI method param type (address, byets32, unit256, ...) and it's value, returns error message in case validation fails or no value provided. + * This function is suitable to work with Formik validations. * @param {string} type * @param {string} value + * @returns {undefined | string} undefined if there is no error, otherwise a string representing the error message. */ export const validateParam = (type: string, value: string): string => { let error; From fd98efd5d816c7301815dc662c5d0f33a618ff40 Mon Sep 17 00:00:00 2001 From: kin Date: Sun, 8 Nov 2020 17:15:32 +0100 Subject: [PATCH 21/41] Add BalancerPoolManager Generic Scheme (#2244) * add BalancerPoolManager.json * upd BalancerPoolManager.json * add BalancerPoolManager.json to index.ts * fix change address to lower case * update BalancerPoolManager.json * fix lint --- src/genericSchemeRegistry/index.ts | 2 + .../schemes/BalancerPoolManager.json | 341 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 src/genericSchemeRegistry/schemes/BalancerPoolManager.json diff --git a/src/genericSchemeRegistry/index.ts b/src/genericSchemeRegistry/index.ts index c2284bf67..fde798241 100644 --- a/src/genericSchemeRegistry/index.ts +++ b/src/genericSchemeRegistry/index.ts @@ -14,6 +14,7 @@ const registryLookupInfo = require("./schemes/RegistryLookup.json"); const co2kenInfo = require("./schemes/CO2ken.json"); const dXTokenRegistry = require("./schemes/dXTokenRegistry.json"); const dXswapGovernance = require("./schemes/DXswapGovernance.json"); +const balancerPoolManager = require("./schemes/BalancerPoolManager.json"); const KNOWNSCHEMES = [ dutchXInfo, @@ -26,6 +27,7 @@ const KNOWNSCHEMES = [ registryLookupInfo, dXTokenRegistry, dXswapGovernance, + balancerPoolManager, ]; const SCHEMEADDRESSES: {[network: string]: { [address: string]: any}} = { diff --git a/src/genericSchemeRegistry/schemes/BalancerPoolManager.json b/src/genericSchemeRegistry/schemes/BalancerPoolManager.json new file mode 100644 index 000000000..7b56d5fa8 --- /dev/null +++ b/src/genericSchemeRegistry/schemes/BalancerPoolManager.json @@ -0,0 +1,341 @@ +{ + "name": "BalancerPoolManager", + "addresses": { + "main": [ + "" + ], + "kovan": [ + "0x5086d91858a5aac4a770212720b297f38e0dd403" + ], + "private": [ + "" + ] + }, + "actions": [ + { + "id": "setPublicSwap", + "label": "Set Public Swap", + "description": "False to pause the pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Set public swap to", + "name": "public swap", + "placeholder": "Boolean (true or false)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "bool", + "name": "publicSwap", + "type": "bool" + } + ], + "name": "setPublicSwap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "setSwapFee", + "label": "Set Swap Fee", + "description": "Update the Swap Fee", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "decimals": 18, + "label": "Set swap fee to", + "name": "amount", + "unit": "%", + "placeholder": "Swap fee (1.5)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "swapFee", + "type": "uint256" + } + ], + "name": "setSwapFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "commitAddToken", + "label": "Add token", + "description": "First step in adding a new token to the balancer pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Token address", + "name": "token", + "placeholder": "Address (0x0000…)" + }, + { + "decimals": 18, + "label": "Balance of the new token to add to the pool", + "name": "balance", + "unit": "token", + "placeholder": "Balance in token (1500)" + }, + { + "decimals": 18, + "label": "Expected denormalizedWeight", + "name": "Denormalized Weight", + "placeholder": "Denormalized Weight (1.5)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denormalizedWeight", + "type": "uint256" + } + ], + "name": "commitAddToken", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "applyAddToken", + "label": "Confirm add token", + "description": "Second step in adding a new token to the balancer pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + ], + "abi": { + "constant": false, + "inputs": [], + "name": "applyAddToken", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "removeToken", + "label": "Remove token", + "description": "Remove a token from the pool", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Token address", + "name": "token", + "placeholder": "Address (0x0000…)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "removeToken", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "updateWeight", + "label": "New weight", + "description": "Set the weight of a token", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "Token address", + "name": "token", + "placeholder": "Address (0x0000…)" + }, + { + "decimals": 18, + "label": "New weight", + "name": "new weight", + "unit": "unit", + "placeholder": "Weight (1500)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newWeight", + "type": "uint256" + } + ], + "name": "updateWeight", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "updateWeightsGradually", + "label": "Update weights", + "description": "Sets token weights (to be gradually updated)", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "label": "New weights in wei", + "name": "New weights that are going to be gradually updated", + "placeholder": "Weight (1000000000000000000)" + }, + { + "decimals": 18, + "label": "Start block", + "name": "Start update from block", + "unit": "unit", + "placeholder": "Block number (199199)" + }, + { + "decimals": 18, + "label": "End block", + "name": "End update at block", + "unit": "unit", + "placeholder": "Block number (399199)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256[]", + "name": "newWeights", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "startBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endBlock", + "type": "uint256" + } + ], + "name": "updateWeightsGradually", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "joinPool", + "label": "Join pool", + "description": "Joins the pool by adding more tokens", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "decimals": 18, + "label": "Pool tokens to recieve", + "name": "Number of pool tokens to receive", + "unit": "uint", + "placeholder": "Number (1000)" + }, + { + "label": "Max tokens to add in wei", + "name": "Max amount of asset tokens to spend", + "placeholder": "Amount (2000000000000000000)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "poolAmountOut", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + } + ], + "name": "joinPool", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + }, + { + "id": "exitPool", + "label": "Exit pool", + "description": "Exits the pool by redeeming the tokens", + "notes": "https://github.com/PrimeDAO/monorepo/blob/master/packages/contracts/contracts/schemes/BalancerProxy.sol", + "fields": [ + { + "decimals": 18, + "label": "Pool tokens to redeem", + "name": "Number of pool tokens to redeem", + "placeholder": "Number (1000)" + }, + { + "label": "Max amount of asset tokens to receive in wei", + "name": "Minimum amount of asset tokens to receive", + "placeholder": "Amount (2000000000000000000)" + } + ], + "abi": { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "poolAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + } + ], + "name": "exitPool", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + } + ] +} From 3799e6a51f5b74175bf5fdef982734b77b458272 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 9 Nov 2020 09:56:50 +0200 Subject: [PATCH 22/41] different error message --- src/components/Proposal/Create/SchemeForms/Validators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index c66d91964..829b9b350 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -37,7 +37,7 @@ export const validateParam = (type: string, value: string): string => { break; case type.includes("byte"): if (!isHexStrict(value)) { - error = "Must be a hex value"; + error = "Must be an hex"; } break; case type.includes("uint"): From 3a561edf9ac5030fb119e5b85191bc572f9d6a88 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 9 Nov 2020 10:01:14 +0200 Subject: [PATCH 23/41] better documentaion to requireValue function --- src/components/Proposal/Create/SchemeForms/Validators.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index 829b9b350..76171167f 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -4,6 +4,7 @@ import { isAddress } from "lib/util"; /** * Given a value returns error message in case value is less than 0 or no value provided * @param {any} value + * @returns {undefined | string} undefined if there is no error, otherwise a "Required" string. */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const requireValue = (value: any): string => { @@ -37,7 +38,7 @@ export const validateParam = (type: string, value: string): string => { break; case type.includes("byte"): if (!isHexStrict(value)) { - error = "Must be an hex"; + error = "Must be an hex value"; } break; case type.includes("uint"): From 73a4da146f86699157b5474b679060ac5179e971 Mon Sep 17 00:00:00 2001 From: orenyodfat Date: Tue, 10 Nov 2020 10:47:52 +0200 Subject: [PATCH 24/41] Fix competition tab crash ; Fix compitition plugin ugly name (#2249) --- .../ContributionRewardExtRewarders/Competition/List.tsx | 6 ++---- src/lib/schemeUtils.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx b/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx index 4ae5e3f65..fbb6e28d3 100644 --- a/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx +++ b/src/components/Scheme/ContributionRewardExtRewarders/Competition/List.tsx @@ -9,7 +9,6 @@ import { getArc } from "arc"; import { CompetitionStatusEnum, CompetitionStatus } from "./utils"; import Card from "./Card"; import * as css from "./Competitions.scss"; -import { standardPolling } from "lib/util"; interface IExternalProps { daoState: IDAOState; @@ -127,7 +126,7 @@ export default withSubscription({ suggestions { ...CompetitionSuggestionFields } - votes { + votes { ...CompetitionVoteFields } } @@ -138,7 +137,7 @@ export default withSubscription({ `; const arc = await getArc(); - await arc.sendQuery(cacheQuery, standardPolling()); + await arc.sendQuery(cacheQuery); // end cache priming // TODO: next lines can use some cleanup up @@ -148,4 +147,3 @@ export default withSubscription({ ); }, }); - diff --git a/src/lib/schemeUtils.ts b/src/lib/schemeUtils.ts index 364c29d4c..8d6f7d92c 100644 --- a/src/lib/schemeUtils.ts +++ b/src/lib/schemeUtils.ts @@ -134,7 +134,7 @@ export function schemeName(scheme: ISchemeState|IContractInfo, fallback?: string /** * this will be "pretty" */ - name = rewarderContractName(scheme as ISchemeState); + name = rewarderContractName(scheme as ISchemeState, false); } else { name = alias ?? splitCamelCase(scheme.name); } From 9afca7b0e2bf7484ec463af6f149ac1a870c6665 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 10 Nov 2020 11:00:57 +0200 Subject: [PATCH 25/41] bump version 1.0.2 --- CHANGELOG.md | 8 ++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d59bf31..e2e1f6fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.2 + - Features Added + - Support Multi-call Generic Scheme + + - Bugs Fixed + - Fix competiotion tab crash + - Better name for some Competition plugins + ## 1.0.1 - Bugs Fixed - Alchemy should not crash immediately when there is a subgraph error. It will retry for approximately 2.5 minutes. diff --git a/package-lock.json b/package-lock.json index cb6720400..98631de19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "alchemy-client", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2c6fdd36a..8d66e9e6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alchemy-client", - "version": "1.0.1", + "version": "1.0.2", "description": "An app for collaborative networks (DAOs), based on the DAO stack.", "author": "DAOstack", "license": "GPL-3.0", From 6d573fdac89695c029129576e3c41d4d4096bbb5 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 10 Nov 2020 11:12:35 +0200 Subject: [PATCH 26/41] added subgraph and arc.js versions to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e1f6fab..829c8990e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 1.0.2 - Features Added - Support Multi-call Generic Scheme + - Use subgraph v40_0 and arc.js 0.2.76 - Bugs Fixed - Fix competiotion tab crash From a695607b040446d6374fc7da347a23b4684d908e Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 11 Nov 2020 00:51:05 +0200 Subject: [PATCH 27/41] support multi-call arrays method params ; better validation and handeling with ABI methods types ; update webpack with etherscan api key --- .../Proposal/Create/SchemeForms/ABIService.ts | 20 ++- .../CreateGenericMultiCallProposal.tsx | 19 +-- .../Proposal/Create/SchemeForms/Validators.ts | 129 +++++++++++++++--- .../ProposalSummaryMultiCallGenericScheme.tsx | 37 +++-- webpack.dev.config.js | 1 + webpack.docker.config.js | 1 + webpack.prod.config.js | 1 + 7 files changed, 159 insertions(+), 49 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts index 64655fee3..52f7d617e 100644 --- a/src/components/Proposal/Create/SchemeForms/ABIService.ts +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -112,9 +112,27 @@ export const encodeABI = (abi: Array, name: string, values: Array): st const contract = new web3.eth.Contract(abi); const interfaceABI = contract.options.jsonInterface; + const isArrayParameter = (parameter: string): boolean => /^\[.*\]$/.test(parameter); + + let parsedValues = []; + if (values) { + parsedValues = values.map(value => { + if (isArrayParameter(value)) { + try { + return JSON.parse(value); + } catch (e) { return; } + } else if (value === "true" || value === "false") { + try { + return JSON.parse(value); + } catch (e) { return; } + } + return value; + }); + } + try { const methodToSend = interfaceABI.filter((method: any) => method.name === name && method.inputs.length === values.length); - return web3.eth.abi.encodeFunctionCall(methodToSend[0], values); + return web3.eth.abi.encodeFunctionCall(methodToSend[0], parsedValues); } catch (error) { return error.reason; } diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index d92c07738..98b46902b 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -14,7 +14,7 @@ import * as css from "../CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; -import { requireValue, validateParam } from "./Validators"; +import { requireValue, validateParam, isArrayParameter, typeArrayPlaceholder } from "./Validators"; interface IExternalProps { daoAvatarAddress: string; @@ -203,30 +203,18 @@ class CreateGenericMultiCallScheme extends React.Component setFieldValue(`contracts.${index}.values`, ""); // reset setFieldValue(`contracts.${index}.callData`, ""); // reset const selectedMethod = methods.filter(method => method.methodSignature === methodName); - let isOnlyBooleanInputs = true; const abiParams = selectedMethod[0].inputs.map((input: any, j: number) => { - /** - * For some reason the default value in Formik checkbox is undefined. - * This is a hack to set the default value to false. - */ - if (input.type === "bool") { - setFieldValue(`contracts.${index}.values.${j}`, false); - } else { // We have at least one non-boolean input - isOnlyBooleanInputs = false; - } return { id: j, name: input.name, type: input.type, - placeholder: `${input.name} (${input.type})`, + placeholder: `${input.name} (${input.type}) ${isArrayParameter(input.type) ? typeArrayPlaceholder(input.type) : ""}`, methodSignature: input.methodSignature, }; }); setFieldValue(`contracts.${index}.params`, abiParams); if (abiParams.length === 0) { // If no params, generate the encoded data setFieldValue(`contracts.${index}.callData`, encodeABI(abi, selectedMethod[0].name, [])); - } else if (isOnlyBooleanInputs) { - setFieldValue(`contracts.${index}.callData`, encodeABI(abi, selectedMethod[0].name, new Array(abiParams.length).fill(false))); } } @@ -447,7 +435,7 @@ class CreateGenericMultiCallScheme extends React.Component {(msg) => {msg}} // eslint-disable-next-line react/jsx-no-bind validate={(e: any) => validateParam(param.type, e)} /> - {param.type === "bool" && `${values.contracts[index].values[i]}`} ))}
diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index 76171167f..6f5710bfa 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -1,6 +1,52 @@ import { isHexStrict } from "web3-utils"; import { isAddress } from "lib/util"; +/** + * Functions to check an input core type + */ +export const isAddressType = (type: string): boolean => type.indexOf("address") === 0; +export const isBooleanType = (type: string): boolean => type.indexOf("bool") === 0; +export const isStringType = (type: string): boolean => type.indexOf("string") === 0; +export const isUintType = (type: string): boolean => type.indexOf("uint") === 0; +export const isIntType = (type: string): boolean => type.indexOf("int") === 0; +export const isByteType = (type: string): boolean => type.indexOf("byte") === 0; + +/** + * RegEx to check if a string ends with [] + * @param {string} parameter + * @returns {boolean} true if a string ends with [], otherwise returns false. + */ +export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter); + +/** + * Given an array input type returns an input example string. + * @param {string} type The input type (e.g. unit256[], bool[], address[], ...) + * @returns {string} Input example string + */ +export const typeArrayPlaceholder = (type: string): string => { + if (isAddressType(type)) { + return "e.g.: ['0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E','0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e']"; + } + + if (isBooleanType(type)) { + return "e.g.: [true, false, false, true]"; + } + + if (isUintType(type)) { + return "e.g.: [1000, 212, 320000022, 23]"; + } + + if (isIntType(type)) { + return "e.g.: [1000, -212, 1232, -1]"; + } + + if (isByteType(type)) { + return "e.g.: ['0xc00000000000000000000000000000000000', '0xc00000000000000000000000000000000001']"; + } + + return "e.g.: ['first value', 'second value', 'third value']"; +}; + /** * Given a value returns error message in case value is less than 0 or no value provided * @param {any} value @@ -18,35 +64,76 @@ export const requireValue = (value: any): string => { }; /** - * Given ABI method param type (address, byets32, unit256, ...) and it's value, returns error message in case validation fails or no value provided. + * Given an ABI method param type (address, byets32, unit256, bool, ...) and it's value, returns error message in case validation fails or no value provided. + * @param {string} type + * @param {string} value + * @returns {undefined | string} undefined if there is no error, otherwise a string representing the error message. + */ +export const validateParamValue = (type: string, value: string): undefined | string => { + if (isAddressType(type)) { + if (!isAddress(value)) { + return "Please enter a valid address"; + } + } + + if (isBooleanType(type)) { + if (value !== "true" && value !== "false") { + return "Must be 'true' or 'false'"; + } + } + + if (isUintType(type)) { + if (/^\d+$/.test(value) === false) { + return "Must contain only positive digits"; + } + } + + if (isIntType(type)) { + if (!Number.isInteger(Number(value)) || value.includes(".")) { + return "Must be an integer"; + } + } + + if (isByteType(type)) { + if (!isHexStrict(value)) { + return "Must be an hex value"; + } + } + + return undefined; +}; + + + +/** + * Given an ABI method param type including array type (address, byets32, unit256, bool, bool[], ...) and it's value, returns error message in case validation fails or no value provided. * This function is suitable to work with Formik validations. * @param {string} type * @param {string} value * @returns {undefined | string} undefined if there is no error, otherwise a string representing the error message. */ -export const validateParam = (type: string, value: string): string => { +export const validateParam = (type: string, value: string): undefined | string => { let error; - if (!value && type !== "bool") { - error = "Required"; + if (!value) { + return "Required"; } - else { - switch (true) { - case type.includes("address"): - if (!isAddress(value)) { - error = "Please enter a valid address"; - } - break; - case type.includes("byte"): - if (!isHexStrict(value)) { - error = "Must be an hex value"; - } - break; - case type.includes("uint"): - if (/^\d+$/.test(value) === false) { - error = "Must contain only digits"; + + if (isArrayParameter(type)) { + try { + const values = JSON.parse(value); + if (!Array.isArray(values)) { + return "Make sure to surround the value with []"; + } else { + for (const value of values) { + if (validateParamValue(type, value) !== undefined) { + throw error; + } } - break; + } + } catch (e) { + return "Invalid format"; } } - return error; + + else return validateParamValue(type, value); }; diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 3f313977b..237be016c 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -7,6 +7,7 @@ import * as css from "./ProposalSummary.scss"; import BN = require("bn.js"); import CopyToClipboard from "components/Shared/CopyToClipboard"; import { getABIByContract, decodeABI, IDecodedData } from "../Create/SchemeForms/ABIService"; +import * as Validators from "../Create/SchemeForms/Validators"; interface IProps { beneficiaryProfile?: IProfileState; @@ -26,18 +27,32 @@ interface IDecodedDataProps { } const parseParamValue = (type: string, value: any) => { - switch (true){ - case type.includes("address"): - return `0x${value}`; - case type.includes("uint"): - return value.toString(10); - case type.includes("byte"): - return `0x${buf2hex(value)}`; - case type.includes("bool"): - return value.toString(); - default: - return "unsupported type"; + if (Validators.isAddressType(type)) { + if (Validators.isArrayParameter(type)) { + value = value.map((element: any) => { + return `0x${element}\n`; + }); + return value; + } + return `0x${value}`; } + if (Validators.isBooleanType(type)) { + return value.toString(); + } + if (Validators.isUintType(type) || Validators.isIntType(type)) { + return value.toString(10); + } + if (Validators.isByteType(type)) { + if (Validators.isArrayParameter(type)) { + value = value.map((element: any) => { + return `0x${buf2hex(element)}\n`; + }); + return value; + } + return `0x${buf2hex(value)}`; + } + + return value; }; diff --git a/webpack.dev.config.js b/webpack.dev.config.js index 63580a41e..ab0833b3b 100644 --- a/webpack.dev.config.js +++ b/webpack.dev.config.js @@ -103,6 +103,7 @@ module.exports = merge(baseConfig, { ARC_IPFSPROVIDER_PROTOCOL : "", ARC_IPFSPROVIDER_API_PATH : "", INFURA_ID : "", + ETHERSCAN_API_KEY : "", MIXPANEL_TOKEN: "eac39430f2d26472411099a0407ad610", }) ] diff --git a/webpack.docker.config.js b/webpack.docker.config.js index 50cba6c54..a574a925b 100644 --- a/webpack.docker.config.js +++ b/webpack.docker.config.js @@ -104,6 +104,7 @@ module.exports = merge(baseConfig, { ARC_IPFSPROVIDER_PROTOCOL : "", ARC_IPFSPROVIDER_API_PATH : "", INFURA_ID : "", + ETHERSCAN_API_KEY : "", MIXPANEL_TOKEN: "" }), ] diff --git a/webpack.prod.config.js b/webpack.prod.config.js index 0e2e485f9..825fe02db 100644 --- a/webpack.prod.config.js +++ b/webpack.prod.config.js @@ -109,6 +109,7 @@ plugins: [ ARC_IPFSPROVIDER_PROTOCOL : "", ARC_IPFSPROVIDER_API_PATH : "", INFURA_ID : "", + ETHERSCAN_API_KEY : "", MIXPANEL_TOKEN: "", }), From 08b5906d4bd10a303148c3e05136b00740e4a58f Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 11 Nov 2020 11:15:35 +0200 Subject: [PATCH 28/41] code improvements and some minor ui/ux changes --- .../Proposal/Create/SchemeForms/ABIService.ts | 13 +++--- .../CreateGenericMultiCallProposal.tsx | 41 ++++++++++++++++--- .../Proposal/Create/SchemeForms/Validators.ts | 41 +++---------------- .../ProposalSummaryMultiCallGenericScheme.tsx | 7 ++-- 4 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/ABIService.ts b/src/components/Proposal/Create/SchemeForms/ABIService.ts index 52f7d617e..cef924be9 100644 --- a/src/components/Proposal/Create/SchemeForms/ABIService.ts +++ b/src/components/Proposal/Create/SchemeForms/ABIService.ts @@ -4,6 +4,7 @@ const Web3 = require("web3"); import axios from "axios"; import { targetedNetwork } from "lib/util"; const InputDataDecoder = require("ethereum-input-data-decoder"); +import { isArrayParameter } from "./Validators"; export interface IAllowedAbiItem extends AbiItem { name: string; @@ -112,16 +113,14 @@ export const encodeABI = (abi: Array, name: string, values: Array): st const contract = new web3.eth.Contract(abi); const interfaceABI = contract.options.jsonInterface; - const isArrayParameter = (parameter: string): boolean => /^\[.*\]$/.test(parameter); - + /** + * The web3 encodeFunctionCall expects arrays and booleans as objects and not as strings. + * If we have parameters that represent an array or a boolean value, parse them to objects. + */ let parsedValues = []; if (values) { parsedValues = values.map(value => { - if (isArrayParameter(value)) { - try { - return JSON.parse(value); - } catch (e) { return; } - } else if (value === "true" || value === "false") { + if (isArrayParameter(value) || value === "true" || value === "false") { try { return JSON.parse(value); } catch (e) { return; } diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 98b46902b..fa4a139c8 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -14,7 +14,7 @@ import * as css from "../CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; -import { requireValue, validateParam, isArrayParameter, typeArrayPlaceholder } from "./Validators"; +import * as Validators from "./Validators"; interface IExternalProps { daoAvatarAddress: string; @@ -69,6 +69,35 @@ interface IFormValues { [key: string]: any; } +/** + * Given an array input type returns an input example string. + * @param {string} type The input type (e.g. unit256[], bool[], address[], ...) + * @returns {string} Input example string + */ +const typeArrayPlaceholder = (type: string): string => { + if (Validators.isAddressType(type)) { + return "e.g: ['0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E','0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e']"; + } + + if (Validators.isBooleanType(type)) { + return "e.g: [true, false, false, true]"; + } + + if (Validators.isUintType(type)) { + return "e.g: [1000, 212, 320000022, 23]"; + } + + if (Validators.isIntType(type)) { + return "e.g: [1000, -212, 1232, -1]"; + } + + if (Validators.isByteType(type)) { + return "e.g: ['0xc00000000000000000000000000000000000', '0xc00000000000000000000000000000000001']"; + } + + return "e.g: ['first value', 'second value', 'third value']"; +}; + class CreateGenericMultiCallScheme extends React.Component { initialFormValues: IFormValues; @@ -208,7 +237,7 @@ class CreateGenericMultiCallScheme extends React.Component id: j, name: input.name, type: input.type, - placeholder: `${input.name} (${input.type}) ${isArrayParameter(input.type) ? typeArrayPlaceholder(input.type) : ""}`, + placeholder: `${input.name} (${input.type}) ${Validators.isArrayParameter(input.type) ? typeArrayPlaceholder(input.type) : ""}`, methodSignature: input.methodSignature, }; }); @@ -376,7 +405,7 @@ class CreateGenericMultiCallScheme extends React.Component placeholder={`How much ${baseTokenName()} to transfer with the call`} name={`contracts.${index}.value`} type="number" - validate={requireValue} + validate={Validators.requireValue} />
@@ -391,7 +420,7 @@ class CreateGenericMultiCallScheme extends React.Component component="select" name={`contracts.${index}.address`} type="text" - validate={requireValue} + validate={Validators.requireValue} > 0 ? "Whitelisted contracts" : "Custom contracts"}> @@ -414,7 +443,7 @@ class CreateGenericMultiCallScheme extends React.Component component="select" name={`contracts.${index}.method`} type="text" - validate={requireValue} + validate={Validators.requireValue} > {values.contracts[index]?.methods?.map((method: any, j: any) => ( @@ -441,7 +470,7 @@ class CreateGenericMultiCallScheme extends React.Component // eslint-disable-next-line react/jsx-no-bind onBlur={(e: any) => { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method.split("(")[0], setFieldValue, index); }} // eslint-disable-next-line react/jsx-no-bind - validate={(e: any) => validateParam(param.type, e)} + validate={(e: any) => Validators.validateParam(param.type, e)} /> ))} diff --git a/src/components/Proposal/Create/SchemeForms/Validators.ts b/src/components/Proposal/Create/SchemeForms/Validators.ts index 6f5710bfa..14fd4ea35 100644 --- a/src/components/Proposal/Create/SchemeForms/Validators.ts +++ b/src/components/Proposal/Create/SchemeForms/Validators.ts @@ -12,40 +12,11 @@ export const isIntType = (type: string): boolean => type.indexOf("int") === 0; export const isByteType = (type: string): boolean => type.indexOf("byte") === 0; /** - * RegEx to check if a string ends with [] + * RegEx to check if a string ends with or surrounded by [] * @param {string} parameter - * @returns {boolean} true if a string ends with [], otherwise returns false. + * @returns {boolean} true if a string ends with [] or surround by [], otherwise returns false. */ -export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter); - -/** - * Given an array input type returns an input example string. - * @param {string} type The input type (e.g. unit256[], bool[], address[], ...) - * @returns {string} Input example string - */ -export const typeArrayPlaceholder = (type: string): string => { - if (isAddressType(type)) { - return "e.g.: ['0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E','0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e']"; - } - - if (isBooleanType(type)) { - return "e.g.: [true, false, false, true]"; - } - - if (isUintType(type)) { - return "e.g.: [1000, 212, 320000022, 23]"; - } - - if (isIntType(type)) { - return "e.g.: [1000, -212, 1232, -1]"; - } - - if (isByteType(type)) { - return "e.g.: ['0xc00000000000000000000000000000000000', '0xc00000000000000000000000000000000001']"; - } - - return "e.g.: ['first value', 'second value', 'third value']"; -}; +export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter) || /^\[.*\]$/.test(parameter); /** * Given a value returns error message in case value is less than 0 or no value provided @@ -58,7 +29,7 @@ export const requireValue = (value: any): string => { if (value === "") { error = "Required"; } else if (value < 0) { - error = "Please enter a non-negative value"; + error = "Must be a non-negative value"; } return error; }; @@ -72,7 +43,7 @@ export const requireValue = (value: any): string => { export const validateParamValue = (type: string, value: string): undefined | string => { if (isAddressType(type)) { if (!isAddress(value)) { - return "Please enter a valid address"; + return "Must be a valid address"; } } @@ -103,8 +74,6 @@ export const validateParamValue = (type: string, value: string): undefined | str return undefined; }; - - /** * Given an ABI method param type including array type (address, byets32, unit256, bool, bool[], ...) and it's value, returns error message in case validation fails or no value provided. * This function is suitable to work with Formik validations. diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 237be016c..96ce03f6f 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -71,14 +71,14 @@ const DecodedData = (props: IDecodedDataProps) => { }, []); const methodParams = decodedData.names.map((param: string, index: number) => { - return
{param}: {parseParamValue(decodedData.types[index], decodedData.inputs[index])}
; + return
{param}:
{parseParamValue(decodedData.types[index], decodedData.inputs[index])}
; }); return (
{lodaing ?
Loading method info...
: -
Method: {decodedData.method}({decodedData.types.join(",")})
+
Method:
{decodedData.method}({decodedData.types.join(",")})
{methodParams}
}
@@ -111,7 +111,8 @@ export default class ProposalSummary extends React.Component { { proposal.genericSchemeMultiCall.contractsToCall.map((contract, index) => (
-

{`Contract #${index + 1}:`} {{getContractName(contract)} {`(${contract})`}}

+

{`#${index + 1}`}

+

Contract: {getContractName(contract)} {`(${contract})`}

{baseTokenName()} value: {fromWei(proposal.genericSchemeMultiCall.values[index])}

Raw call data:

From 21e26f812664a2b042ae5bca3c6051cc7a0a76f3 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 11 Nov 2020 11:35:18 +0200 Subject: [PATCH 29/41] full method signature in summary --- .../ProposalSummaryMultiCallGenericScheme.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx index 96ce03f6f..2a960c7d8 100644 --- a/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx +++ b/src/components/Proposal/ProposalSummary/ProposalSummaryMultiCallGenericScheme.tsx @@ -55,6 +55,12 @@ const parseParamValue = (type: string, value: any) => { return value; }; +const parseMethodSignature = (decodedData: IDecodedData): string => { + const params = decodedData.names.map((name, index) => { + return `${name}: ${decodedData.types[index]}`; + }); + return `${decodedData.method} (${params})`; +}; const DecodedData = (props: IDecodedDataProps) => { const [lodaing, setLoading] = React.useState(false); @@ -78,7 +84,7 @@ const DecodedData = (props: IDecodedDataProps) => {
{lodaing ?
Loading method info...
: -
Method:
{decodedData.method}({decodedData.types.join(",")})
+
Method:
{parseMethodSignature(decodedData)}
{methodParams}
}
From a75faeddfe518eca43923f0b6886cec80298b847 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 11 Nov 2020 15:29:54 +0200 Subject: [PATCH 30/41] added missing voting params for multi call generic scheme --- src/components/Scheme/SchemeInfoPage.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/Scheme/SchemeInfoPage.tsx b/src/components/Scheme/SchemeInfoPage.tsx index 9e72fd3b7..9b5074827 100644 --- a/src/components/Scheme/SchemeInfoPage.tsx +++ b/src/components/Scheme/SchemeInfoPage.tsx @@ -214,6 +214,19 @@ export default class SchemeInfo extends React.Component {
: "" } + + {scheme.genericSchemeMultiCallParams ? +
+

Genesis Protocol Params -- Learn more

+
+
+ {renderVotingMachineLink(votingMachine)} + {renderGpParams(scheme.genericSchemeMultiCallParams.voteParams)} +
+
+
+ : "" + }
; } } From a4e2b2c411e318118129c1a1423e7b9df225cbb0 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Fri, 13 Nov 2020 00:39:05 +0200 Subject: [PATCH 31/41] ignore network and graphql errors --- src/arc.ts | 21 +++++++++++++++------ src/components/Shared/withSubscription.tsx | 12 +++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/arc.ts b/src/arc.ts index a362c36a7..66db94c34 100644 --- a/src/arc.ts +++ b/src/arc.ts @@ -134,16 +134,25 @@ export async function initializeArc(provider?: any): Promise { // https://www.apollographql.com/docs/link/links/retry/ const retryLink = new RetryLink({ - attempts: (count) => { - return (count !== 10); + attempts: { + max: 5, + retryIf: (error, _operation) => { + // eslint-disable-next-line no-console + console.error("error occurred fetching data, retrying..."); + // eslint-disable-next-line no-console + console.log(error); + return !!error; + }, }, - delay: () => { - // This will give a random delay between retries between the range of 5 to 30 seconds. - return Math.floor(Math.random() * (30000 - 5000 + 1) + 5000); + delay: { + initial: 500, // this is the initial time after the first retry + // next retries )up to max) will be exponential (i..e after 2*iniitial, etc) + jitter: true, + max: Infinity, }, }); - arcSettings.graphqlRetryLink = retryLink; + arcSettings.retryLink = retryLink; // if there is no existing arc, we create a new one if ((window as any).arc) { diff --git a/src/components/Shared/withSubscription.tsx b/src/components/Shared/withSubscription.tsx index 4774796a3..e1e564173 100644 --- a/src/components/Shared/withSubscription.tsx +++ b/src/components/Shared/withSubscription.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { combineLatest, Observable, Subscription } from "rxjs"; import { Subtract } from "utility-types"; +import { GRAPH_POLL_INTERVAL } from "../../settings"; function getDisplayName(wrappedComponent: any): string { return wrappedComponent.displayName || wrappedComponent.name || "Component"; @@ -116,7 +117,16 @@ const withSubscription = , Obse // eslint-disable-next-line no-console console.error(getDisplayName(wrappedComponent), "Error in subscription", error); // this will go to the error page - this.setState(() => { throw error; }); + /** + * The below condition is a workaround to avoid crashing Alchemy when a GraphQL error or a Network error occurs. + * This is due to the way Apollo Client works when such an error occurs - it fails and terminates the observable including the polling. + */ + if (error.message.includes("GraphQL") || error.message.includes("Network")) { + this.subscription.unsubscribe(); + setTimeout(this.setupSubscription.bind(this, observable), GRAPH_POLL_INTERVAL); + } else { + this.setState(() => { throw error; }); + } }, () => { this.setState({ complete: true, From 54c21f408860480a16b0abd6742642f3f135d39f Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Sun, 15 Nov 2020 14:27:33 +0200 Subject: [PATCH 32/41] TO DO: need to reset attempts after success --- src/arc.ts | 2 +- src/components/Shared/withSubscription.tsx | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/arc.ts b/src/arc.ts index 66db94c34..995530c90 100644 --- a/src/arc.ts +++ b/src/arc.ts @@ -152,7 +152,7 @@ export async function initializeArc(provider?: any): Promise { }, }); - arcSettings.retryLink = retryLink; + arcSettings.graphqlRetryLink = retryLink; // if there is no existing arc, we create a new one if ((window as any).arc) { diff --git a/src/components/Shared/withSubscription.tsx b/src/components/Shared/withSubscription.tsx index e1e564173..40d65aa9a 100644 --- a/src/components/Shared/withSubscription.tsx +++ b/src/components/Shared/withSubscription.tsx @@ -93,7 +93,7 @@ const withSubscription = , Obse }; } - public async setupSubscription(observable?: Observable) { + public async setupSubscription(observable?: Observable, currentAttempt = 0) { this.teardownSubscription(); @@ -114,17 +114,18 @@ const withSubscription = , Obse }); }, (error: Error) => { - // eslint-disable-next-line no-console - console.error(getDisplayName(wrappedComponent), "Error in subscription", error); - // this will go to the error page /** * The below condition is a workaround to avoid crashing Alchemy when a GraphQL error or a Network error occurs. * This is due to the way Apollo Client works when such an error occurs - it fails and terminates the observable including the polling. */ - if (error.message.includes("GraphQL") || error.message.includes("Network")) { + if ((error.message.includes("GraphQL") || error.message.includes("Network")) && currentAttempt < 10) { + currentAttempt ++; this.subscription.unsubscribe(); - setTimeout(this.setupSubscription.bind(this, observable), GRAPH_POLL_INTERVAL); + setTimeout(this.setupSubscription.bind(this, observable, currentAttempt), GRAPH_POLL_INTERVAL); } else { + // eslint-disable-next-line no-console + console.error(getDisplayName(wrappedComponent), "Error in subscription", error); + // this will go to the error page this.setState(() => { throw error; }); } }, From b08f33b7a1d3eb82c729490659fa86cb9b763528 Mon Sep 17 00:00:00 2001 From: Razzwan Date: Sun, 15 Nov 2020 16:33:02 +0200 Subject: [PATCH 33/41] feat: add link xGEN --> GEN (#2258) --- src/layouts/SidebarMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layouts/SidebarMenu.tsx b/src/layouts/SidebarMenu.tsx index 97123d6b0..5329c514a 100644 --- a/src/layouts/SidebarMenu.tsx +++ b/src/layouts/SidebarMenu.tsx @@ -229,6 +229,7 @@ class SidebarMenu extends React.Component { Feed +
  • xGEN / GEN
  • $ Buy GEN
      From 4e9d6a02b7a8964077d4b46db7ef4328385695c7 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 16 Nov 2020 15:50:53 +0200 Subject: [PATCH 34/41] remove retryLink --- src/arc.ts | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/arc.ts b/src/arc.ts index 995530c90..8ddc6b5dd 100644 --- a/src/arc.ts +++ b/src/arc.ts @@ -1,7 +1,6 @@ import { NotificationStatus } from "reducers/notifications"; import { getNetworkId, getNetworkName, targetedNetwork } from "./lib/util"; import { settings, USE_CONTRACTINFOS_CACHE } from "./settings"; -import { RetryLink } from "apollo-link-retry"; import { Address, Arc } from "@daostack/arc.js"; import Web3Modal, { getProviderInfo, IProviderInfo } from "web3modal"; import { Observable } from "rxjs"; @@ -132,28 +131,6 @@ export async function initializeArc(provider?: any): Promise { const readonly = typeof provider === "string"; - // https://www.apollographql.com/docs/link/links/retry/ - const retryLink = new RetryLink({ - attempts: { - max: 5, - retryIf: (error, _operation) => { - // eslint-disable-next-line no-console - console.error("error occurred fetching data, retrying..."); - // eslint-disable-next-line no-console - console.log(error); - return !!error; - }, - }, - delay: { - initial: 500, // this is the initial time after the first retry - // next retries )up to max) will be exponential (i..e after 2*iniitial, etc) - jitter: true, - max: Infinity, - }, - }); - - arcSettings.graphqlRetryLink = retryLink; - // if there is no existing arc, we create a new one if ((window as any).arc) { arc = (window as any).arc; From 3bd1dfbd0cea3c16719517fc9dfa4857921ebabb Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 16 Nov 2020 18:47:16 +0200 Subject: [PATCH 35/41] reset currentAttempt on success --- src/components/Shared/withSubscription.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Shared/withSubscription.tsx b/src/components/Shared/withSubscription.tsx index 40d65aa9a..60739a1bf 100644 --- a/src/components/Shared/withSubscription.tsx +++ b/src/components/Shared/withSubscription.tsx @@ -108,6 +108,7 @@ const withSubscription = , Obse this.subscription = this.observable.subscribe( (next: ObservableType) => { + currentAttempt = 0; // reset on success this.setState({ data: next, isLoading: false, From d2bfdb291162867c9a5394c97cd0b43acbfcc233 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Mon, 16 Nov 2020 19:07:36 +0200 Subject: [PATCH 36/41] fix wrong share link --- src/components/Proposal/ProposalDetailsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Proposal/ProposalDetailsPage.tsx b/src/components/Proposal/ProposalDetailsPage.tsx index 94d22c70b..5072315a9 100644 --- a/src/components/Proposal/ProposalDetailsPage.tsx +++ b/src/components/Proposal/ProposalDetailsPage.tsx @@ -5,7 +5,7 @@ import AccountProfileName from "components/Account/AccountProfileName"; import ProposalCountdown from "components/Shared/ProposalCountdown"; import FollowButton from "components/Shared/FollowButton"; import { DiscussionEmbed } from "disqus-react"; -import { humanProposalTitle, ensureHttps, formatFriendlyDateForLocalTimezone, safeMoment } from "lib/util"; +import { humanProposalTitle, ensureHttps, formatFriendlyDateForLocalTimezone, safeMoment, targetedNetwork } from "lib/util"; import { schemeName } from "lib/schemeUtils"; import Analytics from "lib/analytics"; import { Page } from "pages"; @@ -336,7 +336,7 @@ class ProposalDetailsPage extends React.Component { {this.state.showShareModal ? : "" }
  • From e0184bbb112751c65c187d6139e89a1c0456021e Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 17 Nov 2020 09:46:56 +0200 Subject: [PATCH 37/41] use BASE_URL env var --- src/components/Proposal/ProposalDetailsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Proposal/ProposalDetailsPage.tsx b/src/components/Proposal/ProposalDetailsPage.tsx index 5072315a9..f79ebf57f 100644 --- a/src/components/Proposal/ProposalDetailsPage.tsx +++ b/src/components/Proposal/ProposalDetailsPage.tsx @@ -5,7 +5,7 @@ import AccountProfileName from "components/Account/AccountProfileName"; import ProposalCountdown from "components/Shared/ProposalCountdown"; import FollowButton from "components/Shared/FollowButton"; import { DiscussionEmbed } from "disqus-react"; -import { humanProposalTitle, ensureHttps, formatFriendlyDateForLocalTimezone, safeMoment, targetedNetwork } from "lib/util"; +import { humanProposalTitle, ensureHttps, formatFriendlyDateForLocalTimezone, safeMoment } from "lib/util"; import { schemeName } from "lib/schemeUtils"; import Analytics from "lib/analytics"; import { Page } from "pages"; @@ -336,7 +336,7 @@ class ProposalDetailsPage extends React.Component { {this.state.showShareModal ? : "" }
    From 21ee05b79db89213962613b09ef1cf9dbc93600c Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Tue, 17 Nov 2020 10:11:58 +0200 Subject: [PATCH 38/41] update change log --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 829c8990e..aab103a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ - Features Added - Support Multi-call Generic Scheme - Use subgraph v40_0 and arc.js 0.2.76 + - Added xGEN / GEN bridge - Bugs Fixed - Fix competiotion tab crash - Better name for some Competition plugins + - Fix wrong link when sharing a proposal + - Ignore Network and GraphQL errors up to 10 attempts in a row ## 1.0.1 - Bugs Fixed From be00c88608ab37d48ff1ed74cb53cae84a817359 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Wed, 18 Nov 2020 11:59:00 +0200 Subject: [PATCH 39/41] update example in multi-call --- .../Create/SchemeForms/CreateGenericMultiCallProposal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index fa4a139c8..79deb9087 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -76,7 +76,7 @@ interface IFormValues { */ const typeArrayPlaceholder = (type: string): string => { if (Validators.isAddressType(type)) { - return "e.g: ['0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E','0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e']"; + return "e.g: [\"0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E\",\"0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e\"]"; } if (Validators.isBooleanType(type)) { @@ -92,10 +92,10 @@ const typeArrayPlaceholder = (type: string): string => { } if (Validators.isByteType(type)) { - return "e.g: ['0xc00000000000000000000000000000000000', '0xc00000000000000000000000000000000001']"; + return "e.g: [\"0xc00000000000000000000000000000000000\", \"0xc00000000000000000000000000000000001\"]"; } - return "e.g: ['first value', 'second value', 'third value']"; + return "e.g: [\"first value\", \"second value\", \"third value\"]"; }; class CreateGenericMultiCallScheme extends React.Component { From fffd07bf43b09cc4ff9ef916e01a5245ad649099 Mon Sep 17 00:00:00 2001 From: Roie Natan Date: Thu, 19 Nov 2020 12:43:03 +0200 Subject: [PATCH 40/41] upgrade wdio chrome version --- test/integration/wdio.conf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/wdio.conf.js b/test/integration/wdio.conf.js index 784cb317b..cb43e908e 100644 --- a/test/integration/wdio.conf.js +++ b/test/integration/wdio.conf.js @@ -124,7 +124,7 @@ exports.config = { chrome: { // check for more recent versions of chrome driver here: // https://chromedriver.storage.googleapis.com/index.html - version: "85.0.4183.87", + version: "87.0.4280.20", arch: process.arch, baseURL: "https://chromedriver.storage.googleapis.com", }, @@ -139,7 +139,7 @@ exports.config = { chrome: { // check for more recent versions of chrome driver here: // https://chromedriver.storage.googleapis.com/index.html - version: "85.0.4183.87", + version: "87.0.4280.20", arch: process.arch, baseURL: "https://chromedriver.storage.googleapis.com", }, From 2c0784c21ba32a2a257daa78a9670310cee6c8cc Mon Sep 17 00:00:00 2001 From: Razzwan Date: Thu, 19 Nov 2020 14:00:17 +0200 Subject: [PATCH 41/41] fix: update chrome version for silenium (#2272)