From 0067af40212e5bca6f5cf869d6d251785692e726 Mon Sep 17 00:00:00 2001 From: Dawson Botsford Date: Sun, 7 Apr 2024 17:26:58 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Enable=20address[]=20decoding=20(#2?= =?UTF-8?q?78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix package-lock allowed versions * Add address[] support * prettier sort imports --- .../src/components/Contributions/index.tsx | 2 +- .../src/components/HomepageFeatures/index.tsx | 2 +- docusaurus/src/pages/getting_started.tsx | 6 +- package-lock.json | 2 +- package.json | 5 +- scripts/markdown-magic/generate-readme.ts | 2 +- scripts/markdown-magic/parse-typedoc.ts | 2 +- src/classes/test/Contract/crv-abi.ts | 75 ------------------- src/classes/test/Contract/crv.test.ts | 1 - src/classes/test/Contract/ens.test.ts | 3 +- src/classes/test/Contract/fei.test.ts | 1 - src/classes/test/Contract/jokerrace-abi.ts | 10 +++ .../Contract/jokerrace.integration.test.ts | 19 +++++ src/classes/test/Contract/uni.test.ts | 1 - .../utils/encode-decode-transaction.ts | 17 ++++- src/index.ts | 63 ++++++++-------- 16 files changed, 87 insertions(+), 124 deletions(-) create mode 100644 src/classes/test/Contract/jokerrace-abi.ts create mode 100644 src/classes/test/Contract/jokerrace.integration.test.ts diff --git a/docusaurus/src/components/Contributions/index.tsx b/docusaurus/src/components/Contributions/index.tsx index 9b3efef1..f507057d 100644 --- a/docusaurus/src/components/Contributions/index.tsx +++ b/docusaurus/src/components/Contributions/index.tsx @@ -1,6 +1,6 @@ +import Link from '@docusaurus/Link'; import clsx from 'clsx'; import React from 'react'; -import Link from '@docusaurus/Link'; interface ContributeItem { title: string; diff --git a/docusaurus/src/components/HomepageFeatures/index.tsx b/docusaurus/src/components/HomepageFeatures/index.tsx index 9a519288..4d5f0c49 100644 --- a/docusaurus/src/components/HomepageFeatures/index.tsx +++ b/docusaurus/src/components/HomepageFeatures/index.tsx @@ -1,7 +1,7 @@ +import Link from '@docusaurus/Link'; import CodeBlock from '@theme/CodeBlock'; import clsx from 'clsx'; import React from 'react'; -import Link from '@docusaurus/Link'; import HomepageContributions from '../Contributions'; interface FeatureItem { diff --git a/docusaurus/src/pages/getting_started.tsx b/docusaurus/src/pages/getting_started.tsx index 08ef7bd8..cb8d81d3 100644 --- a/docusaurus/src/pages/getting_started.tsx +++ b/docusaurus/src/pages/getting_started.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import Link from '@docusaurus/Link'; +import CodeBlock from '@theme/CodeBlock'; import Layout from '@theme/Layout'; import clsx from 'clsx'; -import CodeBlock from '@theme/CodeBlock'; -import Link from '@docusaurus/Link'; +import React from 'react'; function GettingStarted() { return ( diff --git a/package-lock.json b/package-lock.json index c9b40364..0b55d9a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "zod": "^3.21.4" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@ampproject/remapping": { diff --git a/package.json b/package.json index 0372bfdd..4c92b5c1 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,10 @@ "prettier": { "trailingComma": "all", "tabWidth": 2, - "singleQuote": true + "singleQuote": true, + "plugins": [ + "prettier-plugin-organize-imports" + ] }, "keywords": [ "ethereum", diff --git a/scripts/markdown-magic/generate-readme.ts b/scripts/markdown-magic/generate-readme.ts index f179e077..790586c5 100644 --- a/scripts/markdown-magic/generate-readme.ts +++ b/scripts/markdown-magic/generate-readme.ts @@ -1,5 +1,5 @@ -import { functionsMarkdown, providerMarkdown } from './parse-typedoc'; import path from 'path'; +import { functionsMarkdown, providerMarkdown } from './parse-typedoc'; import markdownMagic from 'markdown-magic'; import { fileURLToPath } from 'url'; diff --git a/scripts/markdown-magic/parse-typedoc.ts b/scripts/markdown-magic/parse-typedoc.ts index 0e678579..52dbe4f3 100644 --- a/scripts/markdown-magic/parse-typedoc.ts +++ b/scripts/markdown-magic/parse-typedoc.ts @@ -2,8 +2,8 @@ * Parse a typedoc output file and generate the markdown code * This markdown generated is injected into the root readme via markdown-magic */ -import stats from './typedoc.out.json'; import z from 'zod'; +import stats from './typedoc.out.json'; const functions = z .array(z.number()) diff --git a/src/classes/test/Contract/crv-abi.ts b/src/classes/test/Contract/crv-abi.ts index 72b4a1c4..556d3d97 100644 --- a/src/classes/test/Contract/crv-abi.ts +++ b/src/classes/test/Contract/crv-abi.ts @@ -5,81 +5,6 @@ export const abi: JSONABI = // http://api.etherscan.io/api?module=contract&action=getabi&address=0x575CCD8e2D300e2377B43478339E364000318E2c&format=raw [ - { - name: 'Fund', - inputs: [ - { - type: 'address', - name: 'recipient', - indexed: true, - }, - { - type: 'uint256', - name: 'amount', - indexed: false, - }, - ], - anonymous: false, - type: 'event', - }, - { - name: 'Claim', - inputs: [ - { - type: 'address', - name: 'recipient', - indexed: true, - }, - { - type: 'uint256', - name: 'claimed', - indexed: false, - }, - ], - anonymous: false, - type: 'event', - }, - { - name: 'ToggleDisable', - inputs: [ - { - type: 'address', - name: 'recipient', - indexed: false, - }, - { - type: 'bool', - name: 'disabled', - indexed: false, - }, - ], - anonymous: false, - type: 'event', - }, - { - name: 'CommitOwnership', - inputs: [ - { - type: 'address', - name: 'admin', - indexed: false, - }, - ], - anonymous: false, - type: 'event', - }, - { - name: 'ApplyOwnership', - inputs: [ - { - type: 'address', - name: 'admin', - indexed: false, - }, - ], - anonymous: false, - type: 'event', - }, { outputs: [], inputs: [ diff --git a/src/classes/test/Contract/crv.test.ts b/src/classes/test/Contract/crv.test.ts index c8fe34b0..4aab4a74 100644 --- a/src/classes/test/Contract/crv.test.ts +++ b/src/classes/test/Contract/crv.test.ts @@ -4,7 +4,6 @@ import { rpcUrls } from '../../../providers/test/rpc-urls'; import { Contract as EssentialEthContract } from '../../Contract'; import { abi } from './crv-abi'; -// The JSONABI const JSONABI = abi; const rpcURL = rpcUrls.mainnet; diff --git a/src/classes/test/Contract/ens.test.ts b/src/classes/test/Contract/ens.test.ts index aead7245..9514c34d 100644 --- a/src/classes/test/Contract/ens.test.ts +++ b/src/classes/test/Contract/ens.test.ts @@ -3,7 +3,6 @@ import { Contract as EssentialEthContract } from '../../Contract'; import { rpcUrls } from './../../../providers/test/rpc-urls'; import { ensABI } from './ens-abi'; -// The JSONABI const JSONABI = ensABI; const rpcURL = rpcUrls.mainnet; @@ -27,6 +26,6 @@ const labelHash = describe('eNS Base Registrar Expiration', () => { it('should detect expiration properly', async () => { const expiration = await essentialEthContract.nameExpires(labelHash); - expect(expiration.toNumber()).toBe(1853233633); + expect(expiration.toNumber()).toBeGreaterThan(2010913632); }); }); diff --git a/src/classes/test/Contract/fei.test.ts b/src/classes/test/Contract/fei.test.ts index 55ed4142..7d59a3d5 100644 --- a/src/classes/test/Contract/fei.test.ts +++ b/src/classes/test/Contract/fei.test.ts @@ -4,7 +4,6 @@ import { Contract as EssentialEthContract } from '../../Contract'; import { rpcUrls } from './../../../providers/test/rpc-urls'; import { feiABI } from './fei-abi'; -// The JSONABI const JSONABI = feiABI; const rpcURL = rpcUrls.mainnet; diff --git a/src/classes/test/Contract/jokerrace-abi.ts b/src/classes/test/Contract/jokerrace-abi.ts new file mode 100644 index 00000000..d989859d --- /dev/null +++ b/src/classes/test/Contract/jokerrace-abi.ts @@ -0,0 +1,10 @@ +import type { JSONABI } from '../../../types/Contract.types'; + +export const abi: JSONABI = [ + { + name: 'getAllAddressesThatHaveVoted', + outputs: [{ type: 'address[]', name: '' }], + inputs: [], + type: 'function', + }, +]; diff --git a/src/classes/test/Contract/jokerrace.integration.test.ts b/src/classes/test/Contract/jokerrace.integration.test.ts new file mode 100644 index 00000000..dab7060b --- /dev/null +++ b/src/classes/test/Contract/jokerrace.integration.test.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; +import { isAddress } from '../../../utils/is-address'; +import { decodeRPCResponse } from '../../utils/encode-decode-transaction'; +import { abi } from './jokerrace-abi'; + +describe('jokerrace contract', () => { + it('should decode "address[]"', async () => { + const nodeResponse = + '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001300000000000000000000000067243d6c3c3bdc2f59d2f74ba1949a02973a529d0000000000000000000000000b06ca5dcc8a10be0951d4e140d4312702b8d0ec0000000000000000000000003fad8bcd2aea732d02a203c156b19205253f2a0600000000000000000000000073186b2a81952c2340c4eb2e74e89869e1183df000000000000000000000000087ece9936ad2254d03af83958fe3b202dc79793f00000000000000000000000068f272fcaae074cb33e68d88a32c325ed0df8379000000000000000000000000c9d20533c5b8a79526377e5d05dc79b87b28e92f000000000000000000000000e04885c3f1419c6e8495c33bdcf5f8387cd888460000000000000000000000000c887420937d8f9305ff872eaa5aaf5e379a811a0000000000000000000000007234c36a71ec237c2ae7698e8916e0735001e9af0000000000000000000000001c9f765c579f94f6502acd9fc356171d85a1f8d0000000000000000000000000ca85c622d4c61047f96e352cb919695486a193e6000000000000000000000000c11c6f47fe090a706ba82964b8a98f1682b244ff000000000000000000000000020f64f264ab7e90ef24a108c379a796a82175df000000000000000000000000eb2ee1250dc8c954da4eff4df0e4467a1ca6af6c0000000000000000000000003b60e31cfc48a9074cd5bebb26c9eaa77650a43f00000000000000000000000068d36dcbdd7bbf206e27134f28103abe7cf972df0000000000000000000000001a9cee6e1d21c3c09fb83a980ea54299f01920cd0000000000000000000000001d3bf13f8f7a83390d03db5e23a950778e1d1309'; + const addresses = z + .array(z.string()) + .parse(decodeRPCResponse(abi[0], nodeResponse)); + expect(addresses.length).toBeGreaterThan(18); + expect(addresses).toContain('0x0b06ca5DcC8A10Be0951d4E140D4312702B8D0EC'); + addresses.map((address) => { + expect(isAddress(address)).toBe(true); + }); + }); +}); diff --git a/src/classes/test/Contract/uni.test.ts b/src/classes/test/Contract/uni.test.ts index a2e71e92..e15824aa 100644 --- a/src/classes/test/Contract/uni.test.ts +++ b/src/classes/test/Contract/uni.test.ts @@ -3,7 +3,6 @@ import { Contract as EssentialEthContract } from '../../Contract'; import { rpcUrls } from './../../../providers/test/rpc-urls'; import { uniswapABI } from './uniswap-abi'; -// The JSONABI const JSONABI = uniswapABI; const rpcURL = rpcUrls.mainnet; diff --git a/src/classes/utils/encode-decode-transaction.ts b/src/classes/utils/encode-decode-transaction.ts index c3750b1c..a18afbee 100644 --- a/src/classes/utils/encode-decode-transaction.ts +++ b/src/classes/utils/encode-decode-transaction.ts @@ -49,7 +49,8 @@ function expandType(type: ContractTypes) { // https://docs.soliditylang.org/en/v0.8.7/types.html#integers if (type === 'uint[]') { return 'uint256[]'; - } else if (type === 'int[]') { + } + if (type === 'int[]') { return 'int256[]'; } return type; @@ -171,8 +172,17 @@ export function decodeRPCResponse( return hexToUtf8(hexToDecode); } // chunk response every 64 characters - const encodedOutputs = slicedResponse.match(/.{1,64}/g); - const outputs = (encodedOutputs || []).map((output: string, i: number) => { + const encodedOutputs = slicedResponse.match(/.{1,64}/g) || []; + if ( + jsonABIArgument?.outputs?.length === 1 && + jsonABIArgument.outputs[0].type === 'address[]' + ) { + const unformattedAddresses = encodedOutputs.slice(2); + return unformattedAddresses.map((unformattedAddress) => { + return toChecksumAddress(`0x${unformattedAddress.slice(24)}`); + }); + } + const outputs = encodedOutputs.map((output: string, i: number) => { const outputType = (rawOutputs || [])[i].type; switch (outputType) { case 'bool': @@ -187,6 +197,7 @@ export function decodeRPCResponse( return `0x${output}`; case 'uint8': return Number(hexToDecimal(`0x${output}`)); + default: throw new Error( `essential-eth does not yet support "${outputType}" outputs. Make a PR today!"`, diff --git a/src/index.ts b/src/index.ts index dcd673e1..668292ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,12 @@ -export { Contract, BaseContract } from './classes/Contract'; +export { BaseContract, Contract } from './classes/Contract'; export { AlchemyProvider } from './providers/AlchemyProvider'; export { - FallthroughProvider, ConstructorOptions, + FallthroughProvider, } from './providers/FallthroughProvider'; export { jsonRpcProvider, JsonRpcProvider } from './providers/JsonRpcProvider'; export { tinyBig, TinyBig } from './shared/tiny-big/tiny-big'; -export { BlockResponse, RPCBlock, BlockTag } from './types/Block.types'; +export { BlockResponse, BlockTag, RPCBlock } from './types/Block.types'; export { ContractTypes, JSONABI, @@ -15,50 +15,49 @@ export { export { Filter, FilterByBlockHash } from './types/Filter.types'; export { Network } from './types/Network.types'; export { - TransactionResponse, + BlockTransactionResponse, + Log, + RPCLog, RPCTransaction, RPCTransactionReceipt, - TransactionRequest, RPCTransactionRequest, TransactionReceipt, - RPCLog, - Log, - BlockTransactionResponse, + TransactionRequest, + TransactionResponse, } from './types/Transaction.types'; -export { computeAddress } from './utils/compute-address'; -export { computePublicKey } from './utils/compute-public-key'; -export { etherToGwei } from './utils/ether-to-gwei'; -export { etherToWei } from './utils/ether-to-wei'; -export { gweiToEther } from './utils/gwei-to-ether'; -export { hashMessage } from './utils/hash-message'; -export { isAddress } from './utils/is-address'; -export { splitSignature } from './utils/split-signature'; -export { toChecksumAddress } from './utils/to-checksum-address'; -export { toUtf8Bytes } from './utils/to-utf8-bytes'; -export { weiToEther } from './utils/wei-to-ether'; - export { + arrayify, Bytes, BytesLike, BytesLikeWithNumber, + concat, DataOptions, Hexable, - SignatureLike, - Signature, - isBytesLike, - isBytes, - arrayify, - concat, - stripZeros, - zeroPad, - isHexString, - hexlify, + hexConcat, hexDataLength, hexDataSlice, - hexConcat, - hexValue, + hexlify, hexStripZeros, + hexValue, hexZeroPad, + isBytes, + isBytesLike, + isHexString, + Signature, + SignatureLike, + stripZeros, + zeroPad, } from './utils/bytes'; +export { computeAddress } from './utils/compute-address'; +export { computePublicKey } from './utils/compute-public-key'; +export { etherToGwei } from './utils/ether-to-gwei'; +export { etherToWei } from './utils/ether-to-wei'; +export { gweiToEther } from './utils/gwei-to-ether'; +export { hashMessage } from './utils/hash-message'; +export { isAddress } from './utils/is-address'; export { keccak256 } from './utils/keccak256'; export { pack, solidityKeccak256 } from './utils/solidity-keccak256'; +export { splitSignature } from './utils/split-signature'; +export { toChecksumAddress } from './utils/to-checksum-address'; +export { toUtf8Bytes } from './utils/to-utf8-bytes'; +export { weiToEther } from './utils/wei-to-ether';