From 1c4f694f79119b2a126d0ae3c81c7aabcde50b15 Mon Sep 17 00:00:00 2001 From: Ivan Rukhavets Date: Thu, 11 Apr 2024 11:49:25 +0200 Subject: [PATCH] Introduce evm-codec and evm-abi packages with tools for abi encoding/decoding (#258) --- .../evm-codec/evm-utils_2024-03-25-12-14.json | 10 + common/config/rush/pnpm-lock.yaml | 1345 ++++++++++++++++- evm/evm-abi/README.md | 3 + evm/evm-abi/package.json | 29 + evm/evm-abi/src/abi-components/event.ts | 59 + evm/evm-abi/src/abi-components/function.ts | 73 + evm/evm-abi/src/contract-base.ts | 63 + evm/evm-abi/src/index.ts | 5 + evm/evm-abi/src/indexed.ts | 21 + evm/evm-abi/test/event.test.ts | 59 + evm/evm-abi/test/function.test.ts | 146 ++ evm/evm-abi/tsconfig.build.json | 4 + evm/evm-abi/tsconfig.json | 19 + evm/evm-codec/README.md | 3 + evm/evm-codec/package.json | 27 + evm/evm-codec/src/codec.ts | 21 + evm/evm-codec/src/codecs/array.ts | 83 + evm/evm-codec/src/codecs/primitives.ts | 225 +++ evm/evm-codec/src/codecs/struct.ts | 74 + evm/evm-codec/src/index.ts | 4 + evm/evm-codec/src/sink.ts | 213 +++ evm/evm-codec/src/src.ts | 158 ++ evm/evm-codec/test/array.test.ts | 141 ++ evm/evm-codec/test/sink.test.ts | 89 ++ evm/evm-codec/test/src.test.ts | 121 ++ evm/evm-codec/test/struct.bench.ts | 118 ++ evm/evm-codec/test/struct.test.ts | 175 +++ evm/evm-codec/tsconfig.build.json | 4 + evm/evm-codec/tsconfig.json | 19 + rush.json | 12 + 30 files changed, 3320 insertions(+), 3 deletions(-) create mode 100644 common/changes/@subsquid/evm-codec/evm-utils_2024-03-25-12-14.json create mode 100644 evm/evm-abi/README.md create mode 100644 evm/evm-abi/package.json create mode 100644 evm/evm-abi/src/abi-components/event.ts create mode 100644 evm/evm-abi/src/abi-components/function.ts create mode 100644 evm/evm-abi/src/contract-base.ts create mode 100644 evm/evm-abi/src/index.ts create mode 100644 evm/evm-abi/src/indexed.ts create mode 100644 evm/evm-abi/test/event.test.ts create mode 100644 evm/evm-abi/test/function.test.ts create mode 100644 evm/evm-abi/tsconfig.build.json create mode 100644 evm/evm-abi/tsconfig.json create mode 100644 evm/evm-codec/README.md create mode 100644 evm/evm-codec/package.json create mode 100644 evm/evm-codec/src/codec.ts create mode 100644 evm/evm-codec/src/codecs/array.ts create mode 100644 evm/evm-codec/src/codecs/primitives.ts create mode 100644 evm/evm-codec/src/codecs/struct.ts create mode 100644 evm/evm-codec/src/index.ts create mode 100644 evm/evm-codec/src/sink.ts create mode 100644 evm/evm-codec/src/src.ts create mode 100644 evm/evm-codec/test/array.test.ts create mode 100644 evm/evm-codec/test/sink.test.ts create mode 100644 evm/evm-codec/test/src.test.ts create mode 100644 evm/evm-codec/test/struct.bench.ts create mode 100644 evm/evm-codec/test/struct.test.ts create mode 100644 evm/evm-codec/tsconfig.build.json create mode 100644 evm/evm-codec/tsconfig.json diff --git a/common/changes/@subsquid/evm-codec/evm-utils_2024-03-25-12-14.json b/common/changes/@subsquid/evm-codec/evm-utils_2024-03-25-12-14.json new file mode 100644 index 000000000..e848db19c --- /dev/null +++ b/common/changes/@subsquid/evm-codec/evm-utils_2024-03-25-12-14.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@subsquid/evm-codec", + "comment": "Introduce new package for encoding/decoding EVM ABI data", + "type": "minor" + } + ], + "packageName": "@subsquid/evm-codec" +} \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index cc8365f06..bcab03995 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -59,6 +59,12 @@ dependencies: '@rush-temp/erc20-transfers': specifier: file:./projects/erc20-transfers.tgz version: file:projects/erc20-transfers.tgz(supports-color@8.1.1)(ts-node@10.9.2) + '@rush-temp/evm-abi': + specifier: file:./projects/evm-abi.tgz + version: file:projects/evm-abi.tgz(supports-color@8.1.1) + '@rush-temp/evm-codec': + specifier: file:./projects/evm-codec.tgz + version: file:projects/evm-codec.tgz(supports-color@8.1.1) '@rush-temp/evm-processor': specifier: file:./projects/evm-processor.tgz version: file:projects/evm-processor.tgz @@ -320,9 +326,6 @@ dependencies: dotenv: specifier: ^16.3.1 version: 16.3.1 - ethers: - specifier: ^6.9.0 - version: 6.9.1 expect: specifier: ^29.7.0 version: 29.7.0 @@ -392,6 +395,12 @@ dependencies: upath: specifier: ^2.0.1 version: 2.0.1 + viem: + specifier: ^2.8.14 + version: 2.9.0(typescript@5.3.3) + vitest: + specifier: ^1.4.0 + version: 1.4.0(@types/node@18.19.4)(supports-color@8.1.1) websocket: specifier: ^1.0.34 version: 1.0.34 @@ -1205,6 +1214,528 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: false + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: false + optional: true + + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@ethersproject/abi@5.7.0: + resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} + dependencies: + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + + /@ethersproject/abstract-provider@5.7.0: + resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/properties': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 + dev: false + + /@ethersproject/abstract-signer@5.7.0: + resolution: {integrity: sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==} + dependencies: + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + dev: false + + /@ethersproject/address@5.7.0: + resolution: {integrity: sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/rlp': 5.7.0 + dev: false + + /@ethersproject/base64@5.7.0: + resolution: {integrity: sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==} + dependencies: + '@ethersproject/bytes': 5.7.0 + dev: false + + /@ethersproject/basex@5.7.0: + resolution: {integrity: sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/properties': 5.7.0 + dev: false + + /@ethersproject/bignumber@5.7.0: + resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + bn.js: 5.2.1 + dev: false + + /@ethersproject/bytes@5.7.0: + resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/constants@5.7.0: + resolution: {integrity: sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + dev: false + + /@ethersproject/contracts@5.7.0: + resolution: {integrity: sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/transactions': 5.7.0 + dev: false + + /@ethersproject/hash@5.7.0: + resolution: {integrity: sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==} + dependencies: + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/base64': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + + /@ethersproject/hdnode@5.7.0: + resolution: {integrity: sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==} + dependencies: + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/basex': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/pbkdf2': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/wordlists': 5.7.0 + dev: false + + /@ethersproject/json-wallets@5.7.0: + resolution: {integrity: sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==} + dependencies: + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/hdnode': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/pbkdf2': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/random': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + dev: false + + /@ethersproject/keccak256@5.7.0: + resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} + dependencies: + '@ethersproject/bytes': 5.7.0 + js-sha3: 0.8.0 + dev: false + + /@ethersproject/logger@5.7.0: + resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} + dev: false + + /@ethersproject/networks@5.7.1: + resolution: {integrity: sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/pbkdf2@5.7.0: + resolution: {integrity: sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/sha2': 5.7.0 + dev: false + + /@ethersproject/properties@5.7.0: + resolution: {integrity: sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==} + dependencies: + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/providers@5.7.2: + resolution: {integrity: sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==} + dependencies: + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/base64': 5.7.0 + '@ethersproject/basex': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/properties': 5.7.0 + '@ethersproject/random': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/web': 5.7.1 + bech32: 1.1.4 + ws: 7.4.6 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@ethersproject/random@5.7.0: + resolution: {integrity: sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/rlp@5.7.0: + resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/sha2@5.7.0: + resolution: {integrity: sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + hash.js: 1.1.7 + dev: false + + /@ethersproject/signing-key@5.7.0: + resolution: {integrity: sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + bn.js: 5.2.1 + elliptic: 6.5.4 + hash.js: 1.1.7 + dev: false + + /@ethersproject/solidity@5.7.0: + resolution: {integrity: sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + + /@ethersproject/strings@5.7.0: + resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/transactions@5.7.0: + resolution: {integrity: sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==} + dependencies: + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + dev: false + + /@ethersproject/units@5.7.0: + resolution: {integrity: sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==} + dependencies: + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/logger': 5.7.0 + dev: false + + /@ethersproject/wallet@5.7.0: + resolution: {integrity: sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==} + dependencies: + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/hdnode': 5.7.0 + '@ethersproject/json-wallets': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/random': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/wordlists': 5.7.0 + dev: false + + /@ethersproject/web@5.7.1: + resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} + dependencies: + '@ethersproject/base64': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + + /@ethersproject/wordlists@5.7.0: + resolution: {integrity: sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==} + dependencies: + '@ethersproject/bytes': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/strings': 5.7.0 + dev: false + /@exodus/schemasafe@1.3.0: resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} dev: false @@ -1466,6 +1997,129 @@ packages: resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} dev: false + /@rollup/rollup-android-arm-eabi@4.13.0: + resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-android-arm64@4.13.0: + resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-darwin-arm64@4.13.0: + resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-darwin-x64@4.13.0: + resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.13.0: + resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.13.0: + resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-arm64-musl@4.13.0: + resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.13.0: + resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-x64-gnu@4.13.0: + resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-linux-x64-musl@4.13.0: + resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.13.0: + resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.13.0: + resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@rollup/rollup-win32-x64-msvc@4.13.0: + resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@scure/base@1.1.6: + resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==} + dev: false + + /@scure/bip32@1.3.2: + resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} + dependencies: + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.3 + '@scure/base': 1.1.6 + dev: false + + /@scure/bip39@1.2.1: + resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} + dependencies: + '@noble/hashes': 1.3.3 + '@scure/base': 1.1.6 + dev: false + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: false @@ -2030,6 +2684,10 @@ packages: resolution: {integrity: sha512-tqdiS4otQP4KmY0PR3u6KbZ5EWvhNdUoS/jc93UuK23C220lOZ/9TvjfxdPcKvqwwDVtmtSCrnr0p/2dirAxkA==} dev: false + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: false + /@types/express-serve-static-core@4.17.31: resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==} dependencies: @@ -2221,6 +2879,45 @@ packages: '@types/yargs-parser': 21.0.3 dev: false + /@vitest/expect@1.4.0: + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} + dependencies: + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 + chai: 4.4.1 + dev: false + + /@vitest/runner@1.4.0: + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} + dependencies: + '@vitest/utils': 1.4.0 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: false + + /@vitest/snapshot@1.4.0: + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} + dependencies: + magic-string: 0.30.8 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: false + + /@vitest/spy@1.4.0: + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} + dependencies: + tinyspy: 2.2.1 + dev: false + + /@vitest/utils@1.4.0: + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: false + /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2229,6 +2926,20 @@ packages: through: 2.3.8 dev: false + /abitype@1.0.0(typescript@5.3.3): + resolution: {integrity: sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + dependencies: + typescript: 5.3.3 + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2242,12 +2953,21 @@ packages: engines: {node: '>=0.4.0'} dev: false + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: false + /acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true dev: false + /aes-js@3.0.0: + resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} + dev: false + /aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} dev: false @@ -2483,6 +3203,10 @@ packages: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} dev: false + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: false + /async-retry@1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} dependencies: @@ -2516,6 +3240,10 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false + /bech32@1.1.4: + resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} + dev: false + /big.js@6.2.1: resolution: {integrity: sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==} dev: false @@ -2557,6 +3285,10 @@ packages: nanoassert: 2.0.0 dev: false + /bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: false + /bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} dev: false @@ -2629,6 +3361,10 @@ packages: fill-range: 7.0.1 dev: false + /brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + dev: false + /browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} dev: false @@ -2675,6 +3411,11 @@ packages: engines: {node: '>= 0.8'} dev: false + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: false + /cacheable-request@6.1.0: resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} engines: {node: '>=8'} @@ -2701,6 +3442,19 @@ packages: engines: {node: '>=10'} dev: false + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: false + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2718,6 +3472,12 @@ packages: supports-color: 7.2.0 dev: false + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2945,6 +3705,13 @@ packages: mimic-response: 1.0.1 dev: false + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: false + /deep-equal@2.2.3: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} @@ -3060,6 +3827,18 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false + /elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: false @@ -3128,6 +3907,37 @@ packages: ext: 1.7.0 dev: false + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + dev: false + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -3152,11 +3962,55 @@ packages: engines: {node: '>=10'} dev: false + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: false + /etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} dev: false + /ethers@5.7.2: + resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==} + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/abstract-provider': 5.7.0 + '@ethersproject/abstract-signer': 5.7.0 + '@ethersproject/address': 5.7.0 + '@ethersproject/base64': 5.7.0 + '@ethersproject/basex': 5.7.0 + '@ethersproject/bignumber': 5.7.0 + '@ethersproject/bytes': 5.7.0 + '@ethersproject/constants': 5.7.0 + '@ethersproject/contracts': 5.7.0 + '@ethersproject/hash': 5.7.0 + '@ethersproject/hdnode': 5.7.0 + '@ethersproject/json-wallets': 5.7.0 + '@ethersproject/keccak256': 5.7.0 + '@ethersproject/logger': 5.7.0 + '@ethersproject/networks': 5.7.1 + '@ethersproject/pbkdf2': 5.7.0 + '@ethersproject/properties': 5.7.0 + '@ethersproject/providers': 5.7.2 + '@ethersproject/random': 5.7.0 + '@ethersproject/rlp': 5.7.0 + '@ethersproject/sha2': 5.7.0 + '@ethersproject/signing-key': 5.7.0 + '@ethersproject/solidity': 5.7.0 + '@ethersproject/strings': 5.7.0 + '@ethersproject/transactions': 5.7.0 + '@ethersproject/units': 5.7.0 + '@ethersproject/wallet': 5.7.0 + '@ethersproject/web': 5.7.1 + '@ethersproject/wordlists': 5.7.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /ethers@6.9.1: resolution: {integrity: sha512-kuV8fGd4/8Gj7wkurbsuUsm1DCG6N5gKGYdw3fnWG/7QGknhy1xtHD7kbkCWQAcbAYmzLCLqCPedS3FYncFkKQ==} engines: {node: '>=14.0.0'} @@ -3177,6 +4031,21 @@ packages: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} dev: false + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: false + /expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3363,6 +4232,10 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: false + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: false + /get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} dependencies: @@ -3386,6 +4259,11 @@ packages: pump: 3.0.0 dev: false + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -3557,6 +4435,13 @@ packages: has-symbols: 1.0.3 dev: false + /hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + dev: false + /hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} @@ -3573,6 +4458,14 @@ packages: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: false + /hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + dev: false + /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: false @@ -3588,6 +4481,11 @@ packages: toidentifier: 1.0.1 dev: false + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: false + /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} dependencies: @@ -3760,6 +4658,11 @@ packages: call-bind: 1.0.5 dev: false + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -3817,6 +4720,14 @@ packages: ws: 7.5.9 dev: false + /isows@1.0.3(ws@8.13.0): + resolution: {integrity: sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==} + peerDependencies: + ws: '*' + dependencies: + ws: 8.13.0 + dev: false + /iterall@1.3.0: resolution: {integrity: sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==} dev: false @@ -3904,10 +4815,18 @@ packages: picomatch: 2.3.1 dev: false + /js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: false + /js-tokens@8.0.3: + resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + dev: false + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -3963,6 +4882,14 @@ packages: resolution: {integrity: sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==} dev: false + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.6.1 + pkg-types: 1.0.3 + dev: false + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -4003,6 +4930,12 @@ packages: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} dev: false + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: false + /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -4036,6 +4969,13 @@ packages: engines: {node: '>=12'} dev: false + /magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: false + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: false @@ -4049,6 +4989,10 @@ packages: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: false + /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -4080,11 +5024,24 @@ packages: hasBin: true dev: false + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: false + /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} dev: false + /minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + dev: false + + /minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + dev: false + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -4127,6 +5084,15 @@ packages: hasBin: true dev: false + /mlly@1.6.1: + resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.5.3 + dev: false + /mocha@10.2.0: resolution: {integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==} engines: {node: '>= 14.0.0'} @@ -4185,6 +5151,12 @@ packages: hasBin: true dev: false + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -4246,6 +5218,13 @@ packages: engines: {node: '>=8'} dev: false + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: false + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4295,6 +5274,13 @@ packages: wrappy: 1.0.2 dev: false + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: false + /p-cancelable@1.1.0: resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} engines: {node: '>=6'} @@ -4307,6 +5293,13 @@ packages: yocto-queue: 0.1.0 dev: false + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: false + /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -4366,6 +5359,11 @@ packages: engines: {node: '>=8'} dev: false + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: false + /path-scurry@1.10.1: resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} engines: {node: '>=16 || 14 >=14.17'} @@ -4378,6 +5376,14 @@ packages: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: false + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: false + /pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} requiresBuild: true @@ -4460,11 +5466,32 @@ packages: split2: 4.2.0 dev: false + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: false + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.6.1 + pathe: 1.1.2 + dev: false + + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 + dev: false + /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -4681,6 +5708,29 @@ packages: engines: {node: '>= 4'} dev: false + /rollup@4.13.0: + resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.13.0 + '@rollup/rollup-android-arm64': 4.13.0 + '@rollup/rollup-darwin-arm64': 4.13.0 + '@rollup/rollup-darwin-x64': 4.13.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.13.0 + '@rollup/rollup-linux-arm64-gnu': 4.13.0 + '@rollup/rollup-linux-arm64-musl': 4.13.0 + '@rollup/rollup-linux-riscv64-gnu': 4.13.0 + '@rollup/rollup-linux-x64-gnu': 4.13.0 + '@rollup/rollup-linux-x64-musl': 4.13.0 + '@rollup/rollup-win32-arm64-msvc': 4.13.0 + '@rollup/rollup-win32-ia32-msvc': 4.13.0 + '@rollup/rollup-win32-x64-msvc': 4.13.0 + fsevents: 2.3.3 + dev: false + /rpc-websockets@7.9.0: resolution: {integrity: sha512-DwKewQz1IUA5wfLvgM8wDpPRcr+nWSxuFxx5CbrI2z/MyyZ4nXLM86TvIA+cI1ZAdqC8JIBR1mZR55dzaLU+Hw==} dependencies: @@ -4701,6 +5751,10 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false + /scrypt-js@3.0.1: + resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} + dev: false + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4800,6 +5854,10 @@ packages: object-inspect: 1.13.1 dev: false + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: false + /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -4817,6 +5875,11 @@ packages: tslib: 2.6.2 dev: false + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: false + /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -4829,6 +5892,10 @@ packages: escape-string-regexp: 2.0.0 dev: false + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: false + /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} dev: false @@ -4838,6 +5905,10 @@ packages: engines: {node: '>= 0.8'} dev: false + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: false + /stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -4882,6 +5953,11 @@ packages: ansi-regex: 6.0.1 dev: false + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: false + /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -4892,6 +5968,12 @@ packages: engines: {node: '>=8'} dev: false + /strip-literal@2.0.0: + resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} + dependencies: + js-tokens: 8.0.3 + dev: false + /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} dev: false @@ -4952,6 +6034,20 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: false + /tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + dev: false + + /tinypool@0.8.2: + resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} + engines: {node: '>=14.0.0'} + dev: false + + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + dev: false + /to-readable-stream@1.0.0: resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} engines: {node: '>=6'} @@ -5020,6 +6116,11 @@ packages: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} dev: false + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: false + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -5152,6 +6253,10 @@ packages: hasBin: true dev: false + /ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + dev: false + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: false @@ -5226,6 +6331,142 @@ packages: engines: {node: '>= 0.8'} dev: false + /viem@2.9.0(typescript@5.3.3): + resolution: {integrity: sha512-7jNrY9GY4aLGU2qX4/TCXpA9qR4PDx5ctQyJpxoh8jDmlV0Rh2FLlnJsgJs9sAB8cKbNafkRTYQtuMA3OOn0JA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@scure/bip32': 1.3.2 + '@scure/bip39': 1.2.1 + abitype: 1.0.0(typescript@5.3.3) + isows: 1.0.3(ws@8.13.0) + typescript: 5.3.3 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + dev: false + + /vite-node@1.4.0(@types/node@18.19.4)(supports-color@8.1.1): + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@8.1.1) + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.2.6(@types/node@18.19.4) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: false + + /vite@5.2.6(@types/node@18.19.4): + resolution: {integrity: sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.19.4 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + dev: false + + /vitest@1.4.0(@types/node@18.19.4)(supports-color@8.1.1): + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 18.19.4 + '@vitest/expect': 1.4.0 + '@vitest/runner': 1.4.0 + '@vitest/snapshot': 1.4.0 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4(supports-color@8.1.1) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.8 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.0.0 + tinybench: 2.6.0 + tinypool: 0.8.2 + vite: 5.2.6(@types/node@18.19.4) + vite-node: 1.4.0(@types/node@18.19.4)(supports-color@8.1.1) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: false + /web-streams-polyfill@3.2.1: resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} engines: {node: '>= 8'} @@ -5297,6 +6538,15 @@ packages: isexe: 2.0.0 dev: false + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: false + /workerpool@6.2.1: resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} dev: false @@ -5323,6 +6573,19 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: false + /ws@7.4.6: + resolution: {integrity: sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws@7.5.9: resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} engines: {node: '>=8.3.0'} @@ -5336,6 +6599,19 @@ packages: optional: true dev: false + /ws@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws@8.16.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} engines: {node: '>=10.0.0'} @@ -5459,6 +6735,11 @@ packages: engines: {node: '>=10'} dev: false + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: false + file:projects/astar-erc20.tgz(supports-color@8.1.1)(ts-node@10.9.2): resolution: {integrity: sha512-6Y+SABUJyryoGFyTAT6Kcn88q+yIeg0pLymX51ZqxdQyeyWWhG1lRoCjJ1EmEAppuDxJIOYeXP9s8ISuhf8Jkg==, tarball: file:projects/astar-erc20.tgz} id: file:projects/astar-erc20.tgz @@ -5620,6 +6901,64 @@ packages: - utf-8-validate dev: false + file:projects/evm-abi.tgz(supports-color@8.1.1): + resolution: {integrity: sha512-iGwaVpQ1b2kAr7NplbftX+bTwhll7eiEyT8T3DM/ElBFzUTm599Ku1qQn5tf27v+Zx0AI/o2H7m/GYpZHdDJ+w==, tarball: file:projects/evm-abi.tgz} + id: file:projects/evm-abi.tgz + name: '@rush-temp/evm-abi' + version: 0.0.0 + dependencies: + '@types/node': 18.19.4 + ethers: 5.7.2 + typescript: 5.3.3 + viem: 2.9.0(typescript@5.3.3) + vitest: 1.4.0(@types/node@18.19.4)(supports-color@8.1.1) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@vitest/browser' + - '@vitest/ui' + - bufferutil + - happy-dom + - jsdom + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - utf-8-validate + - zod + dev: false + + file:projects/evm-codec.tgz(supports-color@8.1.1): + resolution: {integrity: sha512-hOGYA4NXJPJcAsfxMiFXV4OrXzn0kdF8fgzG0VuDkQyEWRP089SUzr6ORp8NN4hLPoSo2El1gBU4CqIAQQenZg==, tarball: file:projects/evm-codec.tgz} + id: file:projects/evm-codec.tgz + name: '@rush-temp/evm-codec' + version: 0.0.0 + dependencies: + '@types/node': 18.19.4 + ethers: 5.7.2 + typescript: 5.3.3 + viem: 2.9.0(typescript@5.3.3) + vitest: 1.4.0(@types/node@18.19.4)(supports-color@8.1.1) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@vitest/browser' + - '@vitest/ui' + - bufferutil + - happy-dom + - jsdom + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - utf-8-validate + - zod + dev: false + file:projects/evm-processor.tgz: resolution: {integrity: sha512-3ySUVUdEjJEF87dP9nIYufbwXJy6AWM4pVmndc+1S+qO40RUsyBZdanmPL2XVWt9KkHKsuzeJ8KL2xGU1XkENA==, tarball: file:projects/evm-processor.tgz} name: '@rush-temp/evm-processor' diff --git a/evm/evm-abi/README.md b/evm/evm-abi/README.md new file mode 100644 index 000000000..751461424 --- /dev/null +++ b/evm/evm-abi/README.md @@ -0,0 +1,3 @@ +# @subsquid/evm-abi + +This package provides a set of utilities for encoding and decoding Ethereum contract ABI data. diff --git a/evm/evm-abi/package.json b/evm/evm-abi/package.json new file mode 100644 index 000000000..e4814bd35 --- /dev/null +++ b/evm/evm-abi/package.json @@ -0,0 +1,29 @@ +{ + "name": "@subsquid/evm-abi", + "version": "0.0.0", + "description": "EVM ABI encoding and decoding helpers", + "license": "GPL-3.0-or-later", + "repository": "git@github.com:subsquid/squid.git", + "publishConfig": { + "access": "public" + }, + "main": "lib/index.js", + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "rm -rf lib && tsc -p tsconfig.build.json", + "test": "vitest run" + }, + "dependencies": { + "@subsquid/evm-codec": "^0.0.0" + }, + "devDependencies": { + "@types/node": "^18.18.14", + "ethers": "^5.7.2", + "typescript": "~5.3.2", + "viem": "^2.8.14", + "vitest": "^1.4.0" + } +} diff --git a/evm/evm-abi/src/abi-components/event.ts b/evm/evm-abi/src/abi-components/event.ts new file mode 100644 index 000000000..9cbef763e --- /dev/null +++ b/evm/evm-abi/src/abi-components/event.ts @@ -0,0 +1,59 @@ +import type { Pretty } from '../indexed' +import { bytes32, Src, type Codec, type StructTypes, type Struct } from '@subsquid/evm-codec' + +export interface EventRecord { + topics: string[] + data: string +} + +type EventArgs = { + [key: string]: Pretty & { indexed?: boolean }> +} + +export type IndexedCodecs = Pretty<{ + [K in keyof T]: T[K] extends { indexed: true; isDynamic: true } ? typeof bytes32 & { indexed: true } : T[K] +}> + +export class AbiEvent { + public readonly params: any + constructor(public readonly topic: string, args: T) { + const entries = Object.entries(args) + this.params = Object.fromEntries( + entries.map( + ([key, arg]) => + [ + key, + arg.indexed && arg.isDynamic + ? { + ...bytes32, + isDynamic: true, + indexed: true, + } + : arg, + ] as const, + ), + ) as IndexedCodecs + } + + is(rec: EventRecord): boolean { + return rec.topics[0] === this.topic + } + + decode(rec: EventRecord): StructTypes> { + const src = new Src(Buffer.from(rec.data.slice(2), 'hex')) + const result = {} as any + let topicCounter = 1 + for (let i in this.params) { + if (this.params[i].indexed) { + const topic = rec.topics[topicCounter++] + const topicSrc = new Src(Buffer.from(topic.slice(2), 'hex')) + result[i] = this.params[i].decode(topicSrc) + } else { + result[i] = this.params[i].decode(src) + } + } + return result + } +} + +export const event = (topic: string, args: T) => new AbiEvent(topic, args) diff --git a/evm/evm-abi/src/abi-components/function.ts b/evm/evm-abi/src/abi-components/function.ts new file mode 100644 index 000000000..459781961 --- /dev/null +++ b/evm/evm-abi/src/abi-components/function.ts @@ -0,0 +1,73 @@ +import assert from 'node:assert' +import { type Codec, type Struct, type StructTypes, Sink, Src } from '@subsquid/evm-codec' + +function slotsCount(codecs: readonly Codec[]) { + let count = 0 + for (const codec of codecs) { + count += codec.slotsCount ?? 1 + } + return count +} + +type FunctionReturn = T extends Codec ? U : T extends Struct ? StructTypes : undefined + +export class AbiFunction | Struct | undefined> { + readonly #selector: Buffer + private readonly slotsCount: number + + constructor(public selector: string, public readonly args: T, public readonly returnType?: R) { + assert(selector.startsWith('0x'), 'selector must start with 0x') + assert(selector.length === 10, 'selector must be 4 bytes long') + this.#selector = Buffer.from(selector.slice(2), 'hex') + this.args = args + this.slotsCount = slotsCount(Object.values(args)) + } + + is(calldata: string) { + return calldata.startsWith(this.selector) + } + + encode(args: StructTypes) { + const sink = new Sink(this.slotsCount) + for (let i in this.args) { + this.args[i].encode(sink, args[i]) + } + return `0x${Buffer.concat([this.#selector, sink.result()]).toString('hex')}` + } + + decode(calldata: string): StructTypes { + assert(this.is(calldata), `unexpected function signature: ${calldata.slice(0, 10)}`) + const src = new Src(Buffer.from(calldata.slice(10), 'hex')) + const result = {} as any + for (let i in this.args) { + result[i] = this.args[i].decode(src) + } + return result + } + + private isCodecs(value: any): value is Codec { + return 'decode' in value && 'encode' in value + } + + decodeResult(output: string): FunctionReturn { + if (!this.returnType) { + return undefined as any + } + const src = new Src(Buffer.from(output.slice(2), 'hex')) + if (this.isCodecs(this.returnType)) { + return this.returnType.decode(src) as any + } + const result = {} as any + for (let i in this.returnType) { + const codec = this.returnType[i] as Codec + result[i] = codec.decode(src) + } + return result + } +} + +export const fun = | Struct | undefined>( + signature: string, + args: T, + returnType?: R, +) => new AbiFunction(signature, args, returnType) diff --git a/evm/evm-abi/src/contract-base.ts b/evm/evm-abi/src/contract-base.ts new file mode 100644 index 000000000..bb1d12c75 --- /dev/null +++ b/evm/evm-abi/src/contract-base.ts @@ -0,0 +1,63 @@ +import type { AbiFunction } from './abi-components/function' +import type { Codec, Struct, StructTypes } from '@subsquid/evm-codec' + +export interface Chain { + client: { + call: (method: string, params?: unknown[]) => Promise + } +} + +export interface ChainContext { + _chain: Chain +} + +export interface BlockContext { + _chain: Chain + block: Block +} + +export interface Block { + height: number +} + +export class ContractBase { + private readonly _chain: Chain + private readonly blockHeight: number + readonly address: string + + constructor(ctx: BlockContext, address: string) + constructor(ctx: ChainContext, block: Block, address: string) + constructor(ctx: BlockContext, blockOrAddress: Block | string, address?: string) { + this._chain = ctx._chain + if (typeof blockOrAddress === 'string') { + this.blockHeight = ctx.block.height + this.address = blockOrAddress + } else { + if (address == null) { + throw new Error('missing contract address') + } + this.blockHeight = blockOrAddress.height + this.address = address + } + } + + /** + * Call a contract function using eth_call with the given calldata. + * Might be necessary to override for some chains. + */ + private async rpc_call(calldata: string) { + return this._chain.client.call('eth_call', [ + { to: this.address, data: calldata }, + '0x' + this.blockHeight.toString(16), + ]) + } + + async eth_call | Struct | undefined>( + func: AbiFunction, + args: StructTypes, + ): Promise { + const data = func.encode(args) + const result = await this.rpc_call(data) + return func.decodeResult(result) + } +} diff --git a/evm/evm-abi/src/index.ts b/evm/evm-abi/src/index.ts new file mode 100644 index 000000000..3c1456d70 --- /dev/null +++ b/evm/evm-abi/src/index.ts @@ -0,0 +1,5 @@ +export { ContractBase } from './contract-base' + +export { indexed } from './indexed' +export { fun, AbiFunction } from './abi-components/function' +export { event, AbiEvent, type EventRecord } from './abi-components/event' diff --git a/evm/evm-abi/src/indexed.ts b/evm/evm-abi/src/indexed.ts new file mode 100644 index 000000000..4a627036c --- /dev/null +++ b/evm/evm-abi/src/indexed.ts @@ -0,0 +1,21 @@ +import type { Codec } from '@subsquid/evm-codec' + +export type Pretty = { [K in keyof T]: T[K] } & unknown + +export function indexed>(codec: T): Pretty { + return new Proxy(codec, { + get(target: any, prop, receiver) { + if (prop === 'indexed') { + return true + } + const value = target[prop] + if (value instanceof Function) { + return function (...args: any[]) { + // @ts-ignore + return value.apply(this === receiver ? target : this, args) + } + } + return value + }, + }) +} diff --git a/evm/evm-abi/test/event.test.ts b/evm/evm-abi/test/event.test.ts new file mode 100644 index 000000000..8e3f2b495 --- /dev/null +++ b/evm/evm-abi/test/event.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' +import { encodeAbiParameters, encodeEventTopics, parseAbiItem } from 'viem' +import { bool, bytes, string, struct, uint256 } from '@subsquid/evm-codec' +import { event as _event, indexed } from '../src' + +describe('Event', () => { + it('decodes simple args', () => { + const topics = encodeEventTopics({ + abi: [parseAbiItem('event Test(uint256 indexed a, uint256 b)')], + eventName: 'Test', + args: { a: 123n }, + }) + const event = _event(topics[0], { + a: indexed(uint256), + b: uint256, + }) + const decoded = event.decode({ + topics, + data: encodeAbiParameters([{ type: 'uint256' }], [100n]), + }) + expect(decoded).toEqual({ a: 123n, b: 100n }) + }) + + it('decodes complex args', () => { + const topics = encodeEventTopics({ + abi: [parseAbiItem('event Test(string indexed a, string b, bytes c, (uint256, string) d, bool indexed e)')], + eventName: 'Test', + args: { a: 'xdxdxd', e: true }, + }) + const event = _event(topics[0], { + a: indexed(string), + b: string, + c: bytes, + d: struct({ _0: uint256, _1: string }), + e: indexed(bool), + }) + const decoded = event.decode({ + topics, + data: encodeAbiParameters( + [ + { type: 'string' }, + { type: 'bytes' }, + { + type: 'tuple', + components: [{ type: 'uint256' }, { type: 'string' }], + }, + ], + ['hello', '0x1234', [100n, 'world']], + ), + }) + expect(decoded).toEqual({ + a: Buffer.from(topics[1].slice(2), 'hex'), + b: 'hello', + c: Buffer.from([0x12, 0x34]), + d: { _0: 100n, _1: 'world' }, + e: true, + }) + }) +}) diff --git a/evm/evm-abi/test/function.test.ts b/evm/evm-abi/test/function.test.ts new file mode 100644 index 000000000..f0a1f47a8 --- /dev/null +++ b/evm/evm-abi/test/function.test.ts @@ -0,0 +1,146 @@ +import { describe, expect, it } from 'vitest' +import { encodeFunctionData, encodeFunctionResult, parseAbiItem } from 'viem' +import { array, bool, bytes4, fixedSizeArray, int32, Sink, string, struct, uint256 } from '@subsquid/evm-codec' +import { fun } from '../src' + +describe('Function', () => { + it('encodes/decodes simple args', () => { + const simpleFunction = fun('0x12345678', { + foo: uint256, + _1: int32, + _2: bool, + }) + const calldata = simpleFunction.encode({ + foo: 100n, + _1: -420, + _2: true, + }) + expect(calldata).toBe( + '0x123456780000000000000000000000000000000000000000000000000000000000000064fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5c0000000000000000000000000000000000000000000000000000000000000001', + ) + + const decoded = simpleFunction.decode(calldata) + expect(decoded).toStrictEqual({ + foo: 100n, + _1: -420, + _2: true, + }) + }) + + it('encodes/decodes dynamic args', () => { + const staticStruct = struct({ + foo: uint256, + bar: bytes4, + }) + const dynamicFunction = fun('0x423917ce', { + arg1: array(uint256), + arg2: fixedSizeArray(array(uint256), 10), + arg3: struct({ + foo: uint256, + bar: array(uint256), + str: staticStruct, + }), + arg4: staticStruct, + }) + const args = { + arg1: [100n, 2n], + arg2: [[], [1n], [], [], [100n, 2n, 3n], [], [], [1337n], [], []], + arg3: { + foo: 100n, + bar: [1n, 2n, 3n], + str: { + foo: 123n, + bar: Buffer.from([0x12, 0x34, 0x56, 0x78]), + }, + }, + arg4: { + foo: 100n, + bar: Buffer.from([0x12, 0x34, 0x56, 0x78]), + }, + } + const viemArgs = [ + [100n, 2n], + [[], [1n], [], [], [100n, 2n, 3n], [], [], [1337n], [], []], + { + foo: 100n, + bar: [1n, 2n, 3n], + str: { + foo: 123n, + bar: '0x12345678', + }, + }, + { + foo: 100n, + bar: '0x12345678', + }, + ] as const + + const calldata = dynamicFunction.encode(args) + const expected = encodeFunctionData({ + abi: [ + { + name: 'foo', + type: 'function', + inputs: [ + { name: 'arg1', type: 'uint256[]' }, + { name: 'arg2', type: 'uint256[][10]' }, + { + name: 'arg3', + type: 'tuple', + components: [ + { name: 'foo', type: 'uint256' }, + { name: 'bar', type: 'uint256[]' }, + { + name: 'str', + type: 'tuple', + components: [ + { name: 'foo', type: 'uint256' }, + { name: 'bar', type: 'bytes4' }, + ], + }, + ], + }, + { + name: 'arg4', + type: 'tuple', + components: [ + { name: 'foo', type: 'uint256' }, + { name: 'bar', type: 'bytes4' }, + ], + }, + ], + }, + ], + functionName: 'foo', + args: viemArgs, + }) + expect(calldata).toBe(expected) + + expect(dynamicFunction.decode(calldata)).toStrictEqual(args) + }) + + it('return simple type', () => { + const simpleFunction = fun( + '0x12345678', + { + foo: uint256, + }, + int32, + ) + const sink = new Sink(1) + int32.encode(sink, -420) + sink.toString() + const output = simpleFunction.decodeResult(sink.toString()) + expect(output).toBe(-420) + }) + + it('return tuple', () => { + const data = encodeFunctionResult({ + abi: [parseAbiItem('function foo() external returns (uint256, string memory b)')], + functionName: 'foo', + result: [100n, 'hello'], + }) + const _fun = fun('0x12345678', {}, { _0: uint256, b: string }) + expect(_fun.decodeResult(data)).toStrictEqual({ _0: 100n, b: 'hello' }) + }) +}) diff --git a/evm/evm-abi/tsconfig.build.json b/evm/evm-abi/tsconfig.build.json new file mode 100644 index 000000000..b90fc83e0 --- /dev/null +++ b/evm/evm-abi/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} diff --git a/evm/evm-abi/tsconfig.json b/evm/evm-abi/tsconfig.json new file mode 100644 index 000000000..845192ba7 --- /dev/null +++ b/evm/evm-abi/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "lib", + "rootDir": "src", + "allowJs": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true + }, + "include": ["src", "test"], + "exclude": ["node_modules"] +} diff --git a/evm/evm-codec/README.md b/evm/evm-codec/README.md new file mode 100644 index 000000000..a6c58e6ec --- /dev/null +++ b/evm/evm-codec/README.md @@ -0,0 +1,3 @@ +# @subsquid/evm-codec + +Encoder and decoder for EVM arguments diff --git a/evm/evm-codec/package.json b/evm/evm-codec/package.json new file mode 100644 index 000000000..0817cd91f --- /dev/null +++ b/evm/evm-codec/package.json @@ -0,0 +1,27 @@ +{ + "name": "@subsquid/evm-codec", + "version": "0.0.0", + "description": "EVM encoder/decoder tools", + "license": "GPL-3.0-or-later", + "repository": "git@github.com:subsquid/squid.git", + "publishConfig": { + "access": "public" + }, + "main": "lib/index.js", + "files": [ + "lib", + "src" + ], + "scripts": { + "build": "rm -rf lib && tsc -p tsconfig.build.json", + "test": "vitest run" + }, + "dependencies": {}, + "devDependencies": { + "@types/node": "^18.18.14", + "ethers": "^5.7.2", + "typescript": "~5.3.2", + "viem": "^2.8.14", + "vitest": "^1.4.0" + } +} diff --git a/evm/evm-codec/src/codec.ts b/evm/evm-codec/src/codec.ts new file mode 100644 index 000000000..f1a4fcec3 --- /dev/null +++ b/evm/evm-codec/src/codec.ts @@ -0,0 +1,21 @@ +import type { Sink } from './sink' +import type { Src } from './src' + +export const WORD_SIZE = 32 + +export interface Codec { + encode(sink: Sink, val: T): void + decode(src: Src): T + isDynamic: boolean + slotsCount?: number +} + +export type Struct = { + [key: string]: Codec +} + +type Pretty = { [K in keyof T]: T[K] } & unknown + +export type StructTypes = Pretty<{ + [K in keyof T]: T[K] extends Codec ? U : never +}> diff --git a/evm/evm-codec/src/codecs/array.ts b/evm/evm-codec/src/codecs/array.ts new file mode 100644 index 000000000..3d469425e --- /dev/null +++ b/evm/evm-codec/src/codecs/array.ts @@ -0,0 +1,83 @@ +import { Codec, WORD_SIZE } from '../codec' +import { Sink } from '../sink' +import { Src } from '../src' + +export class ArrayCodec implements Codec { + public readonly isDynamic = true + + constructor(public readonly item: Codec) {} + + encode(sink: Sink, val: T[]) { + sink.newDynamicDataArea(val.length) + for (let i = 0; i < val.length; i++) { + this.item.encode(sink, val[i]) + } + sink.increaseCurrentDataAreaSize(WORD_SIZE) + sink.endCurrentDataArea() + } + + decode(src: Src): T[] { + const offset = src.u32() + + src.safeJump(offset) + const len = src.u32() + + const tmpSrc = src.slice(offset + WORD_SIZE) + const val = new Array(len) + for (let i = 0; i < val.length; i++) { + val[i] = this.item.decode(tmpSrc) + } + src.jumpBack() + return val + } +} + +export class FixedSizeArrayCodec implements Codec { + public isDynamic: boolean + public slotsCount: number + constructor(public readonly item: Codec, public readonly size: number) { + this.isDynamic = item.isDynamic && size > 0 + this.slotsCount = this.isDynamic ? 1 : size + } + + encode(sink: Sink, val: T[]) { + if (val.length !== this.size) { + throw new Error(`invalid array length: ${val.length}. Expected: ${this.size}`) + } + if (this.isDynamic) { + return this.encodeDynamic(sink, val) + } + for (let i = 0; i < this.size; i++) { + this.item.encode(sink, val[i]) + } + } + + private encodeDynamic(sink: Sink, val: T[]) { + sink.newStaticDataArea(this.size) + for (let i = 0; i < val.length; i++) { + this.item.encode(sink, val[i]) + } + sink.endCurrentDataArea() + } + + decode(src: Src): T[] { + if (this.isDynamic) { + return this.decodeDynamic(src) + } + let val = new Array(this.size) + for (let i = 0; i < val.length; i++) { + val[i] = this.item.decode(src) + } + return val + } + + private decodeDynamic(src: Src): T[] { + const offset = src.u32() + const tmpSrc = src.slice(offset) + let val = new Array(this.size) + for (let i = 0; i < val.length; i++) { + val[i] = this.item.decode(tmpSrc) + } + return val + } +} diff --git a/evm/evm-codec/src/codecs/primitives.ts b/evm/evm-codec/src/codecs/primitives.ts new file mode 100644 index 000000000..03ba68e0a --- /dev/null +++ b/evm/evm-codec/src/codecs/primitives.ts @@ -0,0 +1,225 @@ +import { Codec } from '../codec' +import { Sink } from '../sink' +import { Src } from '../src' +import { ArrayCodec, FixedSizeArrayCodec } from './array' +import { StructCodec } from './struct' + +export const bool: Codec = { + encode: function (sink: Sink, val: boolean) { + sink.bool(val) + }, + decode(src: Src): boolean { + return src.bool() + }, + isDynamic: false, +} + +export const uint8: Codec = { + encode(sink: Sink, val: number) { + sink.u8(val) + }, + decode(src: Src): number { + return src.u8() + }, + isDynamic: false, +} + +export const int8: Codec = { + encode(sink: Sink, val: number) { + sink.i8(val) + }, + decode(src: Src): number { + return src.i8() + }, + isDynamic: false, +} + +export const uint16: Codec = { + encode(sink: Sink, val: number) { + sink.u16(val) + }, + decode(src: Src): number { + return src.u16() + }, + isDynamic: false, +} + +export const int16: Codec = { + encode(sink: Sink, val: number) { + sink.i16(val) + }, + decode(src: Src): number { + return src.i16() + }, + isDynamic: false, +} + +export const uint32: Codec = { + encode(sink: Sink, val: number) { + sink.u32(val) + }, + decode(src: Src): number { + return src.u32() + }, + isDynamic: false, +} + +export const int32: Codec = { + encode(sink: Sink, val: number) { + sink.i32(val) + }, + decode(src: Src): number { + return src.i32() + }, + isDynamic: false, +} + +export const uint64: Codec = { + encode(sink: Sink, val: bigint) { + sink.u64(val) + }, + decode(src: Src): bigint { + return src.u64() + }, + isDynamic: false, +} + +export const int64: Codec = { + encode(sink: Sink, val: bigint) { + sink.i64(val) + }, + decode(src: Src): bigint { + return src.i64() + }, + isDynamic: false, +} + +export const uint128: Codec = { + encode(sink: Sink, val: bigint) { + sink.u128(val) + }, + decode(src: Src): bigint { + return src.u128() + }, + isDynamic: false, +} + +export const int128: Codec = { + encode(sink: Sink, val: bigint) { + sink.i128(val) + }, + decode(src: Src): bigint { + return src.i128() + }, + isDynamic: false, +} + +export const uint256: Codec = { + encode(sink: Sink, val: bigint) { + sink.u256(val) + }, + decode(src: Src): bigint { + return src.u256() + }, + isDynamic: false, +} + +export const int256: Codec = { + encode(sink: Sink, val: bigint) { + sink.i256(val) + }, + decode(src: Src): bigint { + return src.i256() + }, + isDynamic: false, +} + +export const string = { + encode(sink: Sink, val: string) { + sink.newStaticDataArea() + sink.string(val) + sink.endCurrentDataArea() + }, + decode(src: Src): string { + return src.string() + }, + isDynamic: true, +} + +export const bytes = { + encode(sink: Sink, val: Uint8Array) { + sink.newStaticDataArea() + sink.bytes(val) + sink.endCurrentDataArea() + }, + decode(src: Src): Uint8Array { + return src.bytes() + }, + isDynamic: true, +} + +const bytesN = (size: number): Codec => ({ + encode(sink: Sink, val: Uint8Array) { + sink.staticBytes(size, val) + }, + decode(src: Src): Uint8Array { + return src.staticBytes(size) + }, + isDynamic: false, +}) + +export const bytes0 = bytesN(0) +export const bytes1 = bytesN(1) +export const bytes2 = bytesN(2) +export const bytes3 = bytesN(3) +export const bytes4 = bytesN(4) +export const bytes5 = bytesN(5) +export const bytes6 = bytesN(6) +export const bytes7 = bytesN(7) +export const bytes8 = bytesN(8) +export const bytes9 = bytesN(9) +export const bytes10 = bytesN(10) +export const bytes11 = bytesN(11) +export const bytes12 = bytesN(12) +export const bytes13 = bytesN(13) +export const bytes14 = bytesN(14) +export const bytes15 = bytesN(15) +export const bytes16 = bytesN(16) +export const bytes17 = bytesN(17) +export const bytes18 = bytesN(18) +export const bytes19 = bytesN(19) +export const bytes20 = bytesN(20) +export const bytes21 = bytesN(21) +export const bytes22 = bytesN(22) +export const bytes23 = bytesN(23) +export const bytes24 = bytesN(24) +export const bytes25 = bytesN(25) +export const bytes26 = bytesN(26) +export const bytes27 = bytesN(27) +export const bytes28 = bytesN(28) +export const bytes29 = bytesN(29) +export const bytes30 = bytesN(30) +export const bytes31 = bytesN(31) +export const bytes32 = bytesN(32) + +export const address: Codec = { + encode(sink: Sink, val: string) { + sink.address(val) + }, + decode(src: Src): string { + return src.address() + }, + isDynamic: false, +} + +export const fixedSizeArray = (item: Codec, size: number): Codec => new FixedSizeArrayCodec(item, size) + +export const array = (item: Codec): Codec => new ArrayCodec(item) + +type Struct = { + [key: string]: Codec +} + +export const struct = (components: T) => new StructCodec(components) + +export const tuple = struct diff --git a/evm/evm-codec/src/codecs/struct.ts b/evm/evm-codec/src/codecs/struct.ts new file mode 100644 index 000000000..2aadec44b --- /dev/null +++ b/evm/evm-codec/src/codecs/struct.ts @@ -0,0 +1,74 @@ +import { Codec, Struct, StructTypes } from '../codec' +import { Sink } from '../sink' +import { Src } from '../src' + +function slotsCount(codecs: readonly Codec[]) { + let count = 0 + for (const codec of codecs) { + count += codec.slotsCount ?? 1 + } + return count +} + +export class StructCodec implements Codec> { + public readonly isDynamic: boolean + public readonly slotsCount: number + private readonly childrenSlotsCount: number + private readonly components: T + + constructor(components: T) { + this.components = components + const codecs = Object.values(components) + this.isDynamic = codecs.some((codec) => codec.isDynamic) + this.childrenSlotsCount = slotsCount(codecs) + if (this.isDynamic) { + this.slotsCount = 1 + } else { + this.slotsCount = this.childrenSlotsCount + } + } + + public encode(sink: Sink, val: StructTypes): void { + if (this.isDynamic) { + this.encodeDynamic(sink, val) + return + } + for (let i in this.components) { + let prop = this.components[i] + prop.encode(sink, val[i]) + } + } + + private encodeDynamic(sink: Sink, val: StructTypes): void { + sink.newStaticDataArea(this.childrenSlotsCount) + for (let i in this.components) { + let prop = this.components[i] + prop.encode(sink, val[i]) + } + sink.endCurrentDataArea() + } + + public decode(src: Src): StructTypes { + if (this.isDynamic) { + return this.decodeDynamic(src) + } + let result: any = {} + for (let i in this.components) { + let prop = this.components[i] + result[i] = prop.decode(src) + } + return result + } + + private decodeDynamic(src: Src): StructTypes { + let result: any = {} + + const offset = src.u32() + const tmpSrc = src.slice(offset) + for (let i in this.components) { + let prop = this.components[i] + result[i] = prop.decode(tmpSrc) + } + return result + } +} diff --git a/evm/evm-codec/src/index.ts b/evm/evm-codec/src/index.ts new file mode 100644 index 000000000..1cb507ff9 --- /dev/null +++ b/evm/evm-codec/src/index.ts @@ -0,0 +1,4 @@ +export { Src } from './src' +export { Sink } from './sink' +export type { Codec, Struct, StructTypes } from './codec' +export * from './codecs/primitives' diff --git a/evm/evm-codec/src/sink.ts b/evm/evm-codec/src/sink.ts new file mode 100644 index 000000000..ea95eb0f4 --- /dev/null +++ b/evm/evm-codec/src/sink.ts @@ -0,0 +1,213 @@ +import assert from 'node:assert' +import { WORD_SIZE } from './codec' + +export class Sink { + private pos = 0 + private buf: Buffer + private view: DataView + private stack: { start: number; jumpBackPtr: number; size: number }[] = [] + constructor(fields: number, capacity: number = 1280) { + this.stack.push({ + start: 0, + jumpBackPtr: 0, + size: fields * WORD_SIZE, + }) + this.buf = Buffer.alloc(capacity) + this.view = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength) + } + + result(): Buffer { + assert(this.stack.length === 1, 'Cannot get result during dynamic encoding') + return this.buf.subarray(0, this.size()) + } + + toString() { + return '0x' + this.result().toString('hex') + } + + reserve(additional: number): void { + if (this.buf.length - this.pos < additional) { + this._allocate(this.pos + additional) + } + } + + size() { + return this.stack[this.stack.length - 1].size + } + + private _allocate(cap: number): void { + cap = Math.max(cap, this.buf.length * 2) + let buf = Buffer.alloc(cap) + buf.set(this.buf) + this.buf = buf + this.view = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength) + } + + u8(val: number) { + this.reserve(WORD_SIZE) + this.pos += WORD_SIZE - 1 + this.view.setUint8(this.pos, val) + this.pos += 1 + } + + i8(val: number) { + this.i256(BigInt(val)) + } + + u16(val: number) { + this.reserve(WORD_SIZE) + this.pos += WORD_SIZE - 2 + this.view.setUint16(this.pos, val, false) + this.pos += 2 + } + + i16(val: number) { + this.i256(BigInt(val)) + } + + u32(val: number) { + this.reserve(WORD_SIZE) + this.pos += WORD_SIZE - 4 + this.view.setUint32(this.pos, val, false) + this.pos += 4 + } + + i32(val: number) { + this.i256(BigInt(val)) + } + + u64(val: bigint) { + this.reserve(WORD_SIZE) + this.pos += WORD_SIZE - 8 + this.view.setBigUint64(this.pos, val, false) + this.pos += 8 + } + + i64(val: bigint) { + this.i256(val) + } + + #u64(val: bigint) { + this.view.setBigUint64(this.pos, val, false) + this.pos += 8 + } + + u128(val: bigint) { + this.reserve(WORD_SIZE) + this.pos += WORD_SIZE - 16 + this.#u64(val & 0xffffffffffffffffn) + this.#u64(val >> 64n) + } + + i128(val: bigint) { + this.i256(BigInt(val)) + } + + #u128(val: bigint) { + this.reserve(WORD_SIZE) + this.#u64(val >> 64n) + this.#u64(val & 0xffffffffffffffffn) + } + + u256(val: bigint) { + this.reserve(WORD_SIZE) + this.#u128(val >> 128n) + this.#u128(val & (2n ** 128n - 1n)) + } + + i256(val: bigint) { + let base = 2n ** 256n + val = (val + base) % base + this.u256(val) + } + + bytes(val: Uint8Array) { + const size = Buffer.byteLength(val) + this.u32(size) + const wordsCount = Math.ceil(size / WORD_SIZE) + const reservedSize = WORD_SIZE * wordsCount + this.reserve(reservedSize) + this.buf.set(val, this.pos) + this.pos += reservedSize + this.increaseCurrentDataAreaSize(reservedSize + WORD_SIZE) + } + + staticBytes(len: number, val: Uint8Array) { + if (len > 32) { + throw new Error(`bytes${len} is not a valid type`) + } + const size = Buffer.byteLength(val) + if (size > len) { + throw new Error(`invalid data size for bytes${len}`) + } + this.reserve(WORD_SIZE) + this.buf.set(val, this.pos) + this.pos += WORD_SIZE + } + + address(val: string) { + this.u256(BigInt(val)) + } + + string(val: string) { + const size = Buffer.byteLength(val) + this.u32(size) + const wordsCount = Math.ceil(size / WORD_SIZE) + const reservedSize = WORD_SIZE * wordsCount + this.reserve(reservedSize) + this.buf.write(val, this.pos) + this.pos += reservedSize + this.increaseCurrentDataAreaSize(reservedSize + WORD_SIZE) + } + + bool(val: boolean) { + this.u8(val ? 1 : 0) + } + + /** + * @example + * @link [Solidity docs](https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types) + */ + newStaticDataArea(slotsCount = 0) { + const offset = this.size() + this.u32(offset) + const dataAreaStart = this.currentDataAreaStart() + this.pushDataArea(dataAreaStart + offset, slotsCount) + this.pos = dataAreaStart + offset + } + + // Adds elements count before the data area in an additional slot + newDynamicDataArea(slotsCount: number) { + const offset = this.size() + this.u32(offset) + const dataAreaStart = this.currentDataAreaStart() + this.pushDataArea(dataAreaStart + offset + WORD_SIZE, slotsCount) + this.pos = dataAreaStart + offset + this.u32(slotsCount) + } + + private currentDataAreaStart() { + return this.stack[this.stack.length - 1].start + } + + public increaseCurrentDataAreaSize(amount: number) { + this.stack[this.stack.length - 1].size += amount + } + + private pushDataArea(dataAreaStart: number, slotsCount: number) { + const size = slotsCount * WORD_SIZE + this.reserve(dataAreaStart + size) + this.stack.push({ + start: dataAreaStart, + jumpBackPtr: this.pos, + size, + }) + } + + public endCurrentDataArea() { + assert(this.stack.length > 1, 'No dynamic encoding started') + const { jumpBackPtr, size } = this.stack.pop()! + this.increaseCurrentDataAreaSize(size) + this.pos = jumpBackPtr + } +} diff --git a/evm/evm-codec/src/src.ts b/evm/evm-codec/src/src.ts new file mode 100644 index 000000000..de66ecb29 --- /dev/null +++ b/evm/evm-codec/src/src.ts @@ -0,0 +1,158 @@ +import { WORD_SIZE } from './codec' + +export class Src { + private view: DataView + private pos = 0 + private oldPos = 0 + constructor(private buf: Uint8Array) { + this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength) + } + + slice(start: number, end?: number): Src { + return new Src(this.buf.subarray(start, end)) + } + + u8(): number { + this.pos += WORD_SIZE - 1 + let val = this.view.getUint8(this.pos) + this.pos += 1 + return val + } + + i8(): number { + return Number(this.i256()) + } + + u16(): number { + this.pos += WORD_SIZE - 2 + let val = this.view.getUint16(this.pos, false) + this.pos += 2 + return val + } + + i16(): number { + return Number(this.i256()) + } + + u32(): number { + this.pos += WORD_SIZE - 4 + let val = this.view.getUint32(this.pos, false) + this.pos += 4 + return val + } + + i32(): number { + return Number(this.i256()) + } + + u64(): bigint { + this.pos += WORD_SIZE - 8 + return this.#u64() + } + + #u64(): bigint { + let val = this.view.getBigUint64(this.pos, false) + this.pos += 8 + return val + } + + i64(): bigint { + this.pos += WORD_SIZE - 8 + return this.#i64() + } + + #i64(): bigint { + let val = this.view.getBigInt64(this.pos, false) + this.pos += 8 + return val + } + + u128(): bigint { + this.pos += WORD_SIZE - 16 + return this.#u128() + } + + #u128(): bigint { + let hi = this.#u64() + let lo = this.#u64() + return lo + (hi << 64n) + } + + i128(): bigint { + this.pos += WORD_SIZE - 16 + return this.#i128() + } + + #i128(): bigint { + let hi = this.#i64() + let lo = this.#u64() + return lo + (hi << 64n) + } + + u256(): bigint { + let hi = this.#u128() + let lo = this.#u128() + return lo + (hi << 128n) + } + + i256(): bigint { + let hi = this.#i128() + let lo = this.#u128() + return lo + (hi << 128n) + } + + address(): string { + return '0x' + this.u256().toString(16).padStart(40, '0') + } + + bytes(): Uint8Array { + const ptr = this.u32() + this.safeJump(ptr) + const len = this.u32() + this.assertLength(len) + const val = this.buf.subarray(this.pos, this.pos + len) + this.jumpBack() + return val + } + + staticBytes(len: number): Uint8Array { + if (len > 32) { + throw new Error(`bytes${len} is not a valid type`) + } + const val = this.buf.subarray(this.pos, this.pos + len) + this.pos += WORD_SIZE + return val + } + + string(): string { + const ptr = this.u32() + this.safeJump(ptr) + const len = this.u32() + this.assertLength(len) + const val = Buffer.from(this.buf.buffer, this.buf.byteOffset + this.pos, len).toString('utf-8') + this.jumpBack() + return val + } + + bool(): boolean { + return !!this.u8() + } + + private assertLength(len: number): void { + if (this.buf.length - this.pos < len) { + throw new RangeError('Unexpected end of input') + } + } + + public safeJump(pos: number): void { + if (pos < 0 || pos >= this.buf.length) { + throw new RangeError(`Unexpected pointer location: 0x${pos.toString(16)}`) + } + this.oldPos = this.pos + this.pos = pos + } + + public jumpBack(): void { + this.pos = this.oldPos + } +} diff --git a/evm/evm-codec/test/array.test.ts b/evm/evm-codec/test/array.test.ts new file mode 100644 index 000000000..05f38c8c4 --- /dev/null +++ b/evm/evm-codec/test/array.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from 'vitest' +import { address, array, bytes, fixedSizeArray, int8, Sink, Src, string, uint256 } from '../src' +import { AbiParameter, encodeAbiParameters } from 'viem' + +function compareTypes(sink: Sink, types: AbiParameter[], values: any[]) { + expect(sink.toString()).toEqual(encodeAbiParameters(types, values)) +} + +describe('fixed size array', () => { + it('static types encoding', () => { + const arr = fixedSizeArray(int8, 5) + const sink = new Sink(5) + arr.encode(sink, [1, 2, -3, 4, 5]) + compareTypes(sink, [{ type: 'int8[5]' }], [[1, 2, -3, 4, 5]]) + }) + + it('static types decoding', () => { + const arr = fixedSizeArray(int8, 5) + const sink = new Sink(5) + arr.encode(sink, [1, 2, -3, -4, 5]) + expect(arr.decode(new Src(sink.result()))).toStrictEqual([1, 2, -3, -4, 5]) + }) + + it('dynamic types encoding', () => { + const arr = fixedSizeArray(string, 3) + const sink = new Sink(1) + const data = [ + 'aaa', + 'a relatively long string to test what happens when the string is long, longer than 32 bytes or even better, longer than 64 bytes!!!', + 'dasdas', + ] + arr.encode(sink, data) + compareTypes(sink, [{ type: 'string[3]' }], [data]) + expect(arr.decode(new Src(sink.result()))).toStrictEqual(data) + }) + + it('deep nested arrays', () => { + const arr = fixedSizeArray(fixedSizeArray(string, 3), 2) + const sink = new Sink(1) + const data = [ + 'aaa', + 'a relatively long string to test what happens when the string is long, longer than 32 bytes or even better, longer than 64 bytes!!!', + 'dasdas', + ] + arr.encode(sink, [data, data.reverse()]) + compareTypes(sink, [{ type: 'string[3][2]' }], [[data.reverse(), data.reverse()]]) + }) +}) + +describe('dynamic size array', () => { + it('static types encoding', () => { + const arr = array(int8) + const sink = new Sink(1) + arr.encode(sink, [1, 2, -3, 4, 5]) + compareTypes(sink, [{ type: 'int8[]' }], [[1, 2, -3, 4, 5]]) + }) + + it('static types decoding', () => { + const arr = array(int8) + const sink = new Sink(1) + arr.encode(sink, [1, 2, -3, -4, 5]) + expect(arr.decode(new Src(sink.result()))).toStrictEqual([1, 2, -3, -4, 5]) + }) + + it('array of arrays', () => { + const arr = array(array(int8)) + const sink = new Sink(1) + const data = [ + [1, 2, -3, -4, 5], + [1, 2, -3, -4, 5], + ] + arr.encode(sink, data) + compareTypes(sink, [{ type: 'int8[][]' }], [data]) + expect(arr.decode(new Src(sink.result()))).toStrictEqual(data) + }) + + it('dynamic types encoding', () => { + const arr = array(string) + const sink = new Sink(1) + const data = [ + 'aaa', + 'a relatively long string to test what happens when the string is long, longer than 32 bytes or even better, longer than 64 bytes!!!', + 'dasdas', + ] + arr.encode(sink, data) + compareTypes(sink, [{ type: 'string[]' }], [data]) + expect(arr.decode(new Src(sink.result()))).toStrictEqual(data) + }) + + it('hardcore dynamic types', () => { + const sink = new Sink(5) + const arr1 = array(array(fixedSizeArray(string, 3))) + const arr2 = array(array(uint256)) + const arr3 = array(fixedSizeArray(bytes, 2)) + const data1 = [ + [ + ['aaa', 'bbb', 'ccc'], + ['ddd', 'eee', 'fff'], + ], + [['ggg', 'hhh', 'iii']], + ] + const data2 = [[1n, 2n, 3n], [], [4n]] + const data3 = [ + [Buffer.from('1234', 'hex'), Buffer.from('5678', 'hex')], + [Buffer.from('dead', 'hex'), Buffer.from('beef', 'hex')], + ] + arr1.encode(sink, data1) + address.encode(sink, '0x1234567890123456789012345678901234567890') + arr3.encode(sink, data3) + arr2.encode(sink, data2) + uint256.encode(sink, 123n) + compareTypes( + sink, + [ + { type: 'string[3][][]' }, + { type: 'address' }, + { type: 'bytes[2][]' }, + { type: 'uint256[][]' }, + { type: 'uint256' }, + ], + [ + data1, + '0x1234567890123456789012345678901234567890', + [ + ['0x1234', '0x5678'], + ['0xdead', '0xbeef'], + ], + data2, + 123n, + ], + ) + + const src = new Src(sink.result()) + + expect(arr1.decode(src)).toStrictEqual(data1) + expect(address.decode(src)).toBe('0x1234567890123456789012345678901234567890') + expect(arr3.decode(src)).toStrictEqual(data3) + expect(arr2.decode(src)).toStrictEqual(data2) + expect(uint256.decode(src)).toBe(123n) + }) +}) diff --git a/evm/evm-codec/test/sink.test.ts b/evm/evm-codec/test/sink.test.ts new file mode 100644 index 000000000..018ce9f3e --- /dev/null +++ b/evm/evm-codec/test/sink.test.ts @@ -0,0 +1,89 @@ +import { describe, expect, it } from 'vitest' +import { AbiParameter, encodeAbiParameters } from 'viem' +import { Sink } from '../src' + +describe('sink', () => { + function compareTypes(sink: Sink, types: AbiParameter[], values: any[]) { + expect(sink.toString()).toEqual(encodeAbiParameters(types, values)) + } + + it('negative numbers', () => { + const sink = new Sink(6) + sink.i8(-1) + sink.i16(-123) + sink.i32(-123456) + sink.i64(-1234567890n) + sink.i128(-12345678901234567890n) + sink.i256(-1234567890123456789012345678901234567890n) + compareTypes( + sink, + [ + { type: 'int8' }, + { type: 'int16' }, + { type: 'int32' }, + { type: 'int64' }, + { type: 'int128' }, + { type: 'int256' }, + ], + [-1, -123, -123456, -1234567890n, -12345678901234567890n, -1234567890123456789012345678901234567890n], + ) + }) + + it('mixed types', () => { + const sink = new Sink(5) + sink.u8(1) + sink.i8(-2) + sink.address('0x1234567890123456789012345678901234567890') + sink.u256(3n) + sink.staticBytes(7, Buffer.from('1234567890abcd', 'hex')) + compareTypes( + sink, + [{ type: 'uint8' }, { type: 'int8' }, { type: 'address' }, { type: 'uint256' }, { type: 'bytes7' }], + [1, -2, '0x1234567890123456789012345678901234567890', 3n, '0x1234567890abcd'], + ) + }) + + describe('string', () => { + it('short string', () => { + const sink = new Sink(1) + sink.newStaticDataArea() + sink.string('hello') + sink.endCurrentDataArea() + compareTypes(sink, [{ type: 'string' }], ['hello']) + }) + + it('32 byte string', () => { + const sink = new Sink(1) + sink.newStaticDataArea() + sink.string('this string length is 32 bytes!!') + sink.endCurrentDataArea() + compareTypes(sink, [{ type: 'string' }], ['this string length is 32 bytes!!']) + }) + + it('longer string', () => { + const sink = new Sink(1) + sink.newStaticDataArea() + sink.string('this string length is 33 bytes!!!') + sink.endCurrentDataArea() + compareTypes(sink, [{ type: 'string' }], ['this string length is 33 bytes!!!']) + }) + + it('UTF', () => { + const sink = new Sink(1) + sink.newStaticDataArea() + sink.string('привет 👍') + sink.endCurrentDataArea() + compareTypes(sink, [{ type: 'string' }], ['привет 👍']) + }) + }) + + it('bytes', () => { + const sink = new Sink(1) + sink.newStaticDataArea() + const buffer = Buffer.alloc(150) + buffer.fill('xd') + sink.bytes(buffer) + sink.endCurrentDataArea() + compareTypes(sink, [{ type: 'bytes' }], [`0x${buffer.toString('hex')}`]) + }) +}) diff --git a/evm/evm-codec/test/src.test.ts b/evm/evm-codec/test/src.test.ts new file mode 100644 index 000000000..4eb54cb6e --- /dev/null +++ b/evm/evm-codec/test/src.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it } from 'vitest' +import { Sink, Src } from '../src' +import { encodeAbiParameters } from 'viem' + +describe('src', () => { + it('negative numbers', () => { + const sink = new Sink(6) + sink.i8(-1) + sink.i16(-123) + sink.i32(-123456) + sink.i64(-1234567890n) + sink.i128(-12345678901234567890n) + sink.i256(-1234567890123456789012345678901234567890n) + + const src = new Src(sink.result()) + expect(src.i8()).toBe(-1) + expect(src.i16()).toBe(-123) + expect(src.i32()).toBe(-123456) + expect(src.i64()).toBe(-1234567890n) + expect(src.i128()).toBe(-12345678901234567890n) + expect(src.i256()).toBe(-1234567890123456789012345678901234567890n) + }) + + it('positive signed numbers', () => { + const sink = new Sink(6) + sink.i8(1) + sink.i16(123) + sink.i32(123456) + sink.i64(1234567890n) + sink.i128(12345678901234567890n) + sink.i256(1234567890123456789012345678901234567890n) + + const src = new Src(sink.result()) + expect(src.i8()).toBe(1) + expect(src.i16()).toBe(123) + expect(src.i32()).toBe(123456) + expect(src.i64()).toBe(1234567890n) + expect(src.i128()).toBe(12345678901234567890n) + expect(src.i256()).toBe(1234567890123456789012345678901234567890n) + }) + + it('mixed static types', () => { + const sink = new Sink(4) + sink.u8(1) + sink.i8(-2) + sink.address('0x1234567890123456789012345678901234567890') + sink.u256(3n) + + const src = new Src(sink.result()) + expect(src.u8()).toBe(1) + expect(src.i8()).toBe(-2) + expect(src.address()).toBe('0x1234567890123456789012345678901234567890') + expect(src.u256()).toBe(3n) + }) + + it('mixed dynamic types', () => { + const str1 = 'abc'.repeat(100) + const bytes1 = Buffer.alloc(100).fill('321') + const bytes7 = '0x1234567890abcd' + const str2 = 'hello' + const address = '0xabc4567890123456789012345678901234567890' + const encoded = Buffer.from( + encodeAbiParameters( + [ + { type: 'uint8' }, + { type: 'string' }, + { type: 'bytes7' }, + { type: 'int128' }, + { type: 'bytes' }, + { type: 'address' }, + { type: 'string' }, + ], + [69, str1, bytes7, -21312312452243312424534213123123123123n, `0x${bytes1.toString('hex')}`, address, str2], + ).slice(2), + 'hex', + ) + const src = new Src(encoded) + expect(src.u8()).toBe(69) + expect(src.string()).toBe(str1) + expect(src.staticBytes(7)).toStrictEqual(Buffer.from(bytes7.slice(2), 'hex')) + expect(src.i128()).toBe(-21312312452243312424534213123123123123n) + expect(src.bytes()).toStrictEqual(bytes1) + expect(src.address()).toBe(address) + expect(src.string()).toBe(str2) + }) + + describe('string', () => { + function testString(str: string) { + const encoded = Buffer.from(encodeAbiParameters([{ type: 'string' }], [str]).slice(2), 'hex') + const src = new Src(encoded) + expect(src.string()).toBe(str) + } + + it('short string', () => { + testString('hello') + }) + + it('32 byte string', () => { + testString('this string length is 32 bytes!!') + }) + + it('longer string', () => { + testString('this string length is 33 bytes!!!') + }) + + it('UTF', () => { + testString('привет 👍') + }) + }) + + it('bytes', () => { + const buffer = Buffer.alloc(150) + buffer.fill('xd') + const encoded = Buffer.from( + encodeAbiParameters([{ type: 'bytes' }], [`0x${buffer.toString('hex')}`]).slice(2), + 'hex', + ) + const src = new Src(encoded) + expect(src.bytes()).toStrictEqual(buffer) + }) +}) diff --git a/evm/evm-codec/test/struct.bench.ts b/evm/evm-codec/test/struct.bench.ts new file mode 100644 index 000000000..37db29251 --- /dev/null +++ b/evm/evm-codec/test/struct.bench.ts @@ -0,0 +1,118 @@ +import { bench, describe } from 'vitest' +import { address, array, Codec, Sink, Src, struct, uint256 } from '../src' +import { decodeAbiParameters, encodeAbiParameters } from 'viem' +import { ethers } from 'ethers' + +const hugeArray = Array.from({ length: 1000 }, (_, i) => BigInt(i)) + +const s = struct({ + a: array(uint256), + b: uint256, + c: struct({ d: array(uint256), e: address }), +}) + +describe('StructCodec - encoding', () => { + bench('encoding dynamic tuple', () => { + const sink1 = new Sink(1) + + s.encode(sink1, { + a: hugeArray, + b: 2n, + c: { + d: hugeArray, + e: '0x1234567890123456789012345678901234567890', + }, + }) + }) + + bench('viem - encoding dynamic tuple', () => { + encodeAbiParameters( + [ + { + type: 'tuple', + components: [ + { name: 'a', type: 'uint256[]' }, + { name: 'b', type: 'uint256' }, + { + name: 'c', + type: 'tuple', + components: [ + { name: 'd', type: 'uint256[]' }, + { name: 'e', type: 'address' }, + ], + }, + ], + }, + ], + [ + { + a: hugeArray, + b: 2n, + c: { + d: hugeArray, + e: '0x1234567890123456789012345678901234567890', + }, + }, + ], + ) + }) + + bench('ethers - encoding dynamic tuple', () => { + ethers.utils.defaultAbiCoder.encode( + ['tuple(uint256[] a,uint256 b,tuple(uint256[] d,address e) c)'], + [ + { + a: hugeArray, + b: 2n, + c: { + d: hugeArray, + e: '0x1234567890123456789012345678901234567890', + }, + }, + ], + ) + }) +}) + +describe('StructCodec - decoding', () => { + const sink = new Sink(1) + s.encode(sink, { + a: hugeArray, + b: 2n, + c: { + d: hugeArray, + e: '0x1234567890123456789012345678901234567890', + }, + }) + + bench('decoding dynamic tuple', () => { + s.decode(new Src(sink.result())) + }) + + bench('viem - decoding dynamic tuple', () => { + decodeAbiParameters( + [ + { + type: 'tuple', + components: [ + { name: 'a', type: 'uint256[]' }, + { name: 'b', type: 'uint256' }, + { + name: 'c', + type: 'tuple', + components: [ + { name: 'd', type: 'uint256[]' }, + { name: 'e', type: 'address' }, + ], + }, + ], + }, + ], + sink.result(), + ) + }) + + bench('ethers - decoding dynamic tuple', () => { + ethers.utils.defaultAbiCoder.decode(['tuple(uint256[] a,uint256 b,tuple(uint256[] d,address e) c)'], sink.result()) + }) +}) diff --git a/evm/evm-codec/test/struct.test.ts b/evm/evm-codec/test/struct.test.ts new file mode 100644 index 000000000..de9816742 --- /dev/null +++ b/evm/evm-codec/test/struct.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, it } from 'vitest' +import { AbiParameter, encodeAbiParameters } from 'viem' +import { address, array, bytes4, int8, Sink, Src, struct, uint256 } from '../src' + +function compareTypes(sink: Sink, types: AbiParameter[], values: any[]) { + expect(sink.toString()).toEqual(encodeAbiParameters(types, values)) +} + +describe('StructCodec', () => { + it('static tuple', () => { + const s = struct({ + a: int8, + b: uint256, + c: struct({ e: address }), + }) + + const sink = new Sink(3) + s.encode(sink, { + a: 1, + b: 2n, + c: { + e: '0x1234567890123456789012345678901234567890', + }, + }) + + compareTypes( + sink, + [ + { + type: 'tuple', + components: [ + { name: 'a', type: 'int8' }, + { name: 'b', type: 'uint256' }, + { + name: 'c', + type: 'tuple', + components: [{ name: 'e', type: 'address' }], + }, + ], + }, + ], + [ + { + a: 1, + b: 2n, + c: { + d: [3n, 4n], + e: '0x1234567890123456789012345678901234567890', + }, + }, + ], + ) + expect(s.decode(new Src(sink.result()))).toStrictEqual({ + a: 1, + b: 2n, + c: { + e: '0x1234567890123456789012345678901234567890', + }, + }) + }) + + it('dynamic tuple', () => { + const s = struct({ + a: array(uint256), + b: uint256, + c: struct({ d: array(uint256), e: address }), + }) + + const sink = new Sink(1) + s.encode(sink, { + a: [100n, 1n, 123n], + b: 2n, + c: { + d: [3n, 4n], + e: '0x1234567890123456789012345678901234567890', + }, + }) + compareTypes( + sink, + [ + { + type: 'tuple', + components: [ + { name: 'a', type: 'uint256[]' }, + { name: 'b', type: 'uint256' }, + { + name: 'c', + type: 'tuple', + components: [ + { name: 'd', type: 'uint256[]' }, + { name: 'e', type: 'address' }, + ], + }, + ], + }, + ], + [ + { + a: [100n, 1n, 123n], + b: 2n, + c: { + d: [3n, 4n], + e: '0x1234567890123456789012345678901234567890', + }, + }, + ], + ) + + expect(s.decode(new Src(sink.result()))).toStrictEqual({ + a: [100n, 1n, 123n], + b: 2n, + c: { + d: [3n, 4n], + e: '0x1234567890123456789012345678901234567890', + }, + }) + }) + + it('dynamic tuple2', () => { + const s = struct({ + foo: uint256, + bar: array(uint256), + str: struct({ foo: uint256, bar: bytes4 }), + }) + + const sink = new Sink(1) + s.encode(sink, { + foo: 100n, + bar: [1n, 2n, 3n], + str: { + foo: 123n, + bar: Uint8Array.from([0x12, 0x34, 0x56, 0x78]), + }, + }) + compareTypes( + sink, + [ + { + type: 'tuple', + components: [ + { name: 'foo', type: 'uint256' }, + { name: 'bar', type: 'uint256[]' }, + { + name: 'str', + type: 'tuple', + components: [ + { name: 'foo', type: 'uint256' }, + { name: 'bar', type: 'bytes4' }, + ], + }, + ], + }, + ], + [ + { + foo: 100n, + bar: [1n, 2n, 3n], + str: { + foo: 123n, + bar: '0x12345678', + }, + }, + ], + ) + + expect(s.decode(new Src(sink.result()))).toStrictEqual({ + foo: 100n, + bar: [1n, 2n, 3n], + str: { + foo: 123n, + bar: Buffer.from([0x12, 0x34, 0x56, 0x78]), + }, + }) + }) +}) diff --git a/evm/evm-codec/tsconfig.build.json b/evm/evm-codec/tsconfig.build.json new file mode 100644 index 000000000..b90fc83e0 --- /dev/null +++ b/evm/evm-codec/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"] +} diff --git a/evm/evm-codec/tsconfig.json b/evm/evm-codec/tsconfig.json new file mode 100644 index 000000000..845192ba7 --- /dev/null +++ b/evm/evm-codec/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "outDir": "lib", + "rootDir": "src", + "allowJs": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true + }, + "include": ["src", "test"], + "exclude": ["node_modules"] +} diff --git a/rush.json b/rush.json index e670f12bd..b452483e1 100644 --- a/rush.json +++ b/rush.json @@ -425,6 +425,18 @@ "shouldPublish": true, "versionPolicyName": "npm" }, + { + "packageName": "@subsquid/evm-codec", + "projectFolder": "evm/evm-codec", + "shouldPublish": true, + "versionPolicyName": "npm" + }, + { + "packageName": "@subsquid/evm-abi", + "projectFolder": "evm/evm-abi", + "shouldPublish": true, + "versionPolicyName": "npm" + }, { "packageName": "@subsquid/evm-typegen", "projectFolder": "evm/evm-typegen",