From 6cf889ff756d57ffe5ec1b1776e7a54f980919e5 Mon Sep 17 00:00:00 2001 From: Oleh Misarosh Date: Sun, 2 Oct 2022 14:12:22 +0300 Subject: [PATCH 1/4] Add contract address calculator based on from and nonce --- package.json | 1 + .../index.page.tsx | 102 ++++++++++++++++++ pages/vanity-address-generator/index.page.tsx | 5 +- src/components/ToolTree.tsx | 4 + src/misc/validation/schemas/addressSchema.ts | 5 + src/misc/validation/schemas/decimalSchema.ts | 20 ++-- .../validation/validators/addressValidator.ts | 9 ++ .../validation/validators/numberValidator.ts | 10 +- 8 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 pages/contract-address-calculator/index.page.tsx create mode 100644 src/misc/validation/schemas/addressSchema.ts create mode 100644 src/misc/validation/validators/addressValidator.ts diff --git a/package.json b/package.json index 7782ce8..f582260 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@ethereumjs/common": "^2.6.3", "@ethereumjs/tx": "^3.5.1", "@ethersproject/abi": "^5.6.0", + "@ethersproject/address": "^5.6.0", "@ethersproject/bignumber": "^5.5.0", "@ethersproject/logger": "^5.6.0", "@ethersproject/strings": "5.0.0", diff --git a/pages/contract-address-calculator/index.page.tsx b/pages/contract-address-calculator/index.page.tsx new file mode 100644 index 0000000..80fe8eb --- /dev/null +++ b/pages/contract-address-calculator/index.page.tsx @@ -0,0 +1,102 @@ +import { getContractAddress } from '@ethersproject/address'; +import React, { ReactElement, useState } from 'react'; + +import { CopyableConversionInput } from '../../src/components/CopyableConversionInput'; +import { CalculatorIcon } from '../../src/components/icons/CalculatorIcon'; +import { Button } from '../../src/components/lib/Button'; +import { Header } from '../../src/components/lib/Header'; +import { NodeBlock } from '../../src/components/lib/NodeBlock'; +import { ToolContainer } from '../../src/components/ToolContainer'; +import { handleChangeValidated } from '../../src/misc/handleChangeValidated'; +import { WithError } from '../../src/misc/types'; +import { addressValidator } from '../../src/misc/validation/validators/addressValidator'; +import { numberValidator } from '../../src/misc/validation/validators/numberValidator'; + +export default function ContractAddressCalculator(): ReactElement { + const [fromAddress, setFromAddress] = useState>({ + value: '', + }); + const [nonce, setNonce] = useState>({ + value: '', + }); + + const [calculatedContractAddress, setCalculatedContractAddress] = useState< + string | undefined + >(); + + const flushResults = (): void => setCalculatedContractAddress(undefined); + + const handleChangeFromAddress = (newValue: string): void => + handleChangeValidated({ + newValue, + validateFn: (newValue) => addressValidator(newValue), + setState: setFromAddress, + flushFn: flushResults, + }); + + const handleChangeNonce = (newValue: string): void => + handleChangeValidated({ + newValue, + validateFn: (newValue) => numberValidator(newValue), + setState: setNonce, + flushFn: flushResults, + }); + + const handleCalculateContractAddress = (): void => { + try { + setCalculatedContractAddress( + getContractAddress({ + from: fromAddress.value, + nonce: nonce.value, + }), + ); + } catch { + flushResults(); + } + }; + + const calculateButtonIsDisabled = Boolean( + !fromAddress.value || fromAddress.error || !nonce.value || nonce.error, + ); + + return ( + +
+
} + text={['Calculators', 'Contract Address Calculator']} + /> +
+ handleChangeFromAddress(event.target.value)} + /> + handleChangeNonce(event.target.value)} + /> +
+ + + {calculatedContractAddress && ( +
+ + Contract address: + +
+ )} + + ); +} diff --git a/pages/vanity-address-generator/index.page.tsx b/pages/vanity-address-generator/index.page.tsx index 0991c45..68054ca 100644 --- a/pages/vanity-address-generator/index.page.tsx +++ b/pages/vanity-address-generator/index.page.tsx @@ -91,7 +91,10 @@ export default function VanityAddressGenerator(): ReactElement { const handleChangeCpuCoreCount = (newValue: string): void => handleChangeValidated({ newValue, - validateFn: (newValue) => numberValidator(newValue), + validateFn: (newValue) => + numberValidator(newValue, (schema) => + schema.refine((n) => n >= 0 && n <= 128), + ), setState: setCpuCores, flushFn: flushResults, }); diff --git a/src/components/ToolTree.tsx b/src/components/ToolTree.tsx index e046858..9b9c6c4 100644 --- a/src/components/ToolTree.tsx +++ b/src/components/ToolTree.tsx @@ -101,6 +101,10 @@ const tree: Tree = { title: 'String Bytes32 Conversion', pageHref: 'string-bytes32-conversion', }, + { + title: 'Contract Address Calculator', + pageHref: 'contract-address-calculator', + }, ], }, decoders: { diff --git a/src/misc/validation/schemas/addressSchema.ts b/src/misc/validation/schemas/addressSchema.ts new file mode 100644 index 0000000..fc37fc9 --- /dev/null +++ b/src/misc/validation/schemas/addressSchema.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const addressSchema = z.string().regex(/^0x[0-9a-fA-F]{40}$/, { + message: 'The value must be a valid address', +}); diff --git a/src/misc/validation/schemas/decimalSchema.ts b/src/misc/validation/schemas/decimalSchema.ts index 7b3e2be..47ae4dd 100644 --- a/src/misc/validation/schemas/decimalSchema.ts +++ b/src/misc/validation/schemas/decimalSchema.ts @@ -1,14 +1,10 @@ import { z } from 'zod'; -export const decimalSchema = z - .number() - .or( - z - .string() - .regex(/^\d*$/, { - message: - 'The value must be a hexadecimal number, 0x-prefix is required', - }) - .transform(Number), - ) - .refine((n) => n >= 0 && n <= 128); +export const decimalSchema = z.number().or( + z + .string() + .regex(/^\d*$/, { + message: 'The value must be a decimal number', + }) + .transform(Number), +); diff --git a/src/misc/validation/validators/addressValidator.ts b/src/misc/validation/validators/addressValidator.ts new file mode 100644 index 0000000..454da4e --- /dev/null +++ b/src/misc/validation/validators/addressValidator.ts @@ -0,0 +1,9 @@ +import { zodResultMessage } from '../../../../src/misc/zodResultMessage'; +import { addressSchema } from '../schemas/addressSchema'; +import { ValidatorResult } from './result'; + +export function addressValidator(newValue: string): ValidatorResult { + const validated = addressSchema.safeParse(newValue); + if (validated.success) return { success: true }; + else return { success: false, error: zodResultMessage(validated) }; +} diff --git a/src/misc/validation/validators/numberValidator.ts b/src/misc/validation/validators/numberValidator.ts index 8df1839..10e560d 100644 --- a/src/misc/validation/validators/numberValidator.ts +++ b/src/misc/validation/validators/numberValidator.ts @@ -1,9 +1,15 @@ +import _ from 'lodash'; +import { ZodTypeAny } from 'zod'; + import { zodResultMessage } from '../../../../src/misc/zodResultMessage'; import { decimalSchema } from '../schemas/decimalSchema'; import { ValidatorResult } from './result'; -export function numberValidator(newValue: string): ValidatorResult { - const validated = decimalSchema.safeParse(newValue); +export function numberValidator( + newValue: string, + enhanceSchema: (schema: ZodTypeAny) => ZodTypeAny = _.identity, +): ValidatorResult { + const validated = enhanceSchema(decimalSchema).safeParse(newValue); if (validated.success) return { success: true }; else return { success: false, error: zodResultMessage(validated) }; } From c2dec2b8d6b11a9c8f9bf66bd5392798f6642241 Mon Sep 17 00:00:00 2001 From: Oleh Misarosh Date: Mon, 1 May 2023 22:05:04 +0300 Subject: [PATCH 2/4] Use named functions --- pages/contract-address-calculator/index.page.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pages/contract-address-calculator/index.page.tsx b/pages/contract-address-calculator/index.page.tsx index 80fe8eb..42bc2cd 100644 --- a/pages/contract-address-calculator/index.page.tsx +++ b/pages/contract-address-calculator/index.page.tsx @@ -24,25 +24,29 @@ export default function ContractAddressCalculator(): ReactElement { string | undefined >(); - const flushResults = (): void => setCalculatedContractAddress(undefined); + function flushResults(): void { + setCalculatedContractAddress(undefined); + } - const handleChangeFromAddress = (newValue: string): void => + function handleChangeFromAddress(newValue: string): void { handleChangeValidated({ newValue, validateFn: (newValue) => addressValidator(newValue), setState: setFromAddress, flushFn: flushResults, }); + } - const handleChangeNonce = (newValue: string): void => + function handleChangeNonce(newValue: string): void { handleChangeValidated({ newValue, validateFn: (newValue) => numberValidator(newValue), setState: setNonce, flushFn: flushResults, }); + } - const handleCalculateContractAddress = (): void => { + function handleCalculateContractAddress(): void { try { setCalculatedContractAddress( getContractAddress({ @@ -53,7 +57,7 @@ export default function ContractAddressCalculator(): ReactElement { } catch { flushResults(); } - }; + } const calculateButtonIsDisabled = Boolean( !fromAddress.value || fromAddress.error || !nonce.value || nonce.error, From bb6d56ccf0b6306e2f891b71848274057343e038 Mon Sep 17 00:00:00 2001 From: Oleh Misarosh Date: Mon, 1 May 2023 22:05:21 +0300 Subject: [PATCH 3/4] Use specific import instead of default import --- src/misc/validation/validators/numberValidator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/misc/validation/validators/numberValidator.ts b/src/misc/validation/validators/numberValidator.ts index 10e560d..2d5fb36 100644 --- a/src/misc/validation/validators/numberValidator.ts +++ b/src/misc/validation/validators/numberValidator.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { identity } from 'lodash'; import { ZodTypeAny } from 'zod'; import { zodResultMessage } from '../../../../src/misc/zodResultMessage'; @@ -7,7 +7,7 @@ import { ValidatorResult } from './result'; export function numberValidator( newValue: string, - enhanceSchema: (schema: ZodTypeAny) => ZodTypeAny = _.identity, + enhanceSchema: (schema: ZodTypeAny) => ZodTypeAny = identity, ): ValidatorResult { const validated = enhanceSchema(decimalSchema).safeParse(newValue); if (validated.success) return { success: true }; From aaab275bc9aa58b351641567ff243eabe204c391 Mon Sep 17 00:00:00 2001 From: Oleh Misarosh Date: Mon, 1 May 2023 22:17:17 +0300 Subject: [PATCH 4/4] Add tests for contract address calculator --- pages/contract-address-calculator/index.page.tsx | 2 +- src/lib/contractAddress.test.ts | 14 ++++++++++++++ src/lib/contractAddress.ts | 8 ++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/lib/contractAddress.test.ts create mode 100644 src/lib/contractAddress.ts diff --git a/pages/contract-address-calculator/index.page.tsx b/pages/contract-address-calculator/index.page.tsx index 42bc2cd..d1b51e6 100644 --- a/pages/contract-address-calculator/index.page.tsx +++ b/pages/contract-address-calculator/index.page.tsx @@ -1,4 +1,3 @@ -import { getContractAddress } from '@ethersproject/address'; import React, { ReactElement, useState } from 'react'; import { CopyableConversionInput } from '../../src/components/CopyableConversionInput'; @@ -7,6 +6,7 @@ import { Button } from '../../src/components/lib/Button'; import { Header } from '../../src/components/lib/Header'; import { NodeBlock } from '../../src/components/lib/NodeBlock'; import { ToolContainer } from '../../src/components/ToolContainer'; +import { getContractAddress } from '../../src/lib/contractAddress'; import { handleChangeValidated } from '../../src/misc/handleChangeValidated'; import { WithError } from '../../src/misc/types'; import { addressValidator } from '../../src/misc/validation/validators/addressValidator'; diff --git a/src/lib/contractAddress.test.ts b/src/lib/contractAddress.test.ts new file mode 100644 index 0000000..43c5ec5 --- /dev/null +++ b/src/lib/contractAddress.test.ts @@ -0,0 +1,14 @@ +import { expect } from 'earljs'; + +import { getContractAddress } from './contractAddress'; + +describe(getContractAddress.name, () => { + it('calculates a contract address based on a from address and a nonce', () => { + expect( + getContractAddress({ + from: '0x9C33eaCc2F50E39940D3AfaF2c7B8246B681A374', + nonce: '0', + }), + ).toEqual('0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'); + }); +}); diff --git a/src/lib/contractAddress.ts b/src/lib/contractAddress.ts new file mode 100644 index 0000000..b7ccb6d --- /dev/null +++ b/src/lib/contractAddress.ts @@ -0,0 +1,8 @@ +import { getContractAddress as getContractAddressImpl } from '@ethersproject/address'; + +export function getContractAddress(params: { + from: string; + nonce: string; +}): string { + return getContractAddressImpl(params); +}