From bcdafc064688261bc00124d8e3e8e1a5870b451c Mon Sep 17 00:00:00 2001 From: Miguel de Elias Date: Fri, 6 Nov 2026 16:54:43 -0300 Subject: [PATCH 1/2] chore(billing): import billing-contracts at 8b063db --- .github/workflows/lint.yml | 24 +- .markdownlintignore | 3 + .prettierignore | 3 + .solhintignore | 1 + eslint.config.mjs | 2 + package.json | 12 +- packages/billing/.example.env | 12 + packages/billing/.gitattributes | 1 + packages/billing/.gitignore | 26 + packages/billing/.solcover.js | 10 + packages/billing/LICENSE.md | 7 + packages/billing/README.md | 127 +++ packages/billing/addresses-staging.json | 16 + packages/billing/addresses.json | 53 ++ packages/billing/contracts/BanxaWrapper.sol | 95 +++ packages/billing/contracts/Billing.sol | 375 +++++++++ .../billing/contracts/BillingConnector.sol | 300 +++++++ packages/billing/contracts/Governed.sol | 71 ++ packages/billing/contracts/IBilling.sol | 103 +++ .../billing/contracts/IBillingConnector.sol | 94 +++ .../billing/contracts/IERC20WithPermit.sol | 33 + packages/billing/contracts/Rescuable.sol | 36 + .../contracts/arbitrum/AddressAliasHelper.sol | 46 ++ .../billing/contracts/arbitrum/IBridge.sol | 72 ++ .../billing/contracts/arbitrum/IInbox.sol | 88 +++ .../contracts/arbitrum/IMessageProvider.sol | 32 + .../billing/contracts/arbitrum/IOutbox.sol | 57 ++ .../arbitrum/L1ArbitrumMessenger.sol | 103 +++ packages/billing/contracts/arbitrum/README.md | 5 + .../billing/contracts/tests/BridgeMock.sol | 118 +++ .../billing/contracts/tests/InboxMock.sol | 198 +++++ .../contracts/tests/L1TokenGatewayMock.sol | 171 ++++ packages/billing/contracts/tests/Token.sol | 108 +++ packages/billing/hardhat.config.ts | 150 ++++ packages/billing/package.json | 83 ++ packages/billing/scripts/build | 6 + packages/billing/scripts/coverage | 7 + packages/billing/scripts/deployAll | 177 +++++ packages/billing/scripts/flatten | 24 + packages/billing/scripts/prepublish | 23 + packages/billing/scripts/security | 17 + packages/billing/scripts/test | 62 ++ packages/billing/tasks/deployment/accounts.ts | 10 + packages/billing/tasks/deployment/banxa.ts | 25 + packages/billing/tasks/deployment/billing.ts | 60 ++ .../tasks/deployment/billingConnector.ts | 26 + packages/billing/tasks/deployment/token.ts | 12 + packages/billing/tasks/extendContracts.ts | 28 + packages/billing/tasks/ops/addToMany.ts | 133 ++++ packages/billing/tasks/ops/pullAll.ts | 179 +++++ packages/billing/tasks/ops/utils.ts | 14 + packages/billing/test/banxa.test.ts | 143 ++++ packages/billing/test/billing.test.ts | 536 +++++++++++++ .../billing/test/billingConnector.test.ts | 736 ++++++++++++++++++ packages/billing/test/governed.test.ts | 59 ++ packages/billing/tsconfig.json | 13 + packages/billing/utils/addressBook.ts | 14 + packages/billing/utils/artifacts.ts | 22 + packages/billing/utils/contracts.ts | 61 ++ packages/billing/utils/deploy.ts | 97 +++ packages/billing/utils/helpers.ts | 51 ++ packages/billing/utils/logging.ts | 3 + scripts/lint-staged-run.sh | 1 + 63 files changed, 5156 insertions(+), 18 deletions(-) create mode 100644 packages/billing/.example.env create mode 100644 packages/billing/.gitattributes create mode 100644 packages/billing/.gitignore create mode 100644 packages/billing/.solcover.js create mode 100644 packages/billing/LICENSE.md create mode 100644 packages/billing/README.md create mode 100644 packages/billing/addresses-staging.json create mode 100644 packages/billing/addresses.json create mode 100644 packages/billing/contracts/BanxaWrapper.sol create mode 100644 packages/billing/contracts/Billing.sol create mode 100644 packages/billing/contracts/BillingConnector.sol create mode 100644 packages/billing/contracts/Governed.sol create mode 100644 packages/billing/contracts/IBilling.sol create mode 100644 packages/billing/contracts/IBillingConnector.sol create mode 100644 packages/billing/contracts/IERC20WithPermit.sol create mode 100644 packages/billing/contracts/Rescuable.sol create mode 100644 packages/billing/contracts/arbitrum/AddressAliasHelper.sol create mode 100644 packages/billing/contracts/arbitrum/IBridge.sol create mode 100644 packages/billing/contracts/arbitrum/IInbox.sol create mode 100644 packages/billing/contracts/arbitrum/IMessageProvider.sol create mode 100644 packages/billing/contracts/arbitrum/IOutbox.sol create mode 100644 packages/billing/contracts/arbitrum/L1ArbitrumMessenger.sol create mode 100644 packages/billing/contracts/arbitrum/README.md create mode 100644 packages/billing/contracts/tests/BridgeMock.sol create mode 100644 packages/billing/contracts/tests/InboxMock.sol create mode 100644 packages/billing/contracts/tests/L1TokenGatewayMock.sol create mode 100644 packages/billing/contracts/tests/Token.sol create mode 100644 packages/billing/hardhat.config.ts create mode 100644 packages/billing/package.json create mode 100755 packages/billing/scripts/build create mode 100755 packages/billing/scripts/coverage create mode 100755 packages/billing/scripts/deployAll create mode 100755 packages/billing/scripts/flatten create mode 100755 packages/billing/scripts/prepublish create mode 100755 packages/billing/scripts/security create mode 100755 packages/billing/scripts/test create mode 100644 packages/billing/tasks/deployment/accounts.ts create mode 100644 packages/billing/tasks/deployment/banxa.ts create mode 100644 packages/billing/tasks/deployment/billing.ts create mode 100644 packages/billing/tasks/deployment/billingConnector.ts create mode 100644 packages/billing/tasks/deployment/token.ts create mode 100644 packages/billing/tasks/extendContracts.ts create mode 100644 packages/billing/tasks/ops/addToMany.ts create mode 100644 packages/billing/tasks/ops/pullAll.ts create mode 100644 packages/billing/tasks/ops/utils.ts create mode 100644 packages/billing/test/banxa.test.ts create mode 100644 packages/billing/test/billing.test.ts create mode 100644 packages/billing/test/billingConnector.test.ts create mode 100644 packages/billing/test/governed.test.ts create mode 100644 packages/billing/tsconfig.json create mode 100644 packages/billing/utils/addressBook.ts create mode 100644 packages/billing/utils/artifacts.ts create mode 100644 packages/billing/utils/contracts.ts create mode 100644 packages/billing/utils/deploy.ts create mode 100644 packages/billing/utils/helpers.ts create mode 100644 packages/billing/utils/logging.ts diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 70c82990b..e35787c73 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -63,11 +63,11 @@ jobs: echo "Head SHA: $HEAD_SHA" # Get changed files, filtering out deleted files and files in ignored directories - CHANGED_TS_JS=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.(js|ts|jsx|tsx|cjs|mjs)$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' || true) - CHANGED_SOL=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.sol$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts|/test/)' || true) - CHANGED_MD=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.md$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' || true) - CHANGED_JSON=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.json$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' || true) - CHANGED_YAML=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.(yml|yaml)$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' || true) + CHANGED_TS_JS=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.(js|ts|jsx|tsx|cjs|mjs)$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' | grep -v '^packages/billing/' || true) + CHANGED_SOL=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.sol$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts|/test/)' | grep -v '^packages/billing/' || true) + CHANGED_MD=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.md$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' | grep -v '^packages/billing/' || true) + CHANGED_JSON=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.json$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' | grep -v '^packages/billing/' || true) + CHANGED_YAML=$(git diff --name-only --diff-filter=d $BASE_SHA $HEAD_SHA | grep -E '\.(yml|yaml)$' | grep -v -E '(node_modules|dist|build|cache|reports|lib|coverage|artifacts|typechain|hardhat-cache|ignition/deployments|ignition/modules/artifacts)' | grep -v '^packages/billing/' || true) # Save to files for later use echo "$CHANGED_TS_JS" > changed_ts_js.txt @@ -104,10 +104,10 @@ jobs: run: | if [ "${{ steps.lint_mode.outputs.mode }}" = "all" ]; then echo "Linting all Solidity files in each package..." - # Run solhint in each workspace package that has contracts (check mode) - pnpm -r exec bash -c 'if [ -d "contracts" ]; then npx solhint --max-warnings=0 --noPrompt --noPoster "contracts/**/*.sol" 2>/dev/null || exit 1; fi' + # Run solhint in each workspace package that has contracts (check mode), skipping imported billing-contracts + pnpm -r --filter '!./packages/billing' exec bash -c 'if [ -d "contracts" ]; then npx solhint --max-warnings=0 --noPrompt --noPoster "contracts/**/*.sol" 2>/dev/null || exit 1; fi' # Check formatting with prettier - SOL_FILES=$(git ls-files '*.sol') + SOL_FILES=$(git ls-files '*.sol' ':!packages/billing/**') if [ -n "$SOL_FILES" ]; then echo "$SOL_FILES" | tr '\n' '\0' | xargs -0 npx prettier --check --cache --log-level warn else @@ -148,7 +148,7 @@ jobs: run: | if [ "${{ steps.lint_mode.outputs.mode }}" = "all" ]; then echo "Linting all TypeScript/JavaScript files..." - TS_FILES=$(git ls-files '*.js' '*.ts' '*.cjs' '*.mjs' '*.jsx' '*.tsx') + TS_FILES=$(git ls-files '*.js' '*.ts' '*.cjs' '*.mjs' '*.jsx' '*.tsx' ':!packages/billing/**') if [ -n "$TS_FILES" ]; then echo "$TS_FILES" | tr '\n' '\0' | xargs -0 npx eslint --max-warnings=0 --no-warn-ignored echo "$TS_FILES" | tr '\n' '\0' | xargs -0 npx prettier --check --cache --log-level warn --no-error-on-unmatched-pattern @@ -169,7 +169,7 @@ jobs: run: | if [ "${{ steps.lint_mode.outputs.mode }}" = "all" ]; then echo "Linting all Markdown files..." - MD_FILES=$(git ls-files '*.md') + MD_FILES=$(git ls-files '*.md' ':!packages/billing/**') if [ -n "$MD_FILES" ]; then echo "$MD_FILES" | tr '\n' '\0' | xargs -0 npx markdownlint --ignore-path .gitignore --ignore-path .markdownlintignore echo "$MD_FILES" | tr '\n' '\0' | xargs -0 npx prettier --check --cache --log-level warn --no-error-on-unmatched-pattern @@ -191,7 +191,7 @@ jobs: if [ "${{ steps.lint_mode.outputs.mode }}" = "all" ]; then echo "Linting all JSON files..." # Exclude Ignition deployment artifacts and other build artifacts - JSON_FILES=$(git ls-files '*.json' | { grep -v -E '(ignition/deployments/.*/artifacts/|ignition/deployments/.*/build-info/|/\.openzeppelin/|deployments/.*/solcInputs/)' || true; }) + JSON_FILES=$(git ls-files '*.json' ':!packages/billing/**' | { grep -v -E '(ignition/deployments/.*/artifacts/|ignition/deployments/.*/build-info/|/\.openzeppelin/|deployments/.*/solcInputs/)' || true; }) if [ -n "$JSON_FILES" ]; then echo "$JSON_FILES" | tr '\n' '\0' | xargs -0 npx prettier --check --cache --log-level warn --no-error-on-unmatched-pattern else @@ -215,7 +215,7 @@ jobs: run: | if [ "${{ steps.lint_mode.outputs.mode }}" = "all" ]; then echo "Linting all YAML files..." - YAML_FILES=$(git ls-files '*.yml' '*.yaml') + YAML_FILES=$(git ls-files '*.yml' '*.yaml' ':!packages/billing/**') if [ -n "$YAML_FILES" ]; then echo "$YAML_FILES" | tr '\n' '\0' | xargs -0 npx yaml-lint echo "$YAML_FILES" | tr '\n' '\0' | xargs -0 npx prettier --check --cache --log-level warn --no-error-on-unmatched-pattern diff --git a/.markdownlintignore b/.markdownlintignore index b841adfce..fe776b4dd 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -38,3 +38,6 @@ deployments/hardhat/ **/*-localhost/ **/*-hardhat/ .changeset/ + +# Imported billing-contracts, must stay byte-identical +packages/billing/ diff --git a/.prettierignore b/.prettierignore index 5ab806792..c7ff70a71 100644 --- a/.prettierignore +++ b/.prettierignore @@ -40,5 +40,8 @@ deployments/hardhat/ # Verified mainnet source, must stay byte-identical packages/legacy/verified/ +# Imported billing-contracts, must stay byte-identical +packages/billing/ + # Pre-port artifacts, must stay byte-identical packages/*/test/fixtures/pre-port/ diff --git a/.solhintignore b/.solhintignore index 6a9effc81..da59efeac 100644 --- a/.solhintignore +++ b/.solhintignore @@ -1 +1,2 @@ packages/legacy/verified/ +packages/billing/ diff --git a/eslint.config.mjs b/eslint.config.mjs index f18655424..b63f0ce8a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -254,6 +254,8 @@ const eslintConfig = [ // Autogenerated GraphClient files (committed but should not be linted) '**/.graphclient-extracted/**', '**/.graphclient/**', + // Imported billing-contracts, must stay byte-identical + 'packages/billing/**', ], }, diff --git a/package.json b/package.json index e5d8ea1db..a1743c57c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "lint:ts": "eslint --fix --cache 'packages/**/*.{js,ts,cjs,mjs,jsx,tsx}' 'scripts/**/*.{js,ts,cjs,mjs}' '*.{js,ts,cjs,mjs}'; prettier -w --cache --log-level warn 'packages/**/*.{js,ts,cjs,mjs,jsx,tsx}' 'scripts/**/*.{js,ts,cjs,mjs}' '*.{js,ts,cjs,mjs}'", "lint:sol": "pnpm -r run lint:sol; prettier -w --cache --log-level warn 'packages/**/*.sol'; pnpm todo", "lint:forge": "pnpm -r run lint:forge", - "lint:md": "markdownlint --fix --ignore-path .gitignore 'packages/**/*.md' 'docs/**/*.md' '*.md'; prettier -w --cache --log-level warn 'packages/**/*.md' 'docs/**/*.md' '*.md'", + "lint:md": "markdownlint --fix --ignore-path .gitignore --ignore 'packages/billing/**' 'packages/**/*.md' 'docs/**/*.md' '*.md'; prettier -w --cache --log-level warn 'packages/**/*.md' 'docs/**/*.md' '*.md'", "lint:json": "prettier -w --cache --log-level warn 'packages/**/*.{json,json5}' '.changeset/**/*.json' '*.{json,json5}'", "lint:yaml": "npx yaml-lint .github/**/*.{yml,yaml}; prettier -w --cache --log-level warn 'packages/**/*.{yml,yaml}' '.github/**/*.{yml,yaml}'", "test": "pnpm build && pnpm -r run test:self", @@ -82,13 +82,13 @@ "scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'" ], "*.md": [ - "markdownlint --fix", - "prettier -w --cache --log-level warn" + "scripts/lint-staged-run.sh 'markdownlint --fix'", + "scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'" ], - "*.{json,json5}": "prettier -w --cache --log-level warn", + "*.{json,json5}": "scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'", "*.{yml,yaml}": [ - "npx yamllint", - "prettier -w --cache --log-level warn" + "scripts/lint-staged-run.sh 'npx yamllint'", + "scripts/lint-staged-run.sh 'prettier -w --cache --log-level warn'" ] } } diff --git a/packages/billing/.example.env b/packages/billing/.example.env new file mode 100644 index 000000000..4b5f6ffac --- /dev/null +++ b/packages/billing/.example.env @@ -0,0 +1,12 @@ +INFURA_KEY= +MNEMONIC="" +L1_MNEMONIC="" +L2_MNEMONIC="" +L1_CHAINID=5 +L2_CHAINID=421613 +PRIVATE_KEY="" +ETHERSCAN_API_KEY= +ARBISCAN_API_KEY= +POLYGON_RPC_URL= +ARBITRUM_RPC_URL= +ADDRESS_BOOK="addresses.json" diff --git a/packages/billing/.gitattributes b/packages/billing/.gitattributes new file mode 100644 index 000000000..52031de51 --- /dev/null +++ b/packages/billing/.gitattributes @@ -0,0 +1 @@ +*.sol linguist-language=Solidity diff --git a/packages/billing/.gitignore b/packages/billing/.gitignore new file mode 100644 index 000000000..58d1c8a45 --- /dev/null +++ b/packages/billing/.gitignore @@ -0,0 +1,26 @@ +# Ignore node stuff +node_modules/ +yarn-error.log + +# Ignore build stuff +cache/ +build/ +dist/ + +# Hardhat cache +cached/ + +# Ignore solc bin output +bin/ + +# Others +.env +.DS_Store +.vscode + +# Coverage and other reports +/reports +coverage.json + +tasks/ops/depositors.json +tasks/ops/depositors.json.bak diff --git a/packages/billing/.solcover.js b/packages/billing/.solcover.js new file mode 100644 index 000000000..de67fe376 --- /dev/null +++ b/packages/billing/.solcover.js @@ -0,0 +1,10 @@ +const skipFiles = ['tests/', 'arbitrum/'] + +module.exports = { + providerOptions: { + mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect', + network_id: 1337, + }, + skipFiles, + istanbulFolder: './reports/coverage', +} diff --git a/packages/billing/LICENSE.md b/packages/billing/LICENSE.md new file mode 100644 index 000000000..3646f2d80 --- /dev/null +++ b/packages/billing/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2020 Edge & Node + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/billing/README.md b/packages/billing/README.md new file mode 100644 index 000000000..0e526ac65 --- /dev/null +++ b/packages/billing/README.md @@ -0,0 +1,127 @@ +# Billing Contract + +This repository contains a Billing contract. It allows users to add tokens (GRT) into the contract for an agent to pull when a billing period is up. + +## Contract Design + +The contract is designed with the following requirements: + +- The contract is owned by the `governor`. +- There is a privileged role named `collector` that has a special permission to pull user funds (this is, for now, the Gateway that acts as intermediary between Consumers and Indexers in The Graph Network). +- Users add tokens into the contract by calling `add()`. It is important to note that the contract + is designed so that the users are trusting the Collector. Once the user adds tokens, the Collector can + pull the funds whenever it wants. +- The trust risk for the User is that the Collector would pull funds before the User spent their funds, which are query fees in The Graph Network. +- The trust risk for the Collector is that the user adds funds, spends them on queries, and then + removes their tokens before the Collector pulled the tokens. +- These combined trust risks make for a situation that we expect both parties to act responsibly. + It will always be recommended to keep the amount of GRT in the contract low for each user, and for + the Collector to pull regularly. This way, if one side does not play nice, the funds lost won't be + that large. + +## L1-L2 communication + +The Billing contract is designed to be deployed to an L2 (Arbitrum). A BillingConnector contract can be deployed to L1 (mainnet), and allows users to add funds to the Billing contract using a token gateway that allows callhooks as described in [GIP-0031](https://forum.thegraph.com/t/gip-0031-arbitrum-grt-bridge/3305) + +To add tokens from L1 to L2, there are two options: + +a) If the user's address is an EOA, they can use [BillingConnector.addToL2WithPermit](./contracts/IBillingConnector.sol#L59-L81). They first need to sign a permit for the BillingConnector contract to pull the GRT, and then run the tx including that signature, which will send those GRT to the billing balance for that same address in L2. + +b) If the user's address is a contract (e.g. a multisig), or they don't want to sign a permit, then they can use [BillingConnector.addToL2](./contracts/IBillingConnector.sol#L24-L39), but they need to run `GraphToken.approve(billingConnector.address, amount)` beforehand. + +To estimate the L2 submission and gas parameters (`_maxGas`, `_gasPriceBid`, and `_maxSubmissionCost`), we recommend using the [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk). An example for this is available in the [graphprotocol/contracts](https://github.com/graphprotocol/contracts/blob/pcv/l2-bridge/cli/commands/bridge/to-l2.ts#L63-L94) repo - just replace the calldata with the [calldata produced by BillingConnector](./test/billingConnector.test.ts#L277-L284) + +To remove tokens on L2, users that have an EOA, or whose account exists in L2 with the same address as L1, can call `Billing.remove` directly on L2 to remove funds to an L2 address. Users whose address doesn't exist in L2 (e.g. when using a multisig), can instead use [BillingConnector.removeOnL2](./contracts/IBillingConnector.sol#L41-L57) to send a message from L1 that will tell the Billing contract to move the tokens to the desired address. There is no validation of the amount actually existing on the L2 balance, and guarantee that the message will arrive before funds are pulled by the Collector, so the message might revert on L2 if the balance is insufficient. Note that this call will also require estimating L2 gas parameters. In this case, the retryable ticket is created directly by the BillingConnector (instead of by the L1GraphTokenGateway), so keep this in mind when estimating gas with the Arbitrum SDK. + +## Testing + +Run the test suite with: + +```bash +yarn test +``` + +## Using the console + +Here is how to use hardhat to quickly do some transactions: + +```bash +hh console --network polygon + +accounts = await ethers.getSigners() +token = await hre.contracts.Token.connect(accounts[0]) +await token.approve('0x5DE9A13C486f5aA12F4D8e5E77246F6E24dac274', '1000000000000000000000') + +billing = await hre.contracts.Billing.connect(accounts[0]) +await billing.add('1000000000000000000') +``` + +## Deploy instructions + +To deploy, see these instructions: + +### Billing +Use arbitrum-one (or arbitrum-goerli) to deploy Billing: + +``` +hh deploy-billing --network arbitrum-one \ + --collector \ + --token \ + --governor \ + --tokengateway +``` + +Then run this to verify on Etherscan: + +``` +hh verify --network arbitrum-one \ + \ + \ + \ + \ + +``` + +### BillingConnector +Use mainnet (or goerli) to deploy BillingConnector: + +``` +hh deploy-billing-connector --network mainnet \ + --tokengateway \ + --billing \ + --token \ + --governor \ + --inbox +``` + +Then run this to verify on Etherscan: + +``` +hh verify --network mainnet \ + \ + \ + \ + \ + \ + +``` + +### BanxaWrapper +Use arbitrum-one (or arbitrum-goerli) to deploy BanxaConnector: + +``` +hh deploy-banxa --network arbitrum-one \ + --token \ + --billing \ + --governor +``` + +Then run this to verify on Etherscan: + +``` +hh verify --network arbitrum-one \ + \ + \ + \ + +``` diff --git a/packages/billing/addresses-staging.json b/packages/billing/addresses-staging.json new file mode 100644 index 000000000..7cb2b53f6 --- /dev/null +++ b/packages/billing/addresses-staging.json @@ -0,0 +1,16 @@ +{ + "421614": { + "L2GraphToken": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04", + "L2GraphTokenGateway": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb", + "collector": "0x66FFdb62688deA21A0031Db7CD4C219C82e9B315", + "governor": "0x97B47D976b9ddD2608D61E77Ab10DAf05d17cbF9", + "Billing": "0x1AbcE187d2401687520dBf720210a0ec84C0690D" + }, + "11155111": { + "ArbitrumInbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", + "GraphToken": "0xCA59cCeb39bE1808d7aA607153f4A5062daF3a83", + "L1GraphTokenGateway": "0x549DCB0b510Ee7C8d6E8e346ab9aC75E22DD78B3", + "governor": "0x20cFA0e7Db2ECd14032784075A4c686befc76758", + "BillingConnector": "0xf9B9FCf0d603e6bead3335953EB5Bcf876990043" + } +} diff --git a/packages/billing/addresses.json b/packages/billing/addresses.json new file mode 100644 index 000000000..0a2e5de3f --- /dev/null +++ b/packages/billing/addresses.json @@ -0,0 +1,53 @@ +{ + "1": { + "ArbitrumInbox": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", + "GraphToken": "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + "L1GraphTokenGateway": "0x01cDC91B0A9bA741903aA3699BF4CE31d6C5cC06", + "governor": "0x74Db79268e63302d3FC69FB5a7627F7454a41732", + "BillingConnector": "0x8017B9AF3F199CC6b08A48DA3859410F20bbea72" + }, + "5": { + "ArbitrumInbox": "0x6BEbC4925716945D46F0Ec336D5C2564F419682C", + "GraphToken": "0x5c946740441C12510a167B447B7dE565C20b9E3C", + "L1GraphTokenGateway": "0xc82fF7b51c3e593D709BA3dE1b3a0d233D1DEca1", + "governor": "0xc6B8Dd6163a0d6E3Ff37Eb4306DeD0069FE4bF59", + "BillingConnector": "0x0de059845a3886A10d076A34d38b6b1aB24900b2" + }, + "137": { + "Billing": "0x10829DB618E6F520Fa3A01c75bC6dDf8722fA9fE" + }, + "42161": { + "L2GraphToken": "0x9623063377AD1B27544C965cCd7342f7EA7e88C7", + "L2GraphTokenGateway": "0x65E1a5e8946e7E87d9774f5288f41c30a99fD302", + "collector": "0x76C00F71F4dACE63fd83eC80dBc8c30a88B2891c", + "governor": "0x270Ea4ea9e8A699f8fE54515E3Bb2c418952623b", + "Billing": "0x1B07D3344188908Fb6DEcEac381f3eE63C48477a", + "BanxaWrapper": "0xc9E1Aa57223aDD21cC88A03088aF552f1ea8A34a" + }, + "80001": { + "Billing": "0x69ccB949a45dBE759DEfE7cb9f5E62031A3E28DA" + }, + "421613": { + "L2GraphToken": "0x18C924BD5E8b83b47EFaDD632b7178E2Fd36073D", + "L2GraphTokenGateway": "0xef2757855d2802bA53733901F90C91645973f743", + "collector": "0x52e498ae9b8a5ee2a5cd26805f06a9f29a7f489f", + "governor": "0xc6B8Dd6163a0d6E3Ff37Eb4306DeD0069FE4bF59", + "Billing": "0x2da5DF23869B05b6641C91297b7025029112abA7", + "BanxaWrapper": "0xDc4787B4c822a36F72A0084B02dA213A7291C35B" + }, + "421614": { + "L2GraphToken": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04", + "L2GraphTokenGateway": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb", + "collector": "0x66FFdb62688deA21A0031Db7CD4C219C82e9B315", + "governor": "0x97B47D976b9ddD2608D61E77Ab10DAf05d17cbF9", + "Billing": "0xDb29A6dD3028e8cb8c7Db27E36701D533BE99EB6", + "BanxaWrapper": "0x08705a03C423124ea6Ab030FCdF20f1AdD7Ee266" + }, + "11155111": { + "ArbitrumInbox": "0xaAe29B0366299461418F5324a79Afc425BE5ae21", + "GraphToken": "0xCA59cCeb39bE1808d7aA607153f4A5062daF3a83", + "L1GraphTokenGateway": "0x549DCB0b510Ee7C8d6E8e346ab9aC75E22DD78B3", + "governor": "0x20cFA0e7Db2ECd14032784075A4c686befc76758", + "BillingConnector": "0x12AF388f7e41c23702B5DE48C12A81f107d92391" + } +} diff --git a/packages/billing/contracts/BanxaWrapper.sol b/packages/billing/contracts/BanxaWrapper.sol new file mode 100644 index 000000000..2f6e8f63b --- /dev/null +++ b/packages/billing/contracts/BanxaWrapper.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IBilling } from "./IBilling.sol"; +import { Governed } from "./Governed.sol"; +import { Rescuable } from "./Rescuable.sol"; + +/** + * @title Banxa Wrapper + * @dev Wraps the billing contract to provide a custom interface for the Banxa Fulfillment Service + */ +contract BanxaWrapper is Governed, Rescuable { + // -- State -- + + /// The Graph Token contract + IERC20 public immutable graphToken; + + /// The billing contract + IBilling public immutable billing; + + // -- Events -- + + /** + * @dev Order fullfilled by Banxa fulfilment service + */ + event OrderFulfilled(address indexed fulfiller, address indexed to, uint256 amount); + + // -- Errors -- + + /** + * @dev Zero address not allowed. + */ + error InvalidZeroAddress(); + + /** + * @dev Zero amount not allowed. + */ + error InvalidZeroAmount(); + + /** + * @notice Constructor function for the contract + * @param _token Graph Token address + * @param _billing billing contract address + */ + constructor( + IERC20 _token, + IBilling _billing, + address _governor + ) Governed(_governor) { + if (address(_token) == address(0) || address(_billing) == address(0)) { + revert InvalidZeroAddress(); + } + + graphToken = _token; + billing = _billing; + } + + /** + * @notice Pulls tokens from sender and adds them into the billing contract for any user + * Ensure graphToken.approve() is called on the wrapper contract first + * @param _to Address that tokens are being added to + * @param _amount Amount of tokens to add + */ + function fulfil(address _to, uint256 _amount) external { + if (_to == address(0)) { + revert InvalidZeroAddress(); + } + + if (_amount == 0) { + revert InvalidZeroAmount(); + } + + graphToken.transferFrom(msg.sender, address(this), _amount); + graphToken.approve(address(billing), _amount); + billing.addTo(_to, _amount); + + emit OrderFulfilled(msg.sender, _to, _amount); + } + + /** + * @notice Allows the Governor to rescue any ERC20 tokens sent to this contract by accident + * @param _to Destination address to send the tokens + * @param _token Token address of the token that was accidentally sent to the contract + * @param _amount Amount of tokens to pull + */ + function rescueTokens( + address _to, + address _token, + uint256 _amount + ) external onlyGovernor { + _rescueTokens(_to, _token, _amount); + } +} diff --git a/packages/billing/contracts/Billing.sol b/packages/billing/contracts/Billing.sol new file mode 100644 index 000000000..250d9fe66 --- /dev/null +++ b/packages/billing/contracts/Billing.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { IBilling } from "./IBilling.sol"; +import { Governed } from "./Governed.sol"; +import { Rescuable } from "./Rescuable.sol"; +import { AddressAliasHelper } from "./arbitrum/AddressAliasHelper.sol"; + +/** + * @title Billing Contract + * @dev The billing contract allows for Graph Tokens to be added by a user. The token can then + * be pulled by a permissioned set of users named 'collectors'. It is owned and controlled by the 'governor'. + */ +contract Billing is IBilling, Governed, Rescuable { + // -- State -- + + // The contract for interacting with The Graph Token + IERC20 private immutable graphToken; + // True for addresses that are Collectors + mapping(address => bool) public isCollector; + + // maps user address --> user billing balance + mapping(address => uint256) public userBalances; + + // The L2 token gateway address + address public l2TokenGateway; + + // The L1 BillingConnector address + address public l1BillingConnector; + + // -- Events -- + + /** + * @dev User adds tokens + */ + event TokensAdded(address indexed user, uint256 amount); + + /** + * @dev User removes tokens + */ + event TokensRemoved(address indexed from, address indexed to, uint256 amount); + + /** + * @dev User tried to remove tokens from L1, + * but they did not have enough balance + */ + event InsufficientBalanceForRemoval(address indexed from, address indexed to, uint256 amount); + + /** + * @dev Gateway pulled tokens from a user + */ + event TokensPulled(address indexed user, uint256 amount); + + /** + * @dev Collector added or removed + */ + event CollectorUpdated(address indexed collector, bool enabled); + + /** + * @dev L2 Token Gateway address updated + */ + event L2TokenGatewayUpdated(address l2TokenGateway); + + /** + * @dev L1 BillingConnector address updated + */ + event L1BillingConnectorUpdated(address l1BillingConnector); + + /** + * @notice Constructor function for the Billing contract + * @dev Note that the l1BillingConnector address must be provided + * afterwards through setL1BillingConnector, since it's expected + * to be deployed after this one. + * @param _collector Initial collector address + * @param _token Graph Token address + * @param _governor Governor address + */ + constructor( + address _collector, + IERC20 _token, + address _governor, + address _l2TokenGateway + ) Governed(_governor) { + _setCollector(_collector, true); + _setL2TokenGateway(_l2TokenGateway); + graphToken = _token; + } + + /** + * @dev Check if the caller is a Collector. + */ + modifier onlyCollector() { + require(isCollector[msg.sender], "Caller must be Collector"); + _; + } + + /** + * @dev Check if the caller is the L2 token gateway. + */ + modifier onlyL2TokenGateway() { + require(msg.sender == l2TokenGateway, "Caller must be L2 token gateway"); + _; + } + + /** + * @dev Check if the caller is the L2 alias of the L1 BillingConnector + */ + modifier onlyL1BillingConnector() { + require(l1BillingConnector != address(0), "BillingConnector not set"); + require( + msg.sender == AddressAliasHelper.applyL1ToL2Alias(l1BillingConnector), + "Caller must be L1 BillingConnector" + ); + _; + } + + /** + * @notice Set or unset an address as an allowed Collector + * @param _collector Collector address + * @param _enabled True to set the _collector address as a Collector, false to remove it + */ + function setCollector(address _collector, bool _enabled) external override onlyGovernor { + _setCollector(_collector, _enabled); + } + + /** + * @notice Sets the L2 token gateway address + * @param _l2TokenGateway New address for the L2 token gateway + */ + function setL2TokenGateway(address _l2TokenGateway) external override onlyGovernor { + _setL2TokenGateway(_l2TokenGateway); + } + + /** + * @notice Sets the L1 Billing Connector address + * @param _l1BillingConnector New address for the L1 BillingConnector (without any aliasing!) + */ + function setL1BillingConnector(address _l1BillingConnector) external override onlyGovernor { + require(_l1BillingConnector != address(0), "L1 Billing Connector cannot be 0"); + l1BillingConnector = _l1BillingConnector; + emit L1BillingConnectorUpdated(_l1BillingConnector); + } + + /** + * @notice Add tokens into the billing contract + * @dev Ensure graphToken.approve() is called on the billing contract first + * @param _amount Amount of tokens to add + */ + function add(uint256 _amount) external override { + _pullAndAdd(msg.sender, msg.sender, _amount); + } + + /** + * @notice Add tokens into the billing contract for any user + * @dev Ensure graphToken.approve() is called on the billing contract first + * @param _to Address that tokens are being added to + * @param _amount Amount of tokens to add + */ + function addTo(address _to, uint256 _amount) external override { + _pullAndAdd(msg.sender, _to, _amount); + } + + /** + * @notice Receive tokens with a callhook from the Arbitrum GRT bridge + * @dev Expects an `address user` in the encoded _data. + * @param _from Token sender in L1 + * @param _amount Amount of tokens that were transferred + * @param _data ABI-encoded callhook data: contains address that tokens are being added to + */ + function onTokenTransfer( + address _from, + uint256 _amount, + bytes calldata _data + ) external override onlyL2TokenGateway { + require(l1BillingConnector != address(0), "BillingConnector not set"); + require(_from == l1BillingConnector, "Invalid L1 sender!"); + address user = abi.decode(_data, (address)); + _add(user, _amount); + } + + /** + * @notice Remove tokens from the billing contract, from L1 + * @dev This can only be called from the BillingConnector on L1. + * If the user does not have enough balance, rather than reverting, + * this function will succeed and emit InsufficientBalanceForRemoval. + * @param _from Address from which the tokens are removed + * @param _to Address to send the tokens + * @param _amount Amount of tokens to remove + */ + function removeFromL1( + address _from, + address _to, + uint256 _amount + ) external override onlyL1BillingConnector { + require(_to != address(0), "destination != 0"); + require(_amount != 0, "Must remove more than 0"); + if (userBalances[_from] >= _amount) { + userBalances[_from] = userBalances[_from] - _amount; + graphToken.transfer(_to, _amount); + emit TokensRemoved(_from, _to, _amount); + } else { + emit InsufficientBalanceForRemoval(_from, _to, _amount); + } + } + + /** + * @notice Add tokens into the billing contract in bulk + * @dev Ensure graphToken.approve() is called on the billing contract first + * @param _to Array of addresses where to add tokens + * @param _amount Array of amount of tokens to add to each account + */ + function addToMany(address[] calldata _to, uint256[] calldata _amount) external override { + require(_to.length == _amount.length, "Lengths not equal"); + + // Get total amount to add + uint256 totalAmount = 0; + for (uint256 i = 0; i < _amount.length; i++) { + require(_amount[i] > 0, "Must add more than 0"); + totalAmount += _amount[i]; + } + graphToken.transferFrom(msg.sender, address(this), totalAmount); + + // Add each amount + for (uint256 i = 0; i < _to.length; i++) { + address user = _to[i]; + require(user != address(0), "user != 0"); + userBalances[user] += _amount[i]; + emit TokensAdded(user, _amount[i]); + } + } + + /** + * @notice Remove tokens from the billing contract + * @dev Tokens will be removed from the sender's balance + * @param _to Address that tokens will be sent to + * @param _amount Amount of tokens to remove + */ + function remove(address _to, uint256 _amount) external override { + require(_to != address(0), "destination != 0"); + require(_amount != 0, "Must remove more than 0"); + require(userBalances[msg.sender] >= _amount, "Too much removed"); + userBalances[msg.sender] = userBalances[msg.sender] - _amount; + graphToken.transfer(_to, _amount); + emit TokensRemoved(msg.sender, _to, _amount); + } + + /** + * @notice Collector pulls tokens from the billing contract + * @param _user Address that tokens are being pulled from + * @param _amount Amount of tokens to pull + * @param _to Destination to send pulled tokens + */ + function pull( + address _user, + uint256 _amount, + address _to + ) external override onlyCollector { + uint256 maxAmount = _pull(_user, _amount); + _sendTokens(_to, maxAmount); + } + + /** + * @notice Collector pulls tokens from many users in the billing contract + * @param _users Addresses that tokens are being pulled from + * @param _amounts Amounts of tokens to pull from each user + * @param _to Destination to send pulled tokens + */ + function pullMany( + address[] calldata _users, + uint256[] calldata _amounts, + address _to + ) external override onlyCollector { + require(_users.length == _amounts.length, "Lengths not equal"); + uint256 totalPulled; + for (uint256 i = 0; i < _users.length; i++) { + uint256 userMax = _pull(_users[i], _amounts[i]); + totalPulled = totalPulled + userMax; + } + _sendTokens(_to, totalPulled); + } + + /** + * @notice Allows the Governor to rescue any ERC20 tokens sent to this contract by accident + * @param _to Destination address to send the tokens + * @param _token Token address of the token that was accidentally sent to the contract + * @param _amount Amount of tokens to pull + */ + function rescueTokens( + address _to, + address _token, + uint256 _amount + ) external onlyGovernor { + _rescueTokens(_to, _token, _amount); + } + + /** + * @dev Collector pulls tokens from the billing contract. Uses Math.min() so that it won't fail + * in the event that a user removes in front of the Collector pulling + * @param _user Address that tokens are being pulled from + * @param _amount Amount of tokens to pull + */ + function _pull(address _user, uint256 _amount) internal returns (uint256) { + uint256 maxAmount = Math.min(_amount, userBalances[_user]); + if (maxAmount > 0) { + userBalances[_user] = userBalances[_user] - maxAmount; + emit TokensPulled(_user, maxAmount); + } + return maxAmount; + } + + /** + * @dev Send tokens to a destination account + * @param _to Address where to send tokens + * @param _amount Amount of tokens to send + */ + function _sendTokens(address _to, uint256 _amount) internal { + if (_amount > 0) { + require(_to != address(0), "Cannot transfer to empty address"); + graphToken.transfer(_to, _amount); + } + } + + /** + * @dev Set or unset an address as an allowed Collector + * @param _collector Collector address + * @param _enabled True to set the _collector address as a Collector, false to remove it + */ + function _setCollector(address _collector, bool _enabled) internal { + require(_collector != address(0), "Collector cannot be 0"); + isCollector[_collector] = _enabled; + emit CollectorUpdated(_collector, _enabled); + } + + /** + * @dev Set the new L2 token gateway address + * @param _l2TokenGateway New L2 token gateway address + */ + function _setL2TokenGateway(address _l2TokenGateway) internal { + require(_l2TokenGateway != address(0), "L2 Token Gateway cannot be 0"); + l2TokenGateway = _l2TokenGateway; + emit L2TokenGatewayUpdated(_l2TokenGateway); + } + + /** + * @dev Pull, then add tokens into the billing contract + * Ensure graphToken.approve() is called on the billing contract first + * @param _from Address that is sending tokens + * @param _user User that is adding tokens + * @param _amount Amount of tokens to add + */ + function _pullAndAdd( + address _from, + address _user, + uint256 _amount + ) private { + require(_amount != 0, "Must add more than 0"); + require(_user != address(0), "user != 0"); + graphToken.transferFrom(_from, address(this), _amount); + _add(_user, _amount); + } + + /** + * @dev Add tokens into the billing account balance for a user + * Tokens must already be in this contract's balance + * @param _user User that is adding tokens + * @param _amount Amount of tokens to add + */ + function _add(address _user, uint256 _amount) private { + userBalances[_user] = userBalances[_user] + _amount; + emit TokensAdded(_user, _amount); + } +} diff --git a/packages/billing/contracts/BillingConnector.sol b/packages/billing/contracts/BillingConnector.sol new file mode 100644 index 000000000..ac3296d52 --- /dev/null +++ b/packages/billing/contracts/BillingConnector.sol @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IBilling } from "./IBilling.sol"; +import { IBillingConnector } from "./IBillingConnector.sol"; +import { Governed } from "./Governed.sol"; +import { ITokenGateway } from "arb-bridge-peripherals/contracts/tokenbridge/libraries/gateway/ITokenGateway.sol"; +import { Rescuable } from "./Rescuable.sol"; +import { IERC20WithPermit } from "./IERC20WithPermit.sol"; +import { L1ArbitrumMessenger } from "./arbitrum/L1ArbitrumMessenger.sol"; + +/** + * @title Billing Connector Contract + * @dev The billing contract allows for Graph Tokens to be added by a user. The + * tokens are immediately sent to the Billing contract on L2 (Arbitrum) + * through the GRT token bridge. + */ +contract BillingConnector is IBillingConnector, Governed, Rescuable, L1ArbitrumMessenger { + // -- State -- + // The contract for interacting with The Graph Token + IERC20 private immutable graphToken; + // The L1 Token Gateway through which tokens are sent to L2 + ITokenGateway public l1TokenGateway; + // The Billing contract in L2 to which we send tokens + address public l2Billing; + // The Arbitrum Delayed Inbox address + address public inbox; + + /** + * @dev Address of the L1 token gateway was updated + */ + event L1TokenGatewayUpdated(address l1TokenGateway); + /** + * @dev Address of the L2 Billing contract was updated + */ + event L2BillingUpdated(address l2Billing); + /** + * @dev Address of the Arbitrum Inbox contract was updated + */ + event ArbitrumInboxUpdated(address inbox); + /** + * @dev Tokens sent to the Billing contract on L2 + */ + event TokensSentToL2(address indexed _from, address indexed _to, uint256 _amount); + /** + * @dev Request sent to the Billing contract on L2 to remove tokens from the balance + */ + event RemovalRequestSentToL2(address indexed _from, address indexed _to, uint256 _amount); + + /** + * @notice Constructor function for BillingConnector + * @param _l1TokenGateway L1GraphTokenGateway address + * @param _l2Billing Address of the Billing contract on L2 + * @param _token Graph Token address + * @param _governor Governor address + * @param _inbox Arbitrum Delayed Inbox address + */ + constructor( + address _l1TokenGateway, + address _l2Billing, + IERC20 _token, + address _governor, + address _inbox + ) Governed(_governor) { + _setL1TokenGateway(_l1TokenGateway); + _setL2Billing(_l2Billing); + _setArbitrumInbox(_inbox); + graphToken = _token; + } + + /** + * @notice Sets the L1 token gateway address + * @param _l1TokenGateway New address for the L1 token gateway + */ + function setL1TokenGateway(address _l1TokenGateway) external override onlyGovernor { + _setL1TokenGateway(_l1TokenGateway); + } + + /** + * @notice Sets the L2 Billing address + * @param _l2Billing New address for the L2 Billing contract + */ + function setL2Billing(address _l2Billing) external override onlyGovernor { + _setL2Billing(_l2Billing); + } + + /** + * @notice Sets the Arbitrum Delayed Inbox address + * @param _inbox New address for the Arbitrum Delayed Inbox + */ + function setArbitrumInbox(address _inbox) external override onlyGovernor { + _setArbitrumInbox(_inbox); + } + + /** + * @notice Add tokens into the billing contract on L2, for any user + * @dev Ensure graphToken.approve() is called for the BillingConnector contract first + * @param _to Address that tokens are being added to + * @param _amount Amount of tokens to add + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + */ + function addToL2( + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable override { + require(_amount != 0, "Must add more than 0"); + require(_to != address(0), "destination != 0"); + _addToL2(msg.sender, _to, _amount, _maxGas, _gasPriceBid, _maxSubmissionCost); + } + + /** + * @notice Remove tokens from the billing contract on L2, sending the tokens + * to an L2 address + * @dev Useful when the tokens are in the balance for an address + * that doesn't exist in L2. + * Keep in mind there's no guarantee that the transaction will succeed in L2, + * e.g. if the sender doesn't actually have enough balance there. + * @param _to L2 address to which the tokens (and any surplus ETH) will be sent + * @param _amount Amount of tokens to remove + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + */ + function removeOnL2( + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable override { + require(_amount != 0, "Must remove more than 0"); + require(_to != address(0), "destination != 0"); + // Callers of this function should generally be L1 contracts + // (e.g. multisigs) that don't exist in L2, so the destination + // must be some other address. + require(_to != msg.sender, "destination != sender"); + + bytes memory l2Calldata = abi.encodeWithSelector(IBilling.removeFromL1.selector, msg.sender, _to, _amount); + + // The bridge will validate msg.value and submission cost later, but at least fail early + // if no submission cost is supplied. + require(_maxSubmissionCost != 0, "Submission cost must be > 0"); + L2GasParams memory gasParams = L2GasParams(_maxSubmissionCost, _maxGas, _gasPriceBid); + + sendTxToL2(inbox, l2Billing, _to, msg.value, 0, gasParams, l2Calldata); + emit RemovalRequestSentToL2(msg.sender, _to, _amount); + } + + /** + * @notice Add tokens into the billing contract on L2 using a signed permit + * @dev _user must be the msg.sender + * @param _user Address of the current owner of the tokens, that will also be the destination in L2 + * @param _amount Amount of tokens to add + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + * @param _deadline Expiration time of the signed permit + * @param _v Signature recovery id + * @param _r Signature r value + * @param _s Signature s value + */ + function addToL2WithPermit( + address _user, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external payable override { + require(_amount != 0, "Must add more than 0"); + require(_user != address(0), "destination != 0"); + require(_user == msg.sender, "Only tokens owner can call"); + _permit(_user, address(this), _amount, _deadline, _v, _r, _s); + _addToL2(_user, _user, _amount, _maxGas, _gasPriceBid, _maxSubmissionCost); + } + + /** + * @notice Allows the Governor to rescue any ERC20 tokens sent to this contract by accident + * @param _to Destination address to send the tokens + * @param _token Token address of the token that was accidentally sent to the contract + * @param _amount Amount of tokens to pull + */ + function rescueTokens( + address _to, + address _token, + uint256 _amount + ) external override onlyGovernor { + _rescueTokens(_to, _token, _amount); + } + + /** + * @dev Add tokens into the billing contract on L2, for any user + * Ensure graphToken.approve() or graphToken.permit() is called for the BillingConnector contract first + * @param _owner Address of the current owner of the tokens + * @param _to Address that tokens are being added to + * @param _amount Amount of tokens to add + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + */ + function _addToL2( + address _owner, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) internal { + graphToken.transferFrom(_owner, address(this), _amount); + + bytes memory extraData = abi.encode(_to); + bytes memory data = abi.encode(_maxSubmissionCost, extraData); + + graphToken.approve(address(l1TokenGateway), _amount); + l1TokenGateway.outboundTransfer{ value: msg.value }( + address(graphToken), + l2Billing, + _amount, + _maxGas, + _gasPriceBid, + data + ); + + emit TokensSentToL2(_owner, _to, _amount); + } + + /** + * @dev Sets the L1 token gateway address + * @param _l1TokenGateway New address for the L1 token gateway + */ + function _setL1TokenGateway(address _l1TokenGateway) internal { + require(_l1TokenGateway != address(0), "L1 Token Gateway cannot be 0"); + l1TokenGateway = ITokenGateway(_l1TokenGateway); + emit L1TokenGatewayUpdated(_l1TokenGateway); + } + + /** + * @dev Sets the L2 Billing address + * @param _l2Billing New address for the L2 Billing contract + */ + function _setL2Billing(address _l2Billing) internal { + require(_l2Billing != address(0), "L2 Billing cannot be zero"); + l2Billing = _l2Billing; + emit L2BillingUpdated(_l2Billing); + } + + /** + * @dev Sets the Arbitrum Delayed Inbox address + * @param _inbox New address for the Arbitrum Delayed Inbox + */ + function _setArbitrumInbox(address _inbox) internal { + require(_inbox != address(0), "Arbitrum Inbox cannot be zero"); + inbox = _inbox; + emit ArbitrumInboxUpdated(_inbox); + } + + /** + * @dev Approve token allowance by validating a message signed by the holder, if permit + * fails check for existing allowance before reverting transaction. + * @param _owner Address of the token holder + * @param _spender Address of the approved spender + * @param _value Amount of tokens to approve the spender + * @param _deadline Expiration time of the signed permit (if zero, the permit will never expire, so use with caution) + * @param _v Signature recovery id + * @param _r Signature r value + * @param _s Signature s value + */ + function _permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) internal { + IERC20WithPermit token = IERC20WithPermit(address(graphToken)); + // Try permit() before allowance check to advance nonce if possible + try token.permit(_owner, _spender, _value, _deadline, _v, _r, _s) { + return; + } catch Error(string memory reason) { + // Check for existing allowance before reverting + if (token.allowance(_owner, _spender) >= _value) { + return; + } + + revert(reason); + } + } +} diff --git a/packages/billing/contracts/Governed.sol b/packages/billing/contracts/Governed.sol new file mode 100644 index 000000000..14ec63746 --- /dev/null +++ b/packages/billing/contracts/Governed.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +/** + * @title Graph Governance contract + * @dev Allows a contract to be owned and controlled by the 'governor' + */ +contract Governed { + // -- State -- + + // The address of the governor + address public governor; + // The address of the pending governor + address public pendingGovernor; + + // -- Events -- + + // Emit when the pendingGovernor state variable is updated + event NewPendingOwnership(address indexed from, address indexed to); + // Emit when the governor state variable is updated + event NewOwnership(address indexed from, address indexed to); + + /** + * @dev Check if the caller is the governor. + */ + modifier onlyGovernor() { + require(msg.sender == governor, "Only Governor can call"); + _; + } + + /** + * @dev Initialize the governor with the _initGovernor param. + * @param _initGovernor Governor address + */ + constructor(address _initGovernor) { + require(_initGovernor != address(0), "Governor must not be 0"); + governor = _initGovernor; + } + + /** + * @dev Admin function to begin change of governor. The `_newGovernor` must call + * `acceptOwnership` to finalize the transfer. + * @param _newGovernor Address of new `governor` + */ + function transferOwnership(address _newGovernor) external onlyGovernor { + require(_newGovernor != address(0), "Governor must be set"); + + address oldPendingGovernor = pendingGovernor; + pendingGovernor = _newGovernor; + + emit NewPendingOwnership(oldPendingGovernor, pendingGovernor); + } + + /** + * @dev Admin function for pending governor to accept role and update governor. + * This function must called by the pending governor. + */ + function acceptOwnership() external { + require(pendingGovernor != address(0) && msg.sender == pendingGovernor, "Caller must be pending governor"); + + address oldGovernor = governor; + address oldPendingGovernor = pendingGovernor; + + governor = pendingGovernor; + pendingGovernor = address(0); + + emit NewOwnership(oldGovernor, governor); + emit NewPendingOwnership(oldPendingGovernor, pendingGovernor); + } +} diff --git a/packages/billing/contracts/IBilling.sol b/packages/billing/contracts/IBilling.sol new file mode 100644 index 000000000..b021a1501 --- /dev/null +++ b/packages/billing/contracts/IBilling.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +interface IBilling { + /** + * @dev Set or unset an address as an allowed Collector + * @param _collector Collector address + * @param _enabled True to set the _collector address as a Collector, false to remove it + */ + function setCollector(address _collector, bool _enabled) external; // onlyGovernor + + /** + * @dev Sets the L2 token gateway address + * @param _l2TokenGateway New address for the L2 token gateway + */ + function setL2TokenGateway(address _l2TokenGateway) external; + + /** + * @dev Sets the L1 Billing Connector address + * @param _l1BillingConnector New address for the L1 BillingConnector (without any aliasing!) + */ + function setL1BillingConnector(address _l1BillingConnector) external; + + /** + * @dev Add tokens into the billing contract + * @param _amount Amount of tokens to add + */ + function add(uint256 _amount) external; + + /** + * @dev Add tokens into the billing contract for any user + * @param _to Address that tokens are being added to + * @param _amount Amount of tokens to add + */ + function addTo(address _to, uint256 _amount) external; + + /** + * @dev Receive tokens with a callhook from the Arbitrum GRT bridge + * Expects an `address user` in the encoded _data. + * @param _from Token sender in L1 + * @param _amount Amount of tokens that were transferred + * @param _data ABI-encoded callhook data: contains address that tokens are being added to + */ + function onTokenTransfer( + address _from, + uint256 _amount, + bytes calldata _data + ) external; + + /** + * @dev Remove tokens from the billing contract, from L1 + * This can only be called from the BillingConnector on L1. + * @param _from Address from which the tokens are removed + * @param _to Address to send the tokens + * @param _amount Amount of tokens to remove + */ + function removeFromL1( + address _from, + address _to, + uint256 _amount + ) external; + + /** + * @dev Add tokens into the billing contract in bulk + * Ensure graphToken.approve() is called on the billing contract first + * @param _to Array of addresses where to add tokens + * @param _amount Array of amount of tokens to add to each account + */ + function addToMany(address[] calldata _to, uint256[] calldata _amount) external; + + /** + * @dev Remove tokens from the billing contract + * Tokens will be removed from the sender's balance + * @param _to Address that tokens are being moved to + * @param _amount Amount of tokens to remove + */ + function remove(address _to, uint256 _amount) external; + + /** + * @dev Collector pulls tokens from the billing contract + * @param _user Address that tokens are being pulled from + * @param _amount Amount of tokens to pull + * @param _to Destination to send pulled tokens + */ + function pull( + address _user, + uint256 _amount, + address _to + ) external; + + /** + * @dev Collector pulls tokens from many users in the billing contract + * @param _users Addresses that tokens are being pulled from + * @param _amounts Amounts of tokens to pull from each user + * @param _to Destination to send pulled tokens + */ + function pullMany( + address[] calldata _users, + uint256[] calldata _amounts, + address _to + ) external; +} diff --git a/packages/billing/contracts/IBillingConnector.sol b/packages/billing/contracts/IBillingConnector.sol new file mode 100644 index 000000000..8b5753d6a --- /dev/null +++ b/packages/billing/contracts/IBillingConnector.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +interface IBillingConnector { + /** + * @dev Sets the L1 token gateway address + * @param _l1TokenGateway New address for the L1 token gateway + */ + function setL1TokenGateway(address _l1TokenGateway) external; + + /** + * @dev Sets the L2 Billing address + * @param _l2Billing New address for the L2 Billing contract + */ + function setL2Billing(address _l2Billing) external; + + /** + * @dev Sets the Arbitrum Delayed Inbox address + * @param _inbox New address for the L2 Billing contract + */ + function setArbitrumInbox(address _inbox) external; + + /** + * @dev Add tokens into the billing contract on L2, for any user + * Ensure graphToken.approve() is called for the BillingConnector contract first + * @param _to Address that tokens are being added to + * @param _amount Amount of tokens to add + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + */ + function addToL2( + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable; + + /** + * @dev Remove tokens from the billing contract on L2, sending the tokens + * to an L2 address. Useful when the tokens are in the balance for an address + * that doesn't exist in L2. + * @param _to L2 address to which the tokens will be sent + * @param _amount Amount of tokens to remove + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + */ + function removeOnL2( + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost + ) external payable; + + /** + * @dev Add tokens into the billing contract on L2, for any user, using a signed permit + * @param _user Address of the current owner of the tokens, that will also be the destination in L2 + * @param _amount Amount of tokens to add + * @param _maxGas Max gas for the L2 retryable ticket execution + * @param _gasPriceBid Gas price for the L2 retryable ticket execution + * @param _maxSubmissionCost Max submission price for the L2 retryable ticket + * @param _deadline Expiration time of the signed permit + * @param _v Signature recovery id + * @param _r Signature r value + * @param _s Signature s value + */ + function addToL2WithPermit( + address _user, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + uint256 _maxSubmissionCost, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external payable; + + /** + * @dev Allows the Governor to rescue any ERC20 tokens sent to this contract by accident + * @param _to Destination address to send the tokens + * @param _token Token address of the token that was accidentally sent to the contract + * @param _amount Amount of tokens to pull + */ + function rescueTokens( + address _to, + address _token, + uint256 _amount + ) external; +} diff --git a/packages/billing/contracts/IERC20WithPermit.sol b/packages/billing/contracts/IERC20WithPermit.sol new file mode 100644 index 000000000..d9f158184 --- /dev/null +++ b/packages/billing/contracts/IERC20WithPermit.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.16; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title ERC20 with Permit interface + * @dev This is an ERC20 interface with an additional permit() function + * that allows approving a user to move tokens using a signed permit. + * The GraphToken contract implements this function. + */ +interface IERC20WithPermit is IERC20 { + /** + * @dev Approve token allowance by validating a message signed by the holder. + * @param _owner Address of the token holder + * @param _spender Address of the approved spender + * @param _value Amount of tokens to approve the spender + * @param _deadline Expiration time of the signed permit (if zero, the permit will never expire, so use with caution) + * @param _v Signature recovery id + * @param _r Signature r value + * @param _s Signature s value + */ + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external; +} diff --git a/packages/billing/contracts/Rescuable.sol b/packages/billing/contracts/Rescuable.sol new file mode 100644 index 000000000..9446c022d --- /dev/null +++ b/packages/billing/contracts/Rescuable.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title Rescuable contract + * @dev Allows a contract to have a function to rescue tokens sent by mistake. + * The contract must implement the external rescueTokens function or similar, + * that calls this contract's _rescueTokens. + */ +contract Rescuable { + /** + * @dev Tokens rescued by the permissioned user + */ + event TokensRescued(address indexed to, address indexed token, uint256 amount); + + /** + * @dev Allows a permissioned user to rescue any ERC20 tokens sent to this contract by accident + * @param _to Destination address to send the tokens + * @param _token Token address of the token that was accidentally sent to the contract + * @param _amount Amount of tokens to pull + */ + function _rescueTokens( + address _to, + address _token, + uint256 _amount + ) internal { + require(_to != address(0), "Cannot send to address(0)"); + require(_amount != 0, "Cannot rescue 0 tokens"); + IERC20 token = IERC20(_token); + require(token.transfer(_to, _amount), "Rescue tokens failed"); + emit TokensRescued(_to, _token, _amount); + } +} diff --git a/packages/billing/contracts/arbitrum/AddressAliasHelper.sol b/packages/billing/contracts/arbitrum/AddressAliasHelper.sol new file mode 100644 index 000000000..508f0a014 --- /dev/null +++ b/packages/billing/contracts/arbitrum/AddressAliasHelper.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2019-2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/84e64dee6ee82adbf8ec34fd4b86c207a61d9007/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.8.16 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.8.16; + +library AddressAliasHelper { + uint160 constant offset = uint160(0x1111000000000000000000000000000000001111); + + /// @notice Utility function that converts the address in the L1 that submitted a tx to + /// the inbox to the msg.sender viewed in the L2 + /// @param l1Address the address in the L1 that triggered the tx to L2 + /// @return l2Address L2 address as viewed in msg.sender + function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) { + l2Address = address(uint160(l1Address) + offset); + } + + /// @notice Utility function that converts the msg.sender viewed in the L2 to the + /// address in the L1 that submitted a tx to the inbox + /// @param l2Address L2 address as viewed in msg.sender + /// @return l1Address the address in the L1 that triggered the tx to L2 + function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) { + l1Address = address(uint160(l2Address) - offset); + } +} diff --git a/packages/billing/contracts/arbitrum/IBridge.sol b/packages/billing/contracts/arbitrum/IBridge.sol new file mode 100644 index 000000000..122fd0076 --- /dev/null +++ b/packages/billing/contracts/arbitrum/IBridge.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.8.16 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.8.16; + +interface IBridge { + event MessageDelivered( + uint256 indexed messageIndex, + bytes32 indexed beforeInboxAcc, + address inbox, + uint8 kind, + address sender, + bytes32 messageDataHash + ); + + event BridgeCallTriggered(address indexed outbox, address indexed destAddr, uint256 amount, bytes data); + + event InboxToggle(address indexed inbox, bool enabled); + + event OutboxToggle(address indexed outbox, bool enabled); + + function deliverMessageToInbox( + uint8 kind, + address sender, + bytes32 messageDataHash + ) external payable returns (uint256); + + function executeCall( + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (bool success, bytes memory returnData); + + // These are only callable by the admin + function setInbox(address inbox, bool enabled) external; + + function setOutbox(address inbox, bool enabled) external; + + // View functions + + function activeOutbox() external view returns (address); + + function allowedInboxes(address inbox) external view returns (bool); + + function allowedOutboxes(address outbox) external view returns (bool); + + function inboxAccs(uint256 index) external view returns (bytes32); + + function messageCount() external view returns (uint256); +} diff --git a/packages/billing/contracts/arbitrum/IInbox.sol b/packages/billing/contracts/arbitrum/IInbox.sol new file mode 100644 index 000000000..396c1f28d --- /dev/null +++ b/packages/billing/contracts/arbitrum/IInbox.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.8.16 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.8.16; + +import "./IBridge.sol"; +import "./IMessageProvider.sol"; + +interface IInbox is IMessageProvider { + function sendL2Message(bytes calldata messageData) external returns (uint256); + + function sendUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256); + + function sendContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256); + + function sendL1FundedUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + bytes calldata data + ) external payable returns (uint256); + + function sendL1FundedContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + bytes calldata data + ) external payable returns (uint256); + + function createRetryableTicket( + address destAddr, + uint256 arbTxCallValue, + uint256 maxSubmissionCost, + address submissionRefundAddress, + address valueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256); + + function depositEth(uint256 maxSubmissionCost) external payable returns (uint256); + + function bridge() external view returns (IBridge); + + function pauseCreateRetryables() external; + + function unpauseCreateRetryables() external; + + function startRewriteAddress() external; + + function stopRewriteAddress() external; +} diff --git a/packages/billing/contracts/arbitrum/IMessageProvider.sol b/packages/billing/contracts/arbitrum/IMessageProvider.sol new file mode 100644 index 000000000..ee73918be --- /dev/null +++ b/packages/billing/contracts/arbitrum/IMessageProvider.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.8.16 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.8.16; + +interface IMessageProvider { + event InboxMessageDelivered(uint256 indexed messageNum, bytes data); + + event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); +} diff --git a/packages/billing/contracts/arbitrum/IOutbox.sol b/packages/billing/contracts/arbitrum/IOutbox.sol new file mode 100644 index 000000000..9a2a90d49 --- /dev/null +++ b/packages/billing/contracts/arbitrum/IOutbox.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-eth + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.8.16 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.8.16; + +interface IOutbox { + event OutboxEntryCreated( + uint256 indexed batchNum, + uint256 outboxEntryIndex, + bytes32 outputRoot, + uint256 numInBatch + ); + event OutBoxTransactionExecuted( + address indexed destAddr, + address indexed l2Sender, + uint256 indexed outboxEntryIndex, + uint256 transactionIndex + ); + + function l2ToL1Sender() external view returns (address); + + function l2ToL1Block() external view returns (uint256); + + function l2ToL1EthBlock() external view returns (uint256); + + function l2ToL1Timestamp() external view returns (uint256); + + function l2ToL1BatchNum() external view returns (uint256); + + function l2ToL1OutputId() external view returns (bytes32); + + function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) external; + + function outboxEntryExists(uint256 batchNum) external view returns (bool); +} diff --git a/packages/billing/contracts/arbitrum/L1ArbitrumMessenger.sol b/packages/billing/contracts/arbitrum/L1ArbitrumMessenger.sol new file mode 100644 index 000000000..c68d27193 --- /dev/null +++ b/packages/billing/contracts/arbitrum/L1ArbitrumMessenger.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2020, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Originally copied from: + * https://github.com/OffchainLabs/arbitrum/tree/e3a6307ad8a2dc2cad35728a2a9908cfd8dd8ef9/packages/arb-bridge-peripherals + * + * MODIFIED from Offchain Labs' implementation: + * - Changed solidity version to 0.8.16 (pablo@edgeandnode.com) + * + */ + +pragma solidity ^0.8.16; + +import "./IInbox.sol"; +import "./IOutbox.sol"; + +/// @notice L1 utility contract to assist with L1 <=> L2 interactions +/// @dev this is an abstract contract instead of library so the functions can be easily overriden when testing +abstract contract L1ArbitrumMessenger { + event TxToL2(address indexed _from, address indexed _to, uint256 indexed _seqNum, bytes _data); + + struct L2GasParams { + uint256 _maxSubmissionCost; + uint256 _maxGas; + uint256 _gasPriceBid; + } + + function sendTxToL2( + address _inbox, + address _to, + address _user, + uint256 _l1CallValue, + uint256 _l2CallValue, + L2GasParams memory _l2GasParams, + bytes memory _data + ) internal virtual returns (uint256) { + // alternative function entry point when struggling with the stack size + return + sendTxToL2( + _inbox, + _to, + _user, + _l1CallValue, + _l2CallValue, + _l2GasParams._maxSubmissionCost, + _l2GasParams._maxGas, + _l2GasParams._gasPriceBid, + _data + ); + } + + function sendTxToL2( + address _inbox, + address _to, + address _user, + uint256 _l1CallValue, + uint256 _l2CallValue, + uint256 _maxSubmissionCost, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes memory _data + ) internal virtual returns (uint256) { + uint256 seqNum = IInbox(_inbox).createRetryableTicket{ value: _l1CallValue }( + _to, + _l2CallValue, + _maxSubmissionCost, + _user, + _user, + _maxGas, + _gasPriceBid, + _data + ); + emit TxToL2(_user, _to, seqNum, _data); + return seqNum; + } + + function getBridge(address _inbox) internal view virtual returns (IBridge) { + return IInbox(_inbox).bridge(); + } + + /// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies + function getL2ToL1Sender(address _inbox) internal view virtual returns (address) { + IOutbox outbox = IOutbox(getBridge(_inbox).activeOutbox()); + address l2ToL1Sender = outbox.l2ToL1Sender(); + + require(l2ToL1Sender != address(0), "NO_SENDER"); + return l2ToL1Sender; + } +} diff --git a/packages/billing/contracts/arbitrum/README.md b/packages/billing/contracts/arbitrum/README.md new file mode 100644 index 000000000..f2d6367b2 --- /dev/null +++ b/packages/billing/contracts/arbitrum/README.md @@ -0,0 +1,5 @@ +# Arbitrum contracts + +These contracts have been copied from the [Arbitrum repo](https://github.com/OffchainLabs/arbitrum). + +They are also available as part of the npm packages [arb-bridge-eth](https://www.npmjs.com/package/arb-bridge-eth) and [arb-bridge-peripherals](https://www.npmjs.com/package/arb-bridge-peripherals). The reason for copying them rather than installing those packages is the contracts only support Solidity `^0.6.11`, so we had to change the version to `^0.8.16` for it to be compatible with our other contracts. diff --git a/packages/billing/contracts/tests/BridgeMock.sol b/packages/billing/contracts/tests/BridgeMock.sol new file mode 100644 index 000000000..3cafae82a --- /dev/null +++ b/packages/billing/contracts/tests/BridgeMock.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.16; + +import "../arbitrum/IBridge.sol"; + +/** + * @title Arbitrum Bridge mock contract + * @dev This contract implements Arbitrum's IBridge interface for testing purposes + */ +contract BridgeMock is IBridge { + // Address of the (mock) Arbitrum Inbox + address public inbox; + // Address of the (mock) Arbitrum Outbox + address public outbox; + // Index of the next message on the inbox messages array + uint256 public messageIndex; + // Inbox messages array + bytes32[] public override inboxAccs; + + /** + * @dev Deliver a message to the inbox. The encoded message will be + * added to the inbox array, and messageIndex will be incremented. + * @param _kind Type of the message + * @param _sender Address that is sending the message + * @param _messageDataHash keccak256 hash of the message data + * @return The next index for the inbox array + */ + function deliverMessageToInbox( + uint8 _kind, + address _sender, + bytes32 _messageDataHash + ) external payable override returns (uint256) { + messageIndex = messageIndex + 1; + inboxAccs.push(keccak256(abi.encodePacked(inbox, _kind, _sender, _messageDataHash))); + emit MessageDelivered(messageIndex, inboxAccs[messageIndex - 1], msg.sender, _kind, _sender, _messageDataHash); + return messageIndex; + } + + /** + * @dev Executes an L1 function call incoing from L2. This can only be called + * by the Outbox. + * @param _destAddr Contract to call + * @param _amount ETH value to send + * @param _data Calldata for the function call + * @return True if the call was successful, false otherwise + * @return Return data from the call + */ + function executeCall( + address _destAddr, + uint256 _amount, + bytes calldata _data + ) external override returns (bool, bytes memory) { + require(outbox == msg.sender, "NOT_FROM_OUTBOX"); + bool success; + bytes memory returnData; + + // solhint-disable-next-line avoid-low-level-calls + (success, returnData) = _destAddr.call{ value: _amount }(_data); + emit BridgeCallTriggered(msg.sender, _destAddr, _amount, _data); + return (success, returnData); + } + + /** + * @dev Set the address of the inbox. Anyone can call this, because it's a mock. + * @param _inbox Address of the inbox + * @param _enabled Enable the inbox (ignored) + */ + function setInbox(address _inbox, bool _enabled) external override { + inbox = _inbox; + emit InboxToggle(inbox, _enabled); + } + + /** + * @dev Set the address of the outbox. Anyone can call this, because it's a mock. + * @param _outbox Address of the outbox + * @param _enabled Enable the outbox (ignored) + */ + function setOutbox(address _outbox, bool _enabled) external override { + outbox = _outbox; + emit OutboxToggle(outbox, _enabled); + } + + // View functions + + /** + * @dev Getter for the active outbox (in this case there's only one) + */ + function activeOutbox() external view override returns (address) { + return outbox; + } + + /** + * @dev Getter for whether an address is an allowed inbox (in this case there's only one) + * @param _inbox Address to check + * @return True if the address is the allowed inbox, false otherwise + */ + function allowedInboxes(address _inbox) external view override returns (bool) { + return _inbox == inbox; + } + + /** + * @dev Getter for whether an address is an allowed outbox (in this case there's only one) + * @param _outbox Address to check + * @return True if the address is the allowed outbox, false otherwise + */ + function allowedOutboxes(address _outbox) external view override returns (bool) { + return _outbox == outbox; + } + + /** + * @dev Getter for the count of messages in the inboxAccs + * @return Number of messages in inboxAccs + */ + function messageCount() external view override returns (uint256) { + return inboxAccs.length; + } +} diff --git a/packages/billing/contracts/tests/InboxMock.sol b/packages/billing/contracts/tests/InboxMock.sol new file mode 100644 index 000000000..96f7ee396 --- /dev/null +++ b/packages/billing/contracts/tests/InboxMock.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.16; + +import "../arbitrum/IInbox.sol"; +import "../arbitrum/AddressAliasHelper.sol"; + +/** + * @title Arbitrum Inbox mock contract + * @dev This contract implements (a subset of) Arbitrum's IInbox interface for testing purposes + */ +contract InboxMock is IInbox { + // Type indicator for a standard L2 message + uint8 internal constant L2_MSG = 3; + // Type indicator for a retryable ticket message + // solhint-disable-next-line const-name-snakecase + uint8 internal constant L1MessageType_submitRetryableTx = 9; + // Address of the Bridge (mock) contract + IBridge public override bridge; + + /** + * @dev Send a message to L2 (by delivering it to the Bridge) + * @param _messageData Encoded data to send in the message + * @return message number returned by the inbox + */ + function sendL2Message(bytes calldata _messageData) external override returns (uint256) { + uint256 msgNum = deliverToBridge(L2_MSG, msg.sender, keccak256(_messageData)); + emit InboxMessageDelivered(msgNum, _messageData); + return msgNum; + } + + /** + * @dev Set the address of the (mock) bridge + * @param _bridge Address of the bridge + */ + function setBridge(address _bridge) external { + bridge = IBridge(_bridge); + } + + /** + * @dev Unimplemented in this mock + */ + function sendUnsignedTransaction( + uint256, + uint256, + uint256, + address, + uint256, + bytes calldata + ) external pure override returns (uint256) { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function sendContractTransaction( + uint256, + uint256, + address, + uint256, + bytes calldata + ) external pure override returns (uint256) { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function sendL1FundedUnsignedTransaction( + uint256, + uint256, + uint256, + address, + bytes calldata + ) external payable override returns (uint256) { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function sendL1FundedContractTransaction( + uint256, + uint256, + address, + bytes calldata + ) external payable override returns (uint256) { + revert("Unimplemented"); + } + + /** + * @dev Creates a retryable ticket for an L2 transaction + * @param _destAddr Address of the contract to call in L2 + * @param _arbTxCallValue Callvalue to use in the L2 transaction + * @param _maxSubmissionCost Max cost of submitting the ticket, in Wei + * @param _submissionRefundAddress L2 address to refund for any remaining value from the submission cost + * @param _valueRefundAddress L2 address to refund if the ticket times out or gets cancelled + * @param _maxGas Max gas for the L2 transcation + * @param _gasPriceBid Gas price bid on L2 + * @param _data Encoded calldata for the L2 transaction (including function selector) + * @return message number returned by the bridge + */ + function createRetryableTicket( + address _destAddr, + uint256 _arbTxCallValue, + uint256 _maxSubmissionCost, + address _submissionRefundAddress, + address _valueRefundAddress, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable override returns (uint256) { + _submissionRefundAddress = AddressAliasHelper.applyL1ToL2Alias(_submissionRefundAddress); + _valueRefundAddress = AddressAliasHelper.applyL1ToL2Alias(_valueRefundAddress); + return + _deliverMessage( + L1MessageType_submitRetryableTx, + msg.sender, + abi.encodePacked( + uint256(uint160(bytes20(_destAddr))), + _arbTxCallValue, + msg.value, + _maxSubmissionCost, + uint256(uint160(bytes20(_submissionRefundAddress))), + uint256(uint160(bytes20(_valueRefundAddress))), + _maxGas, + _gasPriceBid, + _data.length, + _data + ) + ); + } + + function depositEth(uint256) external payable override returns (uint256) { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function pauseCreateRetryables() external pure override { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function unpauseCreateRetryables() external pure override { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function startRewriteAddress() external pure override { + revert("Unimplemented"); + } + + /** + * @dev Unimplemented in this mock + */ + function stopRewriteAddress() external pure override { + revert("Unimplemented"); + } + + /** + * @dev Deliver a message to the bridge + * @param _kind Type of the message + * @param _sender Address that is sending the message + * @param _messageData Encoded message data + * @return Message number returned by the bridge + */ + function _deliverMessage( + uint8 _kind, + address _sender, + bytes memory _messageData + ) internal returns (uint256) { + uint256 msgNum = deliverToBridge(_kind, _sender, keccak256(_messageData)); + emit InboxMessageDelivered(msgNum, _messageData); + return msgNum; + } + + /** + * @dev Deliver a message to the bridge + * @param _kind Type of the message + * @param _sender Address that is sending the message + * @param _messageDataHash keccak256 hash of the encoded message data + * @return Message number returned by the bridge + */ + function deliverToBridge( + uint8 _kind, + address _sender, + bytes32 _messageDataHash + ) internal returns (uint256) { + return bridge.deliverMessageToInbox{ value: msg.value }(_kind, _sender, _messageDataHash); + } +} diff --git a/packages/billing/contracts/tests/L1TokenGatewayMock.sol b/packages/billing/contracts/tests/L1TokenGatewayMock.sol new file mode 100644 index 000000000..462a6aa6e --- /dev/null +++ b/packages/billing/contracts/tests/L1TokenGatewayMock.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ITokenGateway } from "arb-bridge-peripherals/contracts/tokenbridge/libraries/gateway/ITokenGateway.sol"; + +/** + * @title L1 Token Gateway mock contract + * @dev Used for testing purposes, DO NOT USE IN PRODUCTION + */ +contract L1TokenGatewayMock is Ownable { + uint256 public nextSeqNum; + + event FakeTxToL2( + address from, + uint256 value, + uint256 maxGas, + uint256 gasPriceBid, + uint256 maxSubmissionCost, + bytes outboundCalldata + ); + // Emitted when an outbound transfer is initiated, i.e. tokens are deposited from L1 to L2 + event DepositInitiated( + address l1Token, + address indexed from, + address indexed to, + uint256 indexed sequenceNumber, + uint256 amount + ); + + /** + * @dev L1 Token Gateway Contract Constructor. + */ + constructor() {} + + /** + * @notice Creates and sends a fake retryable ticket to transfer GRT to L2. + * This mock will actually just emit an event with parameters equivalent to what the real L1GraphTokenGateway + * would send to L2. + * @param _l1Token L1 Address of the GRT contract (needed for compatibility with Arbitrum Gateway Router) + * @param _to Recipient address on L2 + * @param _amount Amount of tokens to tranfer + * @param _maxGas Gas limit for L2 execution of the ticket + * @param _gasPriceBid Price per gas on L2 + * @param _data Encoded maxSubmissionCost and sender address along with additional calldata + * @return Sequence number of the retryable ticket created by Inbox (always ) + */ + function outboundTransfer( + address _l1Token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data + ) external payable returns (bytes memory) { + require(_amount > 0, "INVALID_ZERO_AMOUNT"); + require(_to != address(0), "INVALID_DESTINATION"); + + // nested scopes to avoid stack too deep errors + address from; + uint256 seqNum = nextSeqNum; + nextSeqNum += 1; + { + uint256 maxSubmissionCost; + bytes memory outboundCalldata; + { + bytes memory extraData; + (from, maxSubmissionCost, extraData) = parseOutboundData(_data); + require(maxSubmissionCost > 0, "NO_SUBMISSION_COST"); + + { + // makes sure only sufficient ETH is supplied as required for successful redemption on L2 + // if a user does not desire immediate redemption they should provide + // a msg.value of AT LEAST maxSubmissionCost + uint256 expectedEth = maxSubmissionCost + (_maxGas * _gasPriceBid); + require(msg.value >= expectedEth, "WRONG_ETH_VALUE"); + } + outboundCalldata = getOutboundCalldata(_l1Token, from, _to, _amount, extraData); + } + { + // transfer tokens to escrow + IERC20(_l1Token).transferFrom(from, address(this), _amount); + + emit FakeTxToL2(from, msg.value, _maxGas, _gasPriceBid, maxSubmissionCost, outboundCalldata); + } + } + emit DepositInitiated(_l1Token, from, _to, seqNum, _amount); + + return abi.encode(seqNum); + } + + /** + * @notice (Mock) Receives withdrawn tokens from L2 + * Actually does nothing, just keeping it here as its useful to define the expected + * calldata for the outgoing transfer in tests. + * @param _l1Token L1 Address of the GRT contract (needed for compatibility with Arbitrum Gateway Router) + * @param _from Address of the sender + * @param _to Recepient address on L1 + * @param _amount Amount of tokens transferred + * @param _data Additional calldata + */ + function finalizeInboundTransfer( + address _l1Token, + address _from, + address _to, + uint256 _amount, + bytes calldata _data + ) external payable {} + + /** + * @notice Decodes calldata required for migration of tokens + * @dev Data must include maxSubmissionCost, extraData can be left empty. When the router + * sends an outbound message, data also contains the from address. + * @param _data encoded callhook data + * @return Sender of the tx + * @return Base ether value required to keep retryable ticket alive + * @return Additional data sent to L2 + */ + function parseOutboundData(bytes memory _data) + private + view + returns ( + address, + uint256, + bytes memory + ) + { + address from; + uint256 maxSubmissionCost; + bytes memory extraData; + from = msg.sender; + extraData = _data; + // User-encoded data contains the max retryable ticket submission cost + // and additional L2 calldata + (maxSubmissionCost, extraData) = abi.decode(extraData, (uint256, bytes)); + return (from, maxSubmissionCost, extraData); + } + + /** + * @notice Creates calldata required to create a retryable ticket + * @dev encodes the target function with its params which + * will be called on L2 when the retryable ticket is redeemed + * @param _l1Token Address of the Graph token contract on L1 + * @param _from Address on L1 from which we're transferring tokens + * @param _to Address on L2 to which we're transferring tokens + * @param _amount Amount of GRT to transfer + * @param _data Additional call data for the L2 transaction, which must be empty unless the caller is whitelisted + * @return Encoded calldata (including function selector) for the L2 transaction + */ + function getOutboundCalldata( + address _l1Token, + address _from, + address _to, + uint256 _amount, + bytes memory _data + ) public pure returns (bytes memory) { + bytes memory emptyBytes; + + return + abi.encodeWithSelector( + ITokenGateway.finalizeInboundTransfer.selector, + _l1Token, + _from, + _to, + _amount, + abi.encode(emptyBytes, _data) + ); + } +} diff --git a/packages/billing/contracts/tests/Token.sol b/packages/billing/contracts/tests/Token.sol new file mode 100644 index 000000000..262bc10b8 --- /dev/null +++ b/packages/billing/contracts/tests/Token.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +/** + * @title Token contract + * @dev Used for testing purposes + * + */ +contract Token is ERC20, Ownable { + // -- EIP712 -- + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-domainseparator + + bytes32 private constant DOMAIN_TYPE_HASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"); + bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Token"); + bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); + bytes32 private constant DOMAIN_SALT = 0x372eb1ffe347bfa68969a6e8193fe5f811f0ac923bde07eb35d7e74347ba031a; // Randomly generated salt + bytes32 private constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + // -- State -- + + // solhint-disable-next-line var-name-mixedcase + bytes32 private DOMAIN_SEPARATOR; + mapping(address => uint256) public nonces; + + /** + * @dev Token Contract Constructor. + * @param _initialSupply Initial supply of GRT + */ + constructor(uint256 _initialSupply) ERC20("Graph Token", "GRT") { + _mint(msg.sender, _initialSupply); + + // EIP-712 domain separator + DOMAIN_SEPARATOR = keccak256( + abi.encode( + DOMAIN_TYPE_HASH, + DOMAIN_NAME_HASH, + DOMAIN_VERSION_HASH, + _getChainID(), + address(this), + DOMAIN_SALT + ) + ); + } + + /** + * @dev Mint more tokens (owner onlu) + * @param _to Destination to send the new tokens + * @param _amount Number of tokens to mint + */ + function mint(address _to, uint256 _amount) external onlyOwner { + _mint(_to, _amount); + } + + /** + * @dev Approve token allowance by validating a message signed by the holder. + * @param _owner Address of the token holder + * @param _spender Address of the approved spender + * @param _value Amount of tokens to approve the spender + * @param _deadline Expiration time of the signed permit (if zero, the permit will never expire, so use with caution) + * @param _v Signature recovery id + * @param _r Signature r value + * @param _s Signature s value + */ + function permit( + address _owner, + address _spender, + uint256 _value, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, nonces[_owner], _deadline)) + ) + ); + + address recoveredAddress = ECDSA.recover(digest, _v, _r, _s); + require(_owner == recoveredAddress, "GRT: invalid permit"); + require(_deadline == 0 || block.timestamp <= _deadline, "GRT: expired permit"); + + nonces[_owner] = nonces[_owner] + 1; + _approve(_owner, _spender, _value); + } + + /** + * @dev Get the running network chain ID. + * @return The chain ID + */ + function _getChainID() private view returns (uint256) { + uint256 id; + // solhint-disable-next-line no-inline-assembly + assembly { + id := chainid() + } + return id; + } +} diff --git a/packages/billing/hardhat.config.ts b/packages/billing/hardhat.config.ts new file mode 100644 index 000000000..b12663049 --- /dev/null +++ b/packages/billing/hardhat.config.ts @@ -0,0 +1,150 @@ +import path from 'path' +import fs from 'fs' +import * as dotenv from 'dotenv' + +import 'hardhat/types/runtime' + +dotenv.config() + +// Plugins + +import '@nomiclabs/hardhat-ethers' +import '@nomiclabs/hardhat-etherscan' +import '@typechain/hardhat' +import 'hardhat-abi-exporter' +import '@nomicfoundation/hardhat-chai-matchers' +import 'solidity-coverage' + +const SKIP_LOAD = process.env.SKIP_LOAD === 'true' + +if (!SKIP_LOAD) { + require('./tasks/extendContracts') + ;['deployment', 'ops'].forEach((folder) => { + const tasksPath = path.join(__dirname, 'tasks', folder) + fs.readdirSync(tasksPath) + .filter((pth) => pth.includes('.ts')) + .forEach((task) => { + require(`${tasksPath}/${task}`) + }) + }) +} + +// Networks + +interface NetworkConfig { + network: string + chainId: number + url?: string + gas?: number | 'auto' + gasPrice?: number | 'auto' +} + +const networkConfigs: NetworkConfig[] = [ + { network: 'mainnet', chainId: 1 }, + { network: 'goerli', chainId: 5 }, + { network: 'sepolia', chainId: 11155111 }, + { network: 'arbitrum-one', chainId: 42161, url: 'https://arb1.arbitrum.io/rpc' }, + { + network: 'arbitrum-goerli', + chainId: 421613, + url: 'https://goerli-rollup.arbitrum.io/rpc', + }, + { + network: 'arbitrum-sepolia', + chainId: 421614, + url: 'https://sepolia-rollup.arbitrum.io/rpcblock', + }, + { network: 'polygon', chainId: 137, url: process.env.POLYGON_RPC_URL }, + { network: 'mumbai', chainId: 80001, url: process.env.POLYGON_MUMBAI_RPC_URL }, +] + +function getAccountsKeys() { + if (process.env.MNEMONIC) return { mnemonic: process.env.MNEMONIC } + if (process.env.PRIVATE_KEY) return [process.env.PRIVATE_KEY] + return 'remote' +} + +function getProviderURL(network: string) { + return `https://${network}.infura.io/v3/${process.env.INFURA_KEY}` +} + +function setupNetworkConfig(config) { + for (const netConfig of networkConfigs) { + config.networks[netConfig.network] = { + chainId: netConfig.chainId, + url: netConfig.url ? netConfig.url : getProviderURL(netConfig.network), + gas: netConfig.gas || 'auto', + gasPrice: netConfig.gasPrice || 'auto', + accounts: getAccountsKeys(), + } + } +} + +// Config + +const config = { + paths: { + sources: './contracts', + tests: './test', + artifacts: './build/artifacts', + }, + solidity: { + version: '0.8.16', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + defaultNetwork: 'hardhat', + networks: { + hardhat: { + chainId: 1337, + loggingEnabled: false, + gas: 11000000, + gasPrice: 'auto', + blockGasLimit: 12000000, + accounts: { + mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect', + }, + }, + ganache: { + chainId: 1337, + url: 'http://localhost:8545', + }, + }, + etherscan: { + apiKey: { + mainnet: process.env.ETHERSCAN_API_KEY, + goerli: process.env.ETHERSCAN_API_KEY, + sepolia: process.env.ETHERSCAN_API_KEY, + polygonMumbai: process.env.POLYGONSCAN_API_KEY, + arbitrumOne: process.env.ARBISCAN_API_KEY, + arbitrumGoerli: process.env.ARBISCAN_API_KEY, + arbitrumSepolia: process.env.ARBISCAN_API_KEY, + }, + customChains: [ + { + network: 'arbitrumSepolia', + chainId: 421614, + urls: { + apiURL: 'https://api-sepolia.arbiscan.io/api', + browserURL: 'https://sepolia.arbiscan.io', + }, + }, + ], + }, + abiExporter: { + path: './build/abis', + clear: true, + }, + typechain: { + outDir: 'build/types', + target: 'ethers-v5', + }, +} + +setupNetworkConfig(config) + +export default config diff --git a/packages/billing/package.json b/packages/billing/package.json new file mode 100644 index 000000000..c6536dbff --- /dev/null +++ b/packages/billing/package.json @@ -0,0 +1,83 @@ +{ + "name": "graph-studio-billing-contracts", + "version": "1.1.0", + "description": "Graph Studio Billing Contracts", + "main": "index.js", + "scripts": { + "prepare": "husky install", + "prepublishOnly": "scripts/prepublish", + "build": "SKIP_LOAD=true scripts/build", + "clean": "rm -rf build/ cache/ dist/", + "clean:all": "rm -rf node_modules && yarn clean", + "compile": "hardhat compile", + "deploy:all": "scripts/deployAll", + "test": "scripts/test", + "test:upgrade": "scripts/upgradeForkTest", + "test:gas": "RUN_EVM=true REPORT_GAS=true scripts/test", + "test:coverage": "scripts/coverage", + "lint": "yarn run lint:ts && yarn run lint:sol", + "lint:fix": "yarn run lint:ts:fix && yarn run lint:sol:fix", + "lint:ts": "eslint '*/**/*.{js,ts}'", + "lint:ts:fix": "yarn prettier:ts && eslint '*/**/*.{js,ts}' --fix", + "lint:sol": "solhint 'contracts/**/*.sol'", + "lint:sol:fix": "yarn prettier:sol && solhint --fix 'contracts/**/*.sol'", + "prettier": "yarn prettier:ts && yarn prettier:sol", + "prettier:ts": "prettier --write '**/*.{js,ts,json}'", + "prettier:sol": "prettier --write 'contracts/**/*.sol'", + "security": "scripts/security", + "flatten": "scripts/flatten", + "typechain": "hardhat typechain", + "verify": "hardhat verify" + }, + "files": [ + "dist/**/*", + "README.md", + "LICENSE" + ], + "author": "", + "license": "MIT", + "devDependencies": { + "@commitlint/cli": "^17.1.2", + "@commitlint/config-conventional": "^17.1.0", + "@graphprotocol/common-ts": "^1.8.6", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", + "@nomiclabs/hardhat-ethers": "*", + "@nomiclabs/hardhat-etherscan": "^3.1.2", + "@openzeppelin/contracts": "^4.3.0", + "@typechain/ethers-v5": "^10.1.0", + "@typechain/hardhat": "^6.1.2", + "@types/mocha": "^9.1.1", + "@types/node": "^18.0.5", + "@typescript-eslint/eslint-plugin": "^5.30.6", + "@typescript-eslint/parser": "^5.30.6", + "arb-bridge-peripherals": "^1.0.11", + "axios": "^0.21.1", + "chai": "^4.2.0", + "consola": "^2.15.3", + "dotenv": "^10.0.0", + "eslint": "^8.19.0", + "eslint-config-prettier": "^8.3.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-mocha-no-only": "^1.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.0.0", + "eslint-plugin-standard": "^5.0.0", + "ethers": "^5.6.0", + "hardhat": "^2.10.1", + "hardhat-abi-exporter": "^2.2.1", + "husky": "^8.0.1", + "inquirer": "^8.1.2", + "lint-staged": "^13.0.3", + "prettier": "^2.1.1", + "prettier-plugin-solidity": "^1.0.0-alpha.56", + "solhint": "^3.3.7", + "solhint-plugin-prettier": "^0.0.5", + "solidity-coverage": "^0.8.2", + "ts-node": "^10.0.0", + "typechain": "^8.1.0", + "typescript": "^4.0.2" + }, + "dependencies": {} +} diff --git a/packages/billing/scripts/build b/packages/billing/scripts/build new file mode 100755 index 000000000..a82a79aae --- /dev/null +++ b/packages/billing/scripts/build @@ -0,0 +1,6 @@ +#!/bin/bash + +set -eo pipefail + +yarn run compile +yarn run flatten \ No newline at end of file diff --git a/packages/billing/scripts/coverage b/packages/billing/scripts/coverage new file mode 100755 index 000000000..dd120cba9 --- /dev/null +++ b/packages/billing/scripts/coverage @@ -0,0 +1,7 @@ +#!/bin/bash + +set -eo pipefail + +yarn run compile +yarn run typechain +npx hardhat coverage $@ diff --git a/packages/billing/scripts/deployAll b/packages/billing/scripts/deployAll new file mode 100755 index 000000000..11ba83a7f --- /dev/null +++ b/packages/billing/scripts/deployAll @@ -0,0 +1,177 @@ +#!/bin/bash + +set -eo pipefail + +yarn clean:all +yarn +yarn build + +source .env + +function error_exit() { + echo $1 + exit 1 +} + +if [[ -z "$L1_MNEMONIC" ]]; then + error_exit "Please set an L1_MNEMONIC in .env" +fi + +if [[ -z "$L2_MNEMONIC" ]]; then + error_exit "Please set an L2_MNEMONIC in .env" +fi + +if [[ "$L1_CHAINID" -eq "1" ]]; then + L1_NETWORK="mainnet" +elif [[ "$L1_CHAINID" -eq "5" ]]; then + L1_NETWORK="goerli" +elif [[ "$L1_CHAINID" -eq "11155111" ]]; then + L1_NETWORK="sepolia" +else + error_exit "Please set a valid L1_CHAINID in .env" +fi + +if [[ "$L2_CHAINID" -eq "42161" ]]; then + if [[ "$L1_CHAINID" -ne "1" ]]; then + error_exit "Should connect Arbitrum One to Mainnet!" + fi + L2_NETWORK="arbitrum-one" +elif [[ "$L2_CHAINID" -eq "421613" ]]; then + if [[ "$L1_CHAINID" -ne "5" ]]; then + error_exit "Should connect Arbitrum Goerli to Goerli!" + fi + L2_NETWORK="arbitrum-goerli" +elif [[ "$L2_CHAINID" -eq "421614" ]]; then + if [[ "$L1_CHAINID" -ne "11155111" ]]; then + error_exit "Should connect Arbitrum Sepolia to Sepolia!" + fi + L2_NETWORK="arbitrum-sepolia" +else + error_exit "Please set a valid L2_CHAINID in .env" +fi + +ADDRESS_BOOK=${ADDRESS_BOOK:-"addresses.json"} + +echo "Using address book: $ADDRESS_BOOK" + +collector=$(jq -r ".[\"$L2_CHAINID\"].collector" $ADDRESS_BOOK || echo "null") +if [[ "$collector" = "null" ]]; then + error_exit "Please set a collector for chainID $L2_CHAINID in $ADDRESS_BOOK" +fi + +L2GraphToken=$(jq -r ".[\"$L2_CHAINID\"].L2GraphToken" $ADDRESS_BOOK || echo "null") +if [[ "$L2GraphToken" = "null" ]]; then + error_exit "Please set an L2GraphToken for chainID $L2_CHAINID in $ADDRESS_BOOK" +fi + +l2Governor=$(jq -r ".[\"$L2_CHAINID\"].governor" $ADDRESS_BOOK || echo "null") +if [[ "$l2Governor" = "null" ]]; then + error_exit "Please set a governor for chainID $L2_CHAINID in $ADDRESS_BOOK" +fi + +L2GraphTokenGateway=$(jq -r ".[\"$L2_CHAINID\"].L2GraphTokenGateway" $ADDRESS_BOOK || echo "null") +if [[ "$L2GraphTokenGateway" = "null" ]]; then + error_exit "Please set an L2GraphTokenGateway for chainID $L2_CHAINID in $ADDRESS_BOOK" +fi + +export MNEMONIC="$L2_MNEMONIC" +l2Deployer=$(npx hardhat print-account --network $L2_NETWORK --num 0) + +echo "Using L2 deployer $l2Deployer" + +echo "Deploying Billing:" +npx hardhat deploy-billing --network $L2_NETWORK \ + --address-book $ADDRESS_BOOK \ + --collector $collector \ + --token $L2GraphToken \ + --governor $l2Deployer \ + --tokengateway $L2GraphTokenGateway + +Billing=$(jq -r ".[\"$L2_CHAINID\"].Billing" $ADDRESS_BOOK || echo "null") +if [[ "$Billing" = "null" ]]; then + error_exit "Could not get new Billing address from $ADDRESS_BOOK" +else + echo "Billing deployed to $Billing" +fi + +function verify_billing() { + npx hardhat verify --network $L2_NETWORK \ + $Billing \ + $collector \ + $L2GraphToken \ + $l2Deployer \ + $L2GraphTokenGateway +} + +echo "Verifying Billing:" +if ! verify_billing; then + echo "Retrying in 60s..." + sleep 60 # Give arbiscan some time to catch up... + verify_billing +fi + +GraphToken=$(jq -r ".[\"$L1_CHAINID\"].GraphToken" $ADDRESS_BOOK || echo "null") +if [[ "$GraphToken" = "null" ]]; then + error_exit "Please set a GraphToken for chainID $L1_CHAINID in $ADDRESS_BOOK" +fi + +l1Governor=$(jq -r ".[\"$L1_CHAINID\"].governor" $ADDRESS_BOOK || echo "null") +if [[ "$l1Governor" = "null" ]]; then + error_exit "Please set a governor for chainID $L1_CHAINID in $ADDRESS_BOOK" +fi + +L1GraphTokenGateway=$(jq -r ".[\"$L1_CHAINID\"].L1GraphTokenGateway" $ADDRESS_BOOK || echo "null") +if [[ "$L1GraphTokenGateway" = "null" ]]; then + error_exit "Please set an L1GraphTokenGateway for chainID $L1_CHAINID in $ADDRESS_BOOK" +fi + +ArbitrumInbox=$(jq -r ".[\"$L1_CHAINID\"].ArbitrumInbox" $ADDRESS_BOOK || echo "null") +if [[ "$ArbitrumInbox" = "null" ]]; then + error_exit "Please set an ArbitrumInbox for chainID $L1_CHAINID in $ADDRESS_BOOK" +fi + +l1Deployer=$(MNEMONIC=$L1_MNEMONIC npx hardhat print-account --network $L1_NETWORK --num 0) + +echo "Using L1 deployer $l1Deployer" + + export MNEMONIC="$L1_MNEMONIC" +echo "Deploying BillingConnector:" +npx hardhat deploy-billing-connector --network $L1_NETWORK \ + --address-book $ADDRESS_BOOK \ + --tokengateway $L1GraphTokenGateway \ + --billing $Billing \ + --token $GraphToken \ + --governor $l1Governor \ + --inbox $ArbitrumInbox + +BillingConnector=$(jq -r ".[\"$L1_CHAINID\"].BillingConnector" $ADDRESS_BOOK || echo "null") +if [[ "$BillingConnector" = "null" ]]; then + error_exit "Could not get new BillingConnector address from $ADDRESS_BOOK" +fi + +function verify_billing_connector() { + npx hardhat verify --network $L1_NETWORK \ + $BillingConnector \ + $L1GraphTokenGateway \ + $Billing \ + $GraphToken \ + $l1Governor \ + $ArbitrumInbox +} + +echo "Verifying BillingConnector:" +if ! verify_billing_connector; then + echo "Retrying in 60s..." + sleep 60 # Give etherscan some time to catch up... + verify_billing_connector +fi + + export MNEMONIC="$L2_MNEMONIC" +echo "Connecting Billing to BillingConnector, and (optionally) transferring ownership:" +npx hardhat configure-billing --network $L2_NETWORK \ + --address-book $ADDRESS_BOOK \ + --governor $l2Governor \ + --billingconnector $BillingConnector + +echo "Billing and BillingConnector deployed and configured" +echo "(but L2 governor $l2Governor might still have to accept ownership of the Billing contract)" diff --git a/packages/billing/scripts/flatten b/packages/billing/scripts/flatten new file mode 100755 index 000000000..80a67c89d --- /dev/null +++ b/packages/billing/scripts/flatten @@ -0,0 +1,24 @@ +#!/bin/bash + +OUT_DIR="build/flatten" + +mkdir -p ${OUT_DIR} + +echo "Flattening contracts..." + +FILES=( + "contracts/Billing.sol" + "contracts/IBilling.sol" + "contracts/BillingConnector.sol" + "contracts/IBillingConnector.sol" +) + +for path in ${FILES[@]}; do + IFS='/' + parts=( $path ) + name=${parts[${#parts[@]}-1]} + echo "Flatten > ${name}" + hardhat flatten "${path}" > "${OUT_DIR}/${name}" +done + +echo "Done!" diff --git a/packages/billing/scripts/prepublish b/packages/billing/scripts/prepublish new file mode 100755 index 000000000..a24463117 --- /dev/null +++ b/packages/billing/scripts/prepublish @@ -0,0 +1,23 @@ +#!/bin/bash + +TYPECHAIN_DIR=dist/types + +set -eo pipefail + +# Build contracts +yarn clean +yarn build + +# Refresh distribution folder +rm -rf dist && mkdir -p dist/types/_src +cp -R build/abis/ dist/abis +cp -R build/types/ dist/types/_src + +### Build Typechain bindings + +# Build and create TS declarations +tsc -d ${TYPECHAIN_DIR}/_src/*.ts --outdir ${TYPECHAIN_DIR} +# Copy back sources +cp ${TYPECHAIN_DIR}/_src/*.ts ${TYPECHAIN_DIR} +# Delete temporary src dir +rm -rf ${TYPECHAIN_DIR}/_src diff --git a/packages/billing/scripts/security b/packages/billing/scripts/security new file mode 100755 index 000000000..90cb4c2f0 --- /dev/null +++ b/packages/billing/scripts/security @@ -0,0 +1,17 @@ +#!/bin/bash + +## Before running: +# This tool requires to have solc installed. +# Ensure that you have the binaries installed by pip3 in your path. +# Install: https://github.com/crytic/slither#how-to-install +# Usage: https://github.com/crytic/slither/wiki/Usage + +mkdir -p reports + +pip3 install --user slither-analyzer && \ +yarn run build && \ + +echo "Analyzing contracts..." +slither . &> reports/analyzer-report.log && \ + +echo "Done!" diff --git a/packages/billing/scripts/test b/packages/billing/scripts/test new file mode 100755 index 000000000..dfb460b41 --- /dev/null +++ b/packages/billing/scripts/test @@ -0,0 +1,62 @@ +#!/bin/bash + +set -eo pipefail + +MNEMONIC="myth like bonus scare over problem client lizard pioneer submit female collect" +TESTRPC_PORT=8545 + +### Functions + +evm_running() { + nc -z localhost "$TESTRPC_PORT" +} + +evm_start() { + echo "Starting our own evm instance at port $TESTRPC_PORT" + npx ganache-cli -m "$MNEMONIC" -i 1337 --gasLimit 8000000 --port "$TESTRPC_PORT" > /dev/null & + evm_pid=$! +} + +evm_kill() { + if evm_running; then + echo "Killing evm instance at port $TESTRPC_PORT" + kill -9 $(lsof -i:$TESTRPC_PORT -t) + fi +} + +### Setup evm + +# Gas reporter needs to run in its own evm instance +if [ "$RUN_EVM" = true ]; then + evm_kill + evm_start + sleep 5 +fi + +### Main + +mkdir -p reports + +yarn run build + +if [ "$RUN_EVM" = true ]; then + # Run using the standalone evm instance + npx hardhat test --network ganache + result=$? +else + # Run using the default evm + npx hardhat test "$@" + result=$? +fi + +### Cleanup + +# Exit error mode so the evm instance always gets killed +set +e +result=0 + +if [ "$RUN_EVM" = true ]; then + evm_kill +fi + +exit $result diff --git a/packages/billing/tasks/deployment/accounts.ts b/packages/billing/tasks/deployment/accounts.ts new file mode 100644 index 000000000..92c23a38d --- /dev/null +++ b/packages/billing/tasks/deployment/accounts.ts @@ -0,0 +1,10 @@ +import { task, types } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +task('print-account', 'Print addresses for an account based on the configured mnemonic') + .addParam('num', 'Account number, default 0', 0, types.int) + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + const accountNum = taskArgs.num ?? 0 + console.log(accounts[accountNum].address) + }) diff --git a/packages/billing/tasks/deployment/banxa.ts b/packages/billing/tasks/deployment/banxa.ts new file mode 100644 index 000000000..2e89bf1d4 --- /dev/null +++ b/packages/billing/tasks/deployment/banxa.ts @@ -0,0 +1,25 @@ +import { Wallet } from 'ethers' +import { task } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { getAddressBook } from '../../utils/addressBook' +import { deployBanxaWrapper } from '../../utils/deploy' + +import { promises as fs } from 'fs' + +task('deploy-banxa', 'Deploy the banxa wrapper contract (use L2 network!)') + .addParam('addressBook', 'Addressese json file name', process.env.ADDRESS_BOOK) + .addParam('billing', 'Address of the billing contract') + .addParam('token', 'Address of the token') + .addParam('governor', 'Address of the governor, leave empty to use the deployer') + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + const chainId = (hre.network.config.chainId as number).toString() + const banxaWrapper = await deployBanxaWrapper( + [taskArgs.token, taskArgs.billing, taskArgs.governor ?? accounts[0].address], + accounts[0] as unknown as Wallet, + ) + const addresses = getAddressBook(taskArgs.addressBook, chainId) + addresses[chainId]['BanxaWrapper'] = banxaWrapper.address + return fs.writeFile(taskArgs.addressBook, JSON.stringify(addresses, null, 2)) + }) diff --git a/packages/billing/tasks/deployment/billing.ts b/packages/billing/tasks/deployment/billing.ts new file mode 100644 index 000000000..720c0b70d --- /dev/null +++ b/packages/billing/tasks/deployment/billing.ts @@ -0,0 +1,60 @@ +import { Wallet } from 'ethers' +import { task } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { deployBilling } from '../../utils/deploy' +import { logger } from '../../utils/logging' + +import { getAddressBook } from '../../utils/addressBook' +import { getContractAt } from '../../utils/contracts' +import { Billing } from '../../build/types' +import { promises as fs } from 'fs' + +task('deploy-billing', 'Deploy the billing contract (use L2 network!)') + .addParam('addressBook', 'Addressese json file name', process.env.ADDRESS_BOOK) + .addParam('collector', 'Address of the collector') + .addParam('token', 'Address of the token') + .addParam('governor', 'Address of the governor, leave empty to use the deployer') + .addParam('tokengateway', 'Address of the L2GraphTokenGateway') + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + const chainId = (hre.network.config.chainId as number).toString() + const billing = await deployBilling( + [taskArgs.collector, taskArgs.token, taskArgs.governor ?? accounts[0].address, taskArgs.tokengateway], + accounts[0] as unknown as Wallet, + ) + const addresses = getAddressBook(taskArgs.addressBook, chainId) + addresses[chainId]['Billing'] = billing.address + return fs.writeFile(taskArgs.addressBook, JSON.stringify(addresses, null, 2)) + }) + +task( + 'configure-billing', + 'Configure the billing contract to set billing connector address and transfer ownership to the governor (use L2 network!)', +) + .addParam('addressBook', 'Addressese json file name', process.env.ADDRESS_BOOK) + .addParam('governor', 'Address of the governor') + .addParam('billingconnector', 'Address of the BillingConnector on L1') + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + const chainId = (hre.network.config.chainId as number).toString() + const addresses = getAddressBook(taskArgs.addressBook, chainId) + const addressBook = addresses[chainId] + const wallet = accounts[0] as unknown as Wallet + const billing = getContractAt('Billing', addressBook['Billing'], wallet) as unknown as Billing + + logger.log(`Setting L1 billing connector address to ${taskArgs.billingconnector}`) + const tx1 = await billing.connect(wallet).setL1BillingConnector(taskArgs.billingconnector) + const receipt1 = await tx1.wait() + logger.log(`> Done. Tx hash: ${receipt1.transactionHash}`) + + const currentBillingGovernor = await billing.governor() + if (currentBillingGovernor != taskArgs.governor) { + logger.log(`Transferring Billing contract ownership to ${taskArgs.governor}`) + const tx2 = await billing.connect(wallet).transferOwnership(taskArgs.governor) + const receipt2 = await tx2.wait() + logger.log(`> Done. Tx hash: ${receipt2.transactionHash}`) + } else { + logger.log('Billing is already owned by the right governor, so skipping ownership transfer.') + } + }) diff --git a/packages/billing/tasks/deployment/billingConnector.ts b/packages/billing/tasks/deployment/billingConnector.ts new file mode 100644 index 000000000..1cc9a5fea --- /dev/null +++ b/packages/billing/tasks/deployment/billingConnector.ts @@ -0,0 +1,26 @@ +import { Wallet } from 'ethers' +import { task } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { getAddressBook } from '../../utils/addressBook' +import { deployBillingConnector } from '../../utils/deploy' +import { promises as fs } from 'fs' + +task('deploy-billing-connector', 'Deploy the billing connector contract (use L1 network!)') + .addParam('addressBook', 'Addressese json file name', process.env.ADDRESS_BOOK) + .addParam('tokengateway', 'Address of the L1GraphTokenGateway') + .addParam('billing', 'Address of the Billing contract on L2') + .addParam('token', 'Address of the token') + .addParam('governor', 'Address of the governor') + .addParam('inbox', 'Address of the Arbitrum DelayedInbox') + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + const chainId = (hre.network.config.chainId as number).toString() + const addresses = getAddressBook(taskArgs.addressBook, chainId) + const billingConnector = await deployBillingConnector( + [taskArgs.tokengateway, taskArgs.billing, taskArgs.token, taskArgs.governor, taskArgs.inbox], + accounts[0] as unknown as Wallet, + ) + addresses[chainId]['BillingConnector'] = billingConnector.address + return fs.writeFile(taskArgs.addressBook, JSON.stringify(addresses, null, 2)) + }) diff --git a/packages/billing/tasks/deployment/token.ts b/packages/billing/tasks/deployment/token.ts new file mode 100644 index 000000000..91b0492b5 --- /dev/null +++ b/packages/billing/tasks/deployment/token.ts @@ -0,0 +1,12 @@ +import { Wallet } from 'ethers' +import { task } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { deployToken } from '../../utils/deploy' + +task('deploy-token', 'Deploy the token contract') + .addParam('totalSupply', 'Total supply of the token') + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + await deployToken([taskArgs.totalSupply], accounts[0] as unknown as Wallet) + }) diff --git a/packages/billing/tasks/extendContracts.ts b/packages/billing/tasks/extendContracts.ts new file mode 100644 index 000000000..9e3cdf168 --- /dev/null +++ b/packages/billing/tasks/extendContracts.ts @@ -0,0 +1,28 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { extendEnvironment } from 'hardhat/config' +import { lazyObject } from 'hardhat/plugins' + +import { getAddressBook } from '../utils/addressBook' +import { BillingContracts, loadContracts } from '../utils/contracts' + +declare module 'hardhat/types/runtime' { + export interface HardhatRuntimeEnvironment { + contracts: BillingContracts + } +} + +extendEnvironment((hre: HardhatRuntimeEnvironment) => { + const chainId = (hre.network.config.chainId as number).toString() + const addressBookPath = process.env.ADDRESS_BOOK || 'addresses.json' + const addresses = getAddressBook(addressBookPath, chainId) + const addressBook = addresses[chainId as unknown as number] + hre['contracts'] = lazyObject(() => { + return loadContracts( + addressBook?.Billing, + addressBook?.BillingConnector, + addressBook?.GraphToken ?? addressBook?.L2GraphToken, + addressBook?.BanxaWrapper, + hre.ethers.provider, + ) + }) +}) diff --git a/packages/billing/tasks/ops/addToMany.ts b/packages/billing/tasks/ops/addToMany.ts new file mode 100644 index 000000000..c443b86ce --- /dev/null +++ b/packages/billing/tasks/ops/addToMany.ts @@ -0,0 +1,133 @@ +import fs from 'fs' +import { BigNumber, utils } from 'ethers' +import { task, types } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { logger } from '../../utils/logging' +import { askForConfirmation, DEFAULT_DEPOSITORS_FILE, DEFAULT_BATCH_SIZE } from './utils' + +task('ops:add-to-many', 'Execute a transaction depositing funds to a set of users from a JSON file') + .addFlag('dryRun', 'Do not execute transaction') + .addOptionalParam( + 'batchSize', + 'Batch size (i.e. number of users to process at a time)', + DEFAULT_BATCH_SIZE, + types.int, + ) + .addOptionalParam('startBatch', 'Batch number to start from', 0, types.int) + .addOptionalParam('depositorsFile', 'Path to depositors file', DEFAULT_DEPOSITORS_FILE) + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const { contracts } = hre + const accounts = await hre.ethers.getSigners() + const account = accounts[0] + const chainId = hre.network.config.chainId + + logger.log(`Getting depositors from ${taskArgs.depositorsFile}...`) + const depositors = JSON.parse(fs.readFileSync(taskArgs.depositorsFile).toString()) + const users: string[] = depositors.map((depositor) => depositor.address) + const balances: BigNumber[] = depositors.map((depositor) => BigNumber.from(depositor.balance)) + + const totalBalance = balances.reduce((a, b) => a.add(b), BigNumber.from(0)) + + logger.log(`Total balance: ${utils.formatEther(totalBalance)}`) + logger.log(`--------------------------------`) + for (const depositor of depositors) { + logger.log(depositor.address, utils.formatEther(depositor.balance.hex)) + } + + const nBatches = Math.floor(depositors.length / taskArgs.batchSize) + 1 + + if (taskArgs.startBatch == 0) { + if (taskArgs.dryRun) { + logger.log('Dry run, so not executing tx') + logger.log('Otherwise we would have executed:') + logger.log(`Token.approve(${contracts.Billing?.address}, ${totalBalance})`) + logger.log(`On Token contract at ${contracts.Token?.address} on chain ${chainId}`) + logger.log(`With signer ${account.address}`) + + logger.log('TX calldata:') + logger.log(`--------------------`) + const grt = contracts.Token + if (!grt) { + throw new Error('GRT contract not found') + } + const tx = await grt.populateTransaction.approve(contracts.Billing?.address, totalBalance) + logger.log(tx.data) + logger.log(`--------------------`) + } else if ( + await askForConfirmation( + `Execute transaction? **This will execute on network with chain ID ${chainId}**`, + ) + ) { + try { + logger.log('Transactions being sent') + logger.log(`--------------------`) + const grt = contracts.Token + if (!grt) { + throw new Error('GRT contract not found') + } + const tx = await grt.connect(account).approve(contracts.Billing?.address, totalBalance) + const receipt = await tx.wait() + logger.log('approve() TX Receipt: ', receipt) + } catch (e) { + logger.log(e) + process.exit(1) + } + } else { + logger.log('Skipping approve tx at user request') + } + } else { + logger.log(`Skipping approve tx since we are not on the first batch`) + } + for (let batch = taskArgs.startBatch; batch < nBatches; batch++) { + const start = batch * taskArgs.batchSize + const end = Math.min(start + taskArgs.batchSize, depositors.length) + const batchUsers = users.slice(start, end) + const batchBalances = balances.slice(start, end) + logger.log(`Batch ${batch} (${batch + 1}/${nBatches}):`) + logger.log(`Users: ${batchUsers.length}`) + logger.log(`Total balance: ${utils.formatEther(batchBalances.reduce((a, b) => a.add(b), BigNumber.from(0)))}`) + logger.log(`--------------------------------`) + if (taskArgs.dryRun) { + logger.log('Dry run, so not executing tx') + logger.log('Otherwise we would have executed:') + logger.log(`--------------------`) + logger.log(`Billing.addToMany([${batchUsers}], [${batchBalances}])`) + logger.log(`--------------------`) + logger.log(`On Billing contract at ${contracts.Billing?.address} on chain ${chainId}`) + logger.log(`With signer ${account.address}`) + + logger.log('TX calldata:') + logger.log(`--------------------`) + const billing = contracts.Billing + if (!billing) { + throw new Error('Billing contract not found') + } + const tx = await billing.populateTransaction.addToMany(batchUsers, batchBalances) + logger.log(tx.data) + logger.log(`--------------------`) + } else if ( + await askForConfirmation( + `Execute transaction? **This will execute on network with chain ID ${chainId}**`, + ) + ) { + try { + logger.log('Transactions being sent') + logger.log(`--------------------`) + const billing = contracts.Billing + if (!billing) { + throw new Error('Billing contract not found') + } + const tx = await billing.connect(account).addToMany(batchUsers, batchBalances) + const receipt = await tx.wait() + logger.log('addToMany TX Receipt: ', receipt) + } catch (e) { + logger.log(e) + process.exit(1) + } + } else { + logger.log('Bye!') + } + + logger.log(`--------------------------------`) + } + }) diff --git a/packages/billing/tasks/ops/pullAll.ts b/packages/billing/tasks/ops/pullAll.ts new file mode 100644 index 000000000..79225fe26 --- /dev/null +++ b/packages/billing/tasks/ops/pullAll.ts @@ -0,0 +1,179 @@ +import fs from 'fs' +import axios from 'axios' + +import { BigNumber, utils } from 'ethers' +import { task, types } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { logger } from '../../utils/logging' +import { askForConfirmation, DEFAULT_BILLING_SUBGRAPH, DEFAULT_DEPOSITORS_FILE, DEFAULT_BATCH_SIZE } from './utils' +import path from 'path' + +// This script will pull the funds from all the billing accounts and store +// them in a file (by default, `depositors.json` in the same directory as this script). + +interface Depositor { + address: string + balance: BigNumber +} + +export async function getAllDepositors( + billingSubgraphUrl: string, + page: number, + pageSize: number, + blockNumber: number, +): Promise { + let queryPartOne: string + if (blockNumber > 0) { + queryPartOne = `{ + users( + block: { number: ${blockNumber} },` + } else { + queryPartOne = `{ + users(` + } + const queryPartTwo = ` + first: ${pageSize}, + skip: ${page * pageSize}, + where: {billingBalance_gt: "0"}, + orderBy: billingBalance, + orderDirection: desc + ) { + id + billingBalance + } + } + ` + const query = queryPartOne + queryPartTwo + const response = await axios.post(billingSubgraphUrl, { query }) + logger.log(`Found: ${response.data.data.users.length} users`) + return response.data.data.users.map((user) => { + return { + address: user.id, + balance: BigNumber.from(user.billingBalance), + } + }) +} + +function writeDepositorsToFile(depositors: Depositor[], filePath: string) { + let allDepositors: Depositor[] = [] + if (fs.existsSync(filePath)) { + allDepositors = JSON.parse(fs.readFileSync(filePath).toString()) + } + allDepositors = allDepositors.concat(depositors) + fs.writeFileSync(filePath, JSON.stringify(allDepositors, null, 2), { flag: 'w+' }) + const writtenDepositors = JSON.parse(fs.readFileSync(filePath).toString()) + if (writtenDepositors.length != allDepositors.length) { + throw new Error('Written depositors does not equal fetched depositors') + } +} + +task('ops:pull-all', 'Execute transaction for pulling all funds from users') + .addFlag('dryRun', 'Do not execute transaction') + .addOptionalParam('dstAddress', 'Destination address of withdrawal') + .addOptionalParam( + 'batchSize', + 'Batch size (i.e. number of users to process at a time)', + DEFAULT_BATCH_SIZE, + types.int, + ) + .addOptionalParam('startBatch', 'Batch number to start from', 0, types.int) + .addOptionalParam('blockNumber', 'Block number to use when fetching balances from the subgraph', 0, types.int) + .addOptionalParam('depositorsFile', 'Path to EOA depositors file', DEFAULT_DEPOSITORS_FILE) + .addOptionalParam('billingSubgraphUrl', 'Billing subgraph URL', DEFAULT_BILLING_SUBGRAPH) + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const accounts = await hre.ethers.getSigners() + const collector = accounts[0] + const { contracts } = hre + const chainId = hre.network.config.chainId + const dstAddress = taskArgs.dstAddress || collector.address + let page = taskArgs.startBatch + const depositorBatches: Depositor[][] = [] + if (!taskArgs.dryRun && taskArgs.startBatch > 0 && taskArgs.blockNumber == 0) { + logger.log('Please specify a block number when starting from a batch other than 0') + process.exit(1) + } + if (fs.existsSync(taskArgs.depositorsFile) && page == 0) { + fs.renameSync(taskArgs.depositorsFile, taskArgs.depositorsFile + '.bak') + } + let depositors: Depositor[] = [] + do { + logger.log(`Getting depositors (batch ${page}, size ${taskArgs.batchSize})...`) + depositors = await getAllDepositors(taskArgs.billingSubgraphUrl, page, taskArgs.batchSize, taskArgs.blockNumber) + if (depositors.length == 0) { + logger.log('No depositors found, done with fetching.') + break + } + + const balances: BigNumber[] = depositors.map((depositor) => depositor.balance) + + try { + writeDepositorsToFile(depositors, taskArgs.depositorsFile) + } catch (e) { + logger.log(`Error writing depositors file: \n${e}`) + process.exit(1) + } + + const totalBalance = balances.reduce((a, b) => a.add(b), BigNumber.from(0)) + + logger.log(`Balance: ${utils.formatEther(totalBalance)}`) + logger.log(`--------------------------------`) + for (const depositor of depositors) { + logger.log(depositor.address, utils.formatEther(depositor.balance)) + } + depositorBatches.push(depositors) + page += 1 + } while (depositors.length > 0) + + page = 0 + for (const depositors of depositorBatches) { + if (depositors.length == 0) { + logger.log('No depositors found, done.') + break + } + logger.log(`Pulling tokens for depositors (batch ${page}, size ${depositors.length})...`) + const users: string[] = depositors.map((depositor) => depositor.address) + const balances: BigNumber[] = depositors.map((depositor) => depositor.balance) + if (taskArgs.dryRun) { + logger.log('Dry run, so not executing tx') + logger.log('Otherwise we would have executed:') + logger.log(`--------------------`) + logger.log(`Billing.pullMany([${users}], [${balances}], ${dstAddress})`) + logger.log(`--------------------`) + logger.log(`On Billing contract at ${contracts.Billing?.address} on chain ${chainId}`) + logger.log(`With signer ${collector.address}`) + + logger.log('TX calldata:') + logger.log(`--------------------`) + const billing = contracts.Billing + if (!billing) { + throw new Error('Billing contract not found') + } + const tx = await billing.populateTransaction.pullMany(users, balances, dstAddress) + logger.log(tx.data) + logger.log(`--------------------`) + } else if ( + await askForConfirmation( + `Execute transaction? **This will execute on network with chain ID ${chainId}**`, + ) + ) { + logger.log('Transaction being sent') + logger.log(`--------------------`) + try { + const billing = contracts.Billing + if (!billing) { + throw new Error('Billing contract not found') + } + const tx = await billing.connect(collector).pullMany(users, balances, dstAddress) + const receipt = await tx.wait() + logger.log('Receipt: ', receipt) + } catch (e) { + logger.log(e) + process.exit(1) + } + logger.log(`--------------------`) + } else { + logger.log('Bye!') + } + page += 1 + } + }) diff --git a/packages/billing/tasks/ops/utils.ts b/packages/billing/tasks/ops/utils.ts new file mode 100644 index 000000000..50d1100fd --- /dev/null +++ b/packages/billing/tasks/ops/utils.ts @@ -0,0 +1,14 @@ +import inquirer from 'inquirer' + +export const DEFAULT_DEPOSITORS_FILE = './tasks/ops/depositors.json' +export const DEFAULT_BILLING_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/graphprotocol/billing' +export const DEFAULT_BATCH_SIZE = 200 + +export async function askForConfirmation(message: string): Promise { + const res = await inquirer.prompt({ + name: 'confirm', + type: 'confirm', + message, + }) + return res.confirm +} diff --git a/packages/billing/test/banxa.test.ts b/packages/billing/test/banxa.test.ts new file mode 100644 index 000000000..466759420 --- /dev/null +++ b/packages/billing/test/banxa.test.ts @@ -0,0 +1,143 @@ +import '@nomicfoundation/hardhat-chai-matchers' + +import { expect } from 'chai' +import { constants, Contract } from 'ethers' +import * as deployment from '../utils/deploy' +import { getAccounts, Account, toGRT, toBN } from '../utils/helpers' + +import { BanxaWrapper } from '../build/types/contracts/BanxaWrapper' +import { Billing } from '../build/types/contracts/Billing' + +const { AddressZero } = constants + +describe('BanxaWrapper', () => { + // Accounts + let me: Account + let collector1: Account + let user1: Account + let governor: Account + let l2TokenGatewayMock: Account + let banxaFulfillmentService: Account + + // Contracts + let banxaWrapper: BanxaWrapper + let billing: Billing + let token: Contract + + before(async function () { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;[me, collector1, user1, governor, l2TokenGatewayMock, banxaFulfillmentService] = await getAccounts() + }) + + const tenBillion = toGRT('10000000000') + const oneHundred = toGRT('100') + const oneMillion = toGRT('1000000') + + beforeEach(async function () { + token = await deployment.deployToken([tenBillion], me.signer, true) + billing = await deployment.deployBilling( + [collector1.address, token.address, governor.address, l2TokenGatewayMock.address], + me.signer, + true, + ) + banxaWrapper = await deployment.deployBanxaWrapper( + [token.address, billing.address, governor.address], + me.signer, + true, + ) + + await token.connect(me.signer).transfer(banxaFulfillmentService.address, oneMillion) + await token.connect(me.signer).transfer(user1.address, oneMillion) + await token.connect(banxaFulfillmentService.signer).approve(banxaWrapper.address, oneMillion) + }) + + describe('constructor', function () { + it('should set the token address', async function () { + expect(await banxaWrapper.graphToken()).to.eq(token.address) + }) + + it('should set the billing address', async function () { + expect(await banxaWrapper.billing()).to.eq(billing.address) + }) + }) + + describe('fulfil', function () { + it('should fulfil orders', async function () { + const beforeBillingBalance = await billing.userBalances(user1.address) + const beforeServiceBalance = await token.balanceOf(banxaFulfillmentService.address) + + const tx = banxaWrapper.connect(banxaFulfillmentService.signer).fulfil(user1.address, oneHundred) + await expect(tx) + .emit(banxaWrapper, 'OrderFulfilled') + .withArgs(banxaFulfillmentService.address, user1.address, oneHundred) + + const afterBillingBalance = await billing.userBalances(user1.address) + const afterServiceBalance = await token.balanceOf(banxaFulfillmentService.address) + + expect(afterBillingBalance).eq(beforeBillingBalance.add(oneHundred)) + expect(afterServiceBalance).eq(beforeServiceBalance.sub(oneHundred)) + }) + + it('should fail to fulfil orders for address(0)', async function () { + const tx = banxaWrapper.connect(banxaFulfillmentService.signer).fulfil(AddressZero, oneHundred) + await expect(tx).revertedWithCustomError(banxaWrapper, 'InvalidZeroAddress') + }) + + it('should fail to fulfil orders with zero tokens', async function () { + const tx = banxaWrapper.connect(banxaFulfillmentService.signer).fulfil(user1.address, toBN(0)) + await expect(tx).revertedWithCustomError(banxaWrapper, 'InvalidZeroAmount') + }) + }) + + describe('rescue', function () { + it('should rescue tokens', async function () { + // deploy token2 and accidentally send to the BanxaWrapper contract + const token2 = await deployment.deployToken([tenBillion], me.signer, true) + await token2.connect(me.signer).transfer(user1.address, oneMillion) + await token2.connect(user1.signer).transfer(banxaWrapper.address, oneMillion) + + // the bad transfer of GRT + await token.connect(user1.signer).transfer(banxaWrapper.address, oneMillion) + + const tokenBeforeUser = await token.balanceOf(user1.address) + const token2BeforeUser = await token2.balanceOf(user1.address) + const tokenBeforeBanxa = await token.balanceOf(banxaWrapper.address) + const token2BeforeBanxa = await token2.balanceOf(banxaWrapper.address) + + const tx = await banxaWrapper.connect(governor.signer).rescueTokens(user1.address, token.address, oneMillion) + await expect(tx).emit(banxaWrapper, 'TokensRescued').withArgs(user1.address, token.address, oneMillion) + await banxaWrapper.connect(governor.signer).rescueTokens(user1.address, token2.address, oneMillion) + + const tokenAfterUser = await token.balanceOf(user1.address) + const token2AfterUser = await token2.balanceOf(user1.address) + const tokenAfterBanxa = await token.balanceOf(banxaWrapper.address) + const token2AfterBanxa = await token2.balanceOf(banxaWrapper.address) + + expect(tokenAfterUser).eq(tokenBeforeUser.add(oneMillion)) + expect(token2AfterUser).eq(token2BeforeUser.add(oneMillion)) + expect(tokenAfterBanxa).eq(tokenBeforeBanxa.sub(oneMillion)) + expect(token2AfterBanxa).eq(token2BeforeBanxa.sub(oneMillion)) + }) + + it('should fail rescue tokens when not the governor', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(banxaWrapper.address, oneMillion) + const tx = banxaWrapper.connect(user1.signer).rescueTokens(user1.address, token.address, oneMillion) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should fail when trying to send to address zero', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(banxaWrapper.address, oneMillion) + const tx = banxaWrapper.connect(governor.signer).rescueTokens(AddressZero, token.address, oneMillion) + await expect(tx).revertedWith('Cannot send to address(0)') + }) + + it('should fail when trying to send zero tokens', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(banxaWrapper.address, oneMillion) + const tx = banxaWrapper.connect(governor.signer).rescueTokens(user1.address, token.address, toBN(0)) + await expect(tx).revertedWith('Cannot rescue 0 tokens') + }) + }) +}) diff --git a/packages/billing/test/billing.test.ts b/packages/billing/test/billing.test.ts new file mode 100644 index 000000000..5d817927e --- /dev/null +++ b/packages/billing/test/billing.test.ts @@ -0,0 +1,536 @@ +import '@nomicfoundation/hardhat-chai-matchers' + +import { expect } from 'chai' +import { PANIC_CODES } from '@nomicfoundation/hardhat-chai-matchers/panic' +import { BigNumber, constants, Contract, Signer } from 'ethers' +import * as deployment from '../utils/deploy' +import { getAccounts, Account, toGRT, toBN, getL2SignerFromL1 } from '../utils/helpers' + +import { Billing } from '../build/types/contracts/Billing' +import { defaultAbiCoder, parseUnits } from 'ethers/lib/utils' + +const { AddressZero, MaxUint256 } = constants + +describe('Billing', () => { + let me: Account + let collector1: Account + let collector2: Account + let user1: Account + let user2: Account + let user3: Account + let governor: Account + let l2TokenGatewayMock: Account + let l1BillingConnectorMock: Account + + let l1BillingConnectorMockAlias: Signer + + let billing: Billing + + let token: Contract + + before(async function () { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;[me, collector1, collector2, user1, user2, user3, governor, l2TokenGatewayMock, l1BillingConnectorMock] = + await getAccounts() + l1BillingConnectorMockAlias = await getL2SignerFromL1(l1BillingConnectorMock.address) + // Send funds to the L1 BillingConnector's alias + await me.signer.sendTransaction({ + to: await l1BillingConnectorMockAlias.getAddress(), + value: parseUnits('1', 'ether'), + }) + }) + + const tenBillion = toGRT('10000000000') + const oneHundred = toGRT('100') + const oneMillion = toGRT('1000000') + + beforeEach(async function () { + token = await deployment.deployToken([tenBillion], me.signer, true) + billing = await deployment.deployBilling( + [collector1.address, token.address, governor.address, l2TokenGatewayMock.address], + me.signer, + true, + ) + await token.connect(me.signer).transfer(user1.address, oneMillion) + await token.connect(me.signer).transfer(user2.address, oneMillion) + await token.connect(user1.signer).approve(billing.address, oneMillion) + await token.connect(user2.signer).approve(billing.address, oneMillion) + await token.connect(user3.signer).approve(billing.address, oneMillion) + }) + + describe('admin', function () { + it('should add a `collector`', async function () { + expect(await billing.isCollector(collector1.address)).eq(true) + const tx = billing.connect(governor.signer).setCollector(collector2.address, true) + await expect(tx).emit(billing, 'CollectorUpdated').withArgs(collector2.address, true) + expect(await billing.isCollector(collector1.address)).eq(true) + expect(await billing.isCollector(collector2.address)).eq(true) + }) + + it('should remove a `collector`', async function () { + expect(await billing.isCollector(collector1.address)).eq(true) + const tx = billing.connect(governor.signer).setCollector(collector1.address, false) + await expect(tx).emit(billing, 'CollectorUpdated').withArgs(collector1.address, false) + expect(await billing.isCollector(collector1.address)).eq(false) + }) + + it('should fail to set `collector` if not governor', async function () { + const tx = billing.connect(me.signer).setCollector(collector2.address, true) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should fail to set `collector` for the zero address', async function () { + const tx = billing.connect(governor.signer).setCollector(AddressZero, true) + await expect(tx).revertedWith('Collector cannot be 0') + }) + + it('should set the L2 token gateway', async function () { + expect(await billing.l2TokenGateway()).eq(l2TokenGatewayMock.address) + const tx = billing.connect(governor.signer).setL2TokenGateway(user3.address) + await expect(tx).emit(billing, 'L2TokenGatewayUpdated').withArgs(user3.address) + expect(await billing.l2TokenGateway()).eq(user3.address) + }) + + it('should fail to set L2 token gateway if not governor', async function () { + const tx = billing.connect(me.signer).setL2TokenGateway(user3.address) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should fail to set L2 token gateway to the zero address', async function () { + const tx = billing.connect(governor.signer).setL2TokenGateway(AddressZero) + await expect(tx).revertedWith('L2 Token Gateway cannot be 0') + }) + + it('should set the L1 billing connector', async function () { + expect(await billing.l1BillingConnector()).eq(AddressZero) + const tx = billing.connect(governor.signer).setL1BillingConnector(user3.address) + await expect(tx).emit(billing, 'L1BillingConnectorUpdated').withArgs(user3.address) + expect(await billing.l1BillingConnector()).eq(user3.address) + }) + + it('should fail to set L1 billing connector if not governor', async function () { + const tx = billing.connect(me.signer).setL1BillingConnector(user3.address) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should fail to set L1 billing connector to the zero address', async function () { + const tx = billing.connect(governor.signer).setL1BillingConnector(AddressZero) + await expect(tx).revertedWith('L1 Billing Connector cannot be 0') + }) + }) + + describe('add', function () { + it('should add', async function () { + const beforeAdd = await billing.userBalances(user1.address) + const beforeBalance = await token.balanceOf(user1.address) + + const tx = billing.connect(user1.signer).add(oneHundred) + await expect(tx).emit(billing, 'TokensAdded').withArgs(user1.address, oneHundred) + + const afterAdd = await billing.userBalances(user1.address) + const afterBalance = await token.balanceOf(user1.address) + expect(afterAdd).eq(beforeAdd.add(oneHundred)) + expect(afterBalance).eq(beforeBalance.sub(oneHundred)) + }) + }) + + describe('addTo', function () { + it('should add to', async function () { + const beforeAdd2 = await billing.userBalances(user2.address) + const beforeBalance1 = await token.balanceOf(user1.address) + + const tx = billing.connect(user1.signer).addTo(user2.address, oneHundred) + await expect(tx).emit(billing, 'TokensAdded').withArgs(user2.address, oneHundred) + + const afterAdd2 = await billing.userBalances(user2.address) + const afterBalance1 = await token.balanceOf(user1.address) + expect(afterAdd2).eq(beforeAdd2.add(oneHundred)) + expect(afterBalance1).eq(beforeBalance1.sub(oneHundred)) + }) + + it('should fail add to on address(0)', async function () { + const tx = billing.connect(user1.signer).addTo(AddressZero, oneHundred) + await expect(tx).revertedWith('user != 0') + }) + + it('should fail on add if no tokens held by user', async function () { + const tx = billing.connect(user3.signer).add(oneHundred) + await expect(tx).revertedWith('ERC20: transfer amount exceeds balance') + }) + + it('should fail add zero tokens', async function () { + const tx = billing.connect(user1.signer).addTo(user2.address, toBN(0)) + await expect(tx).revertedWith('Must add more than 0') + }) + }) + + describe('onTokenTransfer', function () { + context('BillingConnector not set', function () { + it('should fail if called by the token gateway but the billing connector is not set', async function () { + const callhookData = defaultAbiCoder.encode(['address'], [user2.address]) + const tx = billing + .connect(l2TokenGatewayMock.signer) + .onTokenTransfer(l1BillingConnectorMock.address, oneHundred, callhookData) + await expect(tx).revertedWith('BillingConnector not set') + }) + }) + context('BillingConnector properly set', function () { + beforeEach(async function () { + await billing.connect(governor.signer).setL1BillingConnector(l1BillingConnectorMock.address) + }) + + it('should add to (without moving tokens)', async function () { + const beforeUserBalance = await billing.userBalances(user2.address) + const beforeContractBalance = await token.balanceOf(billing.address) + + const callhookData = defaultAbiCoder.encode(['address'], [user2.address]) + const tx = billing + .connect(l2TokenGatewayMock.signer) + .onTokenTransfer(l1BillingConnectorMock.address, oneHundred, callhookData) + await expect(tx).emit(billing, 'TokensAdded').withArgs(user2.address, oneHundred) + + const afterUserBalance = await billing.userBalances(user2.address) + const afterContractBalance = await token.balanceOf(billing.address) + expect(beforeContractBalance).eq(afterContractBalance) // No tokens moved + expect(afterUserBalance).eq(beforeUserBalance.add(oneHundred)) // But balance increased + }) + + it('should fail add if not called by the token gateway', async function () { + const callhookData = defaultAbiCoder.encode(['address'], [user2.address]) + const tx = billing + .connect(user1.signer) + .onTokenTransfer(l1BillingConnectorMock.address, oneHundred, callhookData) + await expect(tx).revertedWith('Caller must be L2 token gateway') + }) + + it('should fail if called by the token gateway but the L1 sender is not the billing connector', async function () { + const callhookData = defaultAbiCoder.encode(['address'], [user2.address]) + const tx = billing.connect(l2TokenGatewayMock.signer).onTokenTransfer(user1.address, oneHundred, callhookData) + await expect(tx).revertedWith('Invalid L1 sender!') + }) + + it('should fail on overflow using built-in Solidity 0.8 safe math', async function () { + const callhookData = defaultAbiCoder.encode(['address'], [user2.address]) + const tx = billing + .connect(l2TokenGatewayMock.signer) + .onTokenTransfer(l1BillingConnectorMock.address, MaxUint256, callhookData) + await expect(tx).emit(billing, 'TokensAdded').withArgs(user2.address, MaxUint256) + + const tx2 = billing + .connect(l2TokenGatewayMock.signer) + .onTokenTransfer(l1BillingConnectorMock.address, oneHundred, callhookData) + await expect(tx2).revertedWithPanic(PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW) + }) + }) + }) + + describe('addToMany', function () { + it('should add many', async function () { + const beforeBalance = await token.balanceOf(billing.address) + + const users = [user1.address, user2.address] + const amounts = [toGRT('1000'), toGRT('2000')] + const totalAmount = amounts.reduce((a, b) => a.add(b), BigNumber.from(0)) + + const tx = billing.connect(user1.signer).addToMany(users, amounts) + await expect(tx) + .emit(billing, 'TokensAdded') + .withArgs(users[0], amounts[0]) + .emit(billing, 'TokensAdded') + .withArgs(users[1], amounts[1]) + + const afterBalance = await token.balanceOf(billing.address) + expect(afterBalance).eq(beforeBalance.add(totalAmount)) + }) + + it('reject add many to unequal lengths', async function () { + const users = [AddressZero, user2.address, user2.address] + const amounts = [toGRT('1000'), toGRT('2000')] + + const tx = billing.connect(user1.signer).addToMany(users, amounts) + await expect(tx).revertedWith('Lengths not equal') + }) + + it('reject add many to empty address', async function () { + const users = [AddressZero, user2.address] + const amounts = [toGRT('1000'), toGRT('2000')] + + const tx = billing.connect(user1.signer).addToMany(users, amounts) + await expect(tx).revertedWith('user != 0') + }) + + it('reject add many with empty amount', async function () { + const users = [user1.address, user2.address] + const amounts = [toGRT('0'), toGRT('2000')] + + const tx = billing.connect(user1.signer).addToMany(users, amounts) + await expect(tx).revertedWith('Must add more than 0') + }) + + it('reject add many with not enough approval', async function () { + const users = [user1.address, user2.address] + const amounts = [toGRT('1000'), toGRT('2000')] + + await token.connect(user1.signer).approve(billing.address, 0) + const tx = billing.connect(user1.signer).addToMany(users, amounts) + await expect(tx).revertedWith('ERC20: insufficient allowance') + }) + }) + + describe('remove', function () { + it('should remove', async function () { + await billing.connect(user1.signer).add(oneHundred) + const beforeRemove = await billing.userBalances(user1.address) + const tx = billing.connect(user1.signer).remove(user1.address, oneHundred) + await expect(tx).emit(billing, 'TokensRemoved').withArgs(user1.address, user1.address, oneHundred) + const afterRemove = await billing.userBalances(user1.address) + expect(afterRemove).eq(beforeRemove.sub(oneHundred)) + }) + + it('should fail on removing too much', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(user1.signer).remove(user1.address, oneMillion) + await expect(tx).revertedWith('Too much removed') + }) + + it('should fail on removing zero', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(user1.signer).remove(user1.address, toBN(0)) + await expect(tx).revertedWith('Must remove more than 0') + }) + + it('should fail on removing to the zero address', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(user1.signer).remove(AddressZero, oneHundred) + await expect(tx).revertedWith('destination != 0') + }) + }) + + describe('removeFromL1', function () { + context('BillingConnector not set', function () { + it('rejects calls if the billing connector was never set', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(l1BillingConnectorMockAlias).removeFromL1(user1.address, user2.address, oneHundred) + await expect(tx).revertedWith('BillingConnector not set') + }) + }) + context('BillingConnector properly set', function () { + beforeEach(async function () { + await billing.connect(governor.signer).setL1BillingConnector(l1BillingConnectorMock.address) + }) + + it('should remove using a message from L1', async function () { + await billing.connect(user1.signer).add(oneHundred) + const beforeRemoveBalance = await billing.userBalances(user1.address) + const beforeRemoveTokens = await token.balanceOf(user2.address) + const tx = billing.connect(l1BillingConnectorMockAlias).removeFromL1(user1.address, user2.address, oneHundred) + await expect(tx).emit(billing, 'TokensRemoved').withArgs(user1.address, user2.address, oneHundred) + const afterRemoveBalance = await billing.userBalances(user1.address) + const afterRemoveTokens = await token.balanceOf(user2.address) + expect(afterRemoveBalance).eq(beforeRemoveBalance.sub(oneHundred)) + expect(afterRemoveTokens).eq(beforeRemoveTokens.add(oneHundred)) + }) + + it('rejects calls from the unaliased L1 billing connector address', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(l1BillingConnectorMock.signer).removeFromL1(user1.address, user2.address, oneHundred) + await expect(tx).revertedWith('Caller must be L1 BillingConnector') + }) + + it('rejects calls from someone who is not the billing connector', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(user1.signer).removeFromL1(user1.address, user2.address, oneHundred) + await expect(tx).revertedWith('Caller must be L1 BillingConnector') + }) + + it('should emit an event when trying to remove too much', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing + .connect(l1BillingConnectorMockAlias) + .removeFromL1(user1.address, user2.address, oneHundred.add(1)) + await expect(tx) + .emit(billing, 'InsufficientBalanceForRemoval') + .withArgs(user1.address, user2.address, oneHundred.add(1)) + }) + + it('should fail on removing zero (even though it should never happen)', async function () { + // BillingConnector should never send this message, but we still wanna test the behavior is correct + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(l1BillingConnectorMockAlias).removeFromL1(user1.address, user2.address, toBN(0)) + await expect(tx).revertedWith('Must remove more than 0') + }) + + it('should fail on removing to the zero address (even though it should never happen)', async function () { + // BillingConnector should never send this message, but we still wanna test the behavior is correct + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(l1BillingConnectorMockAlias).removeFromL1(user1.address, AddressZero, oneHundred) + await expect(tx).revertedWith('destination != 0') + }) + }) + }) + + describe('pull', function () { + it('should pull', async function () { + const collectorBalanceBefore = await token.balanceOf(collector1.address) + const addBefore = await billing.userBalances(user1.address) + + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(collector1.signer).pull(user1.address, oneHundred, collector1.address) + await expect(tx).emit(billing, 'TokensPulled').withArgs(user1.address, oneHundred) + + const collectorBalanceAfter = await token.balanceOf(collector1.address) + const addAfter = await billing.userBalances(user1.address) + expect(collectorBalanceAfter).eq(collectorBalanceBefore.add(oneHundred)) + expect(addAfter).eq(addBefore) // What was added was then removed + }) + + it('should not revert when pullable amount is zero', async function () { + const collectorBalanceBefore = await token.balanceOf(collector1.address) + const addBefore = await billing.userBalances(user1.address) + + await billing.connect(user1.signer).add(oneHundred) + await billing.connect(user1.signer).remove(user1.address, oneHundred) // Right before the pull! + const tx = billing.connect(collector1.signer).pull(user1.address, oneHundred, collector1.address) + await expect(tx).to.not.emit(billing, 'TokensPulled') + + const collectorBalanceAfter = await token.balanceOf(collector1.address) + const addAfter = await billing.userBalances(user1.address) + expect(collectorBalanceBefore).eq(collectorBalanceAfter) + expect(addBefore).eq(addAfter) + }) + + it('should fail on pull when not collector', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(me.signer).pull(user1.address, oneHundred, collector1.address) + await expect(tx).revertedWith('Caller must be Collector') + }) + + it('should fail pull on empty destination address', async function () { + await billing.connect(user1.signer).add(oneHundred) + const tx = billing.connect(collector1.signer).pull(user1.address, oneHundred, AddressZero) + await expect(tx).revertedWith('Cannot transfer to empty address') + }) + }) + + describe('pullMany', function () { + it('should pull many', async function () { + await billing.connect(user1.signer).add(oneHundred) + await billing.connect(user2.signer).add(oneHundred) + const addBefore1 = await billing.userBalances(user1.address) + const addBefore2 = await billing.userBalances(user2.address) + const collectorBalanceBefore = await token.balanceOf(collector1.address) + + await billing + .connect(collector1.signer) + .pullMany([user1.address, user2.address], [oneHundred, oneHundred], collector1.address) + + const addAfter1 = await billing.userBalances(user1.address) + const addAfter2 = await billing.userBalances(user2.address) + const collectorBalanceAfter = await token.balanceOf(collector1.address) + + expect(collectorBalanceAfter).eq(collectorBalanceBefore.add(oneHundred).add(oneHundred)) + expect(addAfter1).eq(addBefore1.sub(oneHundred)) + expect(addAfter2).eq(addBefore2.sub(oneHundred)) + }) + + it('should pull many even with partial user balances', async function () { + await billing.connect(user1.signer).add(oneHundred) + await billing.connect(user2.signer).add(oneHundred) + const addBefore1 = await billing.userBalances(user1.address) + const addBefore2 = await billing.userBalances(user2.address) + const collectorBalanceBefore = await token.balanceOf(collector1.address) + + await billing + .connect(collector1.signer) + .pullMany([user1.address, user2.address], [oneHundred, oneHundred.mul(2)], collector1.address) + + const addAfter1 = await billing.userBalances(user1.address) + const addAfter2 = await billing.userBalances(user2.address) + const collectorBalanceAfter = await token.balanceOf(collector1.address) + + expect(collectorBalanceAfter).eq(collectorBalanceBefore.add(oneHundred).add(oneHundred)) + expect(addAfter1).eq(addBefore1.sub(oneHundred)) + expect(addAfter2).eq(addBefore2.sub(oneHundred)) + }) + + it('should fail to pull when not a collector', async function () { + await billing.connect(user1.signer).add(oneHundred) + await billing.connect(user2.signer).add(oneHundred) + + const tx = billing + .connect(me.signer) + .pullMany([user1.address, user2.address], [oneHundred, oneHundred], collector1.address) + await expect(tx).rejectedWith('Caller must be Collector') + }) + + it('should fail pull on lengths not equal', async function () { + await billing.connect(user1.signer).add(oneHundred) + await billing.connect(user2.signer).add(oneHundred) + const tx = billing + .connect(collector1.signer) + .pullMany([user1.address], [oneHundred, oneHundred], collector1.address) + await expect(tx).revertedWith('Lengths not equal') + }) + + it('should fail pull many on empty destination address', async function () { + await billing.connect(user1.signer).add(oneHundred) + await billing.connect(user2.signer).add(oneHundred) + const tx = billing + .connect(collector1.signer) + .pullMany([user1.address, user2.address], [oneHundred, oneHundred], AddressZero) + await expect(tx).revertedWith('Cannot transfer to empty address') + }) + }) + + describe('rescue', function () { + it('should rescue tokens', async function () { + // deploy token2 and accidentally send to the Billing contract + const token2 = await deployment.deployToken([tenBillion], me.signer, true) + await token2.connect(me.signer).transfer(user1.address, oneMillion) + await token2.connect(user1.signer).transfer(billing.address, oneMillion) + + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billing.address, oneMillion) + + const tokenBeforeUser = await token.balanceOf(user1.address) + const token2BeforeUser = await token2.balanceOf(user1.address) + const tokenBeforeBilling = await token.balanceOf(billing.address) + const token2BeforeBilling = await token2.balanceOf(billing.address) + + const tx = await billing.connect(governor.signer).rescueTokens(user1.address, token.address, oneMillion) + await expect(tx).emit(billing, 'TokensRescued').withArgs(user1.address, token.address, oneMillion) + await billing.connect(governor.signer).rescueTokens(user1.address, token2.address, oneMillion) + + const tokenAfterUser = await token.balanceOf(user1.address) + const token2AfterUser = await token2.balanceOf(user1.address) + const tokenAfterBilling = await token.balanceOf(billing.address) + const token2AfterBilling = await token2.balanceOf(billing.address) + + expect(tokenAfterUser).eq(tokenBeforeUser.add(oneMillion)) + expect(token2AfterUser).eq(token2BeforeUser.add(oneMillion)) + expect(tokenAfterBilling).eq(tokenBeforeBilling.sub(oneMillion)) + expect(token2AfterBilling).eq(token2BeforeBilling.sub(oneMillion)) + }) + + it('should fail rescue tokens when not the governor', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billing.address, oneMillion) + const tx = billing.connect(user1.signer).rescueTokens(user1.address, token.address, oneMillion) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should fail when trying to send to address zero', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billing.address, oneMillion) + const tx = billing.connect(governor.signer).rescueTokens(AddressZero, token.address, oneMillion) + await expect(tx).revertedWith('Cannot send to address(0)') + }) + + it('should fail when trying to send zero tokens', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billing.address, oneMillion) + const tx = billing.connect(governor.signer).rescueTokens(user1.address, token.address, toBN(0)) + await expect(tx).revertedWith('Cannot rescue 0 tokens') + }) + }) +}) diff --git a/packages/billing/test/billingConnector.test.ts b/packages/billing/test/billingConnector.test.ts new file mode 100644 index 000000000..2be1885a9 --- /dev/null +++ b/packages/billing/test/billingConnector.test.ts @@ -0,0 +1,736 @@ +import '@nomicfoundation/hardhat-chai-matchers' +import hre from 'hardhat' +import { expect } from 'chai' +import { BigNumber, constants, Signature } from 'ethers' +import * as deployment from '../utils/deploy' +import { getAccounts, Account, toGRT, toBN, applyL1ToL2Alias } from '../utils/helpers' + +import { BillingConnector, L1TokenGatewayMock, Token, InboxMock, BridgeMock } from '../build/types' + +const { AddressZero, MaxUint256 } = constants +import { eip712 } from '@graphprotocol/common-ts/dist/attestations' + +import path from 'path' +import { Artifacts } from 'hardhat/internal/artifacts' +import { + BytesLike, + defaultAbiCoder, + hexDataLength, + Interface, + keccak256, + SigningKey, + solidityPack, +} from 'ethers/lib/utils' + +const ARTIFACTS_PATH = path.resolve('build/artifacts/contracts') +const artifacts = new Artifacts(ARTIFACTS_PATH) +const billingAbi = artifacts.readArtifactSync('Billing').abi +const billingInterface = new Interface(billingAbi) + +// Permit tools adapted from https://github.com/graphprotocol/contracts/blob/0ac3f53b1898e8bbcfb909b7362fec2ffb498b65/test/lib/graphTokenTests.ts#L20-L87 + +const PERMIT_TYPE_HASH = eip712.typeHash( + 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)', +) +const SALT = '0x372eb1ffe347bfa68969a6e8193fe5f811f0ac923bde07eb35d7e74347ba031a' + +interface Permit { + owner: string + spender: string + value: BigNumber + nonce: BigNumber + deadline: BigNumber +} + +function hashEncodePermit(permit: Permit) { + return eip712.hashStruct( + PERMIT_TYPE_HASH, + ['address', 'address', 'uint256', 'uint256', 'uint256'], + [permit.owner, permit.spender, permit.value, permit.nonce, permit.deadline], + ) +} + +function signPermit( + privateKey: BytesLike, + chainId: number, + contractAddress: string, + permit: Permit, + salt: string, +): Signature { + const domainSeparator = eip712.domainSeparator({ + name: 'Graph Token', + version: '0', + chainId, + verifyingContract: contractAddress, + salt: salt, + }) + const hashEncodedPermit = hashEncodePermit(permit) + const message = eip712.encode(domainSeparator, hashEncodedPermit) + const messageHash = keccak256(message) + const signingKey = new SigningKey(privateKey) + return signingKey.signDigest(messageHash) +} + +describe('BillingConnector', () => { + let me: Account + let user1: Account + let user2: Account + let user3: Account + let governor: Account + let l2BillingMock: Account + + let billingConnector: BillingConnector + let token: Token + let l1TokenGatewayMock: L1TokenGatewayMock + let bridgeMock: BridgeMock + let inboxMock: InboxMock + + const myPrivateKey = '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d' + async function permitOK(value: BigNumber, from: string, to: string): Promise { + const nonce = await token.nonces(from) + return { + owner: from, + spender: to, + value: value, + nonce: nonce, + deadline: toBN('0'), + } + } + + async function permitMaxOK(from: string, to: string): Promise { + return permitOK(MaxUint256, from, to) + } + + async function permitExpired(from: string, to: string): Promise { + const permit = await permitMaxOK(from, to) + permit.deadline = toBN('1') + return permit + } + + function createSignedPermit(permit: Permit, privateKey: string, salt: string): Signature { + const chainID = hre.network.config.chainId + const signature: Signature = signPermit(privateKey, chainID, token.address, permit, salt) + return signature + } + + before(async function () { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;[me, user1, user2, user3, governor, l2BillingMock] = await getAccounts() + }) + + const tenBillion = toGRT('10000000000') + const oneHundred = toGRT('100') + const oneMillion = toGRT('1000000') + const defaultMaxGas = toBN('10') + const defaultGasPriceBid = toBN('20') + const defaultMaxSubmissionPrice = toBN('30') + const defaultMsgValue = defaultMaxSubmissionPrice.add(defaultMaxGas.mul(defaultGasPriceBid)) + + const deployArbitrumMocks = async function () { + bridgeMock = (await deployment.deployBridgeMock([], me.signer, true)) as unknown as BridgeMock + inboxMock = (await deployment.deployInboxMock([], me.signer, true)) as unknown as InboxMock + await bridgeMock.connect(me.signer).setInbox(inboxMock.address, true) + await inboxMock.connect(me.signer).setBridge(bridgeMock.address) + } + + beforeEach(async function () { + await deployArbitrumMocks() + token = (await deployment.deployToken([tenBillion], me.signer, true)) as unknown as Token + l1TokenGatewayMock = (await deployment.deployL1TokenGatewayMock( + [], + me.signer, + true, + )) as unknown as L1TokenGatewayMock + billingConnector = (await deployment.deployBillingConnector( + [l1TokenGatewayMock.address, l2BillingMock.address, token.address, governor.address, inboxMock.address], + me.signer, + true, + )) as unknown as BillingConnector + + await token.connect(me.signer).transfer(user1.address, oneMillion) + await token.connect(me.signer).transfer(user2.address, oneMillion) + await token.connect(me.signer).transfer(user3.address, oneMillion) + }) + + describe('setL1TokenGateway', function () { + it('sets the l1TokenGateway', async function () { + expect(await billingConnector.l1TokenGateway()).eq(l1TokenGatewayMock.address) + const tx = billingConnector.connect(governor.signer).setL1TokenGateway(user2.address) + await expect(tx).emit(billingConnector, 'L1TokenGatewayUpdated').withArgs(user2.address) + expect(await billingConnector.l1TokenGateway()).eq(user2.address) + }) + it('rejects calls from someone other than the governor', async function () { + const tx = billingConnector.connect(me.signer).setL1TokenGateway(user2.address) + await expect(tx).revertedWith('Only Governor can call') + }) + it('rejects calls to set token gateway to zero', async function () { + const tx = billingConnector.connect(governor.signer).setL1TokenGateway(AddressZero) + await expect(tx).revertedWith('L1 Token Gateway cannot be 0') + }) + }) + describe('setL2Billing', function () { + it('sets the l2Billing address', async function () { + expect(await billingConnector.l2Billing()).eq(l2BillingMock.address) + const tx = billingConnector.connect(governor.signer).setL2Billing(user3.address) + await expect(tx).emit(billingConnector, 'L2BillingUpdated').withArgs(user3.address) + expect(await billingConnector.l2Billing()).eq(user3.address) + }) + it('rejects calls from someone other than the governor', async function () { + const tx = billingConnector.connect(me.signer).setL2Billing(user3.address) + await expect(tx).revertedWith('Only Governor can call') + }) + it('rejects calls to set L2 billing to zero', async function () { + const tx = billingConnector.connect(governor.signer).setL2Billing(AddressZero) + await expect(tx).revertedWith('L2 Billing cannot be zero') + }) + }) + describe('setArbitrumInbox', function () { + it('sets the inbox address', async function () { + expect(await billingConnector.inbox()).eq(inboxMock.address) + const tx = billingConnector.connect(governor.signer).setArbitrumInbox(user3.address) + await expect(tx).emit(billingConnector, 'ArbitrumInboxUpdated').withArgs(user3.address) + expect(await billingConnector.inbox()).eq(user3.address) + }) + it('rejects calls from someone other than the governor', async function () { + const tx = billingConnector.connect(me.signer).setArbitrumInbox(user3.address) + await expect(tx).revertedWith('Only Governor can call') + }) + it('rejects calls to set Arbitrum Inbox to zero', async function () { + const tx = billingConnector.connect(governor.signer).setArbitrumInbox(AddressZero) + await expect(tx).revertedWith('Arbitrum Inbox cannot be zero') + }) + }) + describe('addToL2', function () { + it('pulls tokens and sends a message through the token gateway', async function () { + const userBalanceBefore = await token.balanceOf(user1.address) + const connectorBalanceBefore = await token.balanceOf(billingConnector.address) + const bridgeBalanceBefore = await token.balanceOf(l1TokenGatewayMock.address) + await token.connect(user1.signer).approve(billingConnector.address, oneHundred) + const tx = billingConnector + .connect(user1.signer) + .addToL2(user2.address, oneHundred, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + const expectedCallhookData = defaultAbiCoder.encode(['address'], [user2.address]) + const expectedOutboundCalldata = l1TokenGatewayMock.interface.encodeFunctionData('finalizeInboundTransfer', [ + token.address, + billingConnector.address, + l2BillingMock.address, + oneHundred, + defaultAbiCoder.encode(['bytes', 'bytes'], ['0x', expectedCallhookData]), + ]) + // Real event emitted by BillingConnector + await expect(tx).emit(billingConnector, 'TokensSentToL2').withArgs(user1.address, user2.address, oneHundred) + // Mock event from the gateway mock to validate what would be sent to L2 + await expect(tx) + .emit(l1TokenGatewayMock, 'FakeTxToL2') + .withArgs( + billingConnector.address, + defaultMsgValue, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + expectedOutboundCalldata, + ) + const userBalanceAfter = await token.balanceOf(user1.address) + const connectorBalanceAfter = await token.balanceOf(billingConnector.address) + const bridgeBalanceAfter = await token.balanceOf(l1TokenGatewayMock.address) + expect(userBalanceAfter).eq(userBalanceBefore.sub(oneHundred)) + expect(connectorBalanceAfter).eq(connectorBalanceBefore) + expect(bridgeBalanceAfter).eq(bridgeBalanceBefore.add(oneHundred)) + }) + it('rejects calls when the sender has not approved the contract', async function () { + const tx = billingConnector + .connect(user1.signer) + .addToL2(user2.address, oneHundred, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('ERC20: insufficient allowance') + }) + it('rejects calls if the sender is out of funds', async function () { + await token.connect(user1.signer).approve(billingConnector.address, tenBillion) + const tx = billingConnector + .connect(user1.signer) + .addToL2(user2.address, tenBillion, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('ERC20: transfer amount exceeds balance') + }) + it('rejects sending to address zero', async function () { + await token.connect(user1.signer).approve(billingConnector.address, oneHundred) + const tx = billingConnector + .connect(user1.signer) + .addToL2(AddressZero, oneHundred, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('destination != 0') + }) + it('rejects sending a zero amount', async function () { + await token.connect(user1.signer).approve(billingConnector.address, oneHundred) + const tx = billingConnector + .connect(user1.signer) + .addToL2(user2.address, toBN(0), defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('Must add more than 0') + }) + it("relies on the gateway's validation for the L2 gas params", async function () { + await token.connect(user1.signer).approve(billingConnector.address, oneHundred) + const tx = billingConnector + .connect(user1.signer) + .addToL2(user2.address, oneHundred, defaultMaxGas, defaultGasPriceBid, toBN(0), { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('NO_SUBMISSION_COST') + }) + }) + describe('addToL2WithPermit', function () { + it('pulls tokens using a permit and sends a message through the token gateway', async function () { + // Note the permit is from me to billingConnector, but the addToL2WithPermit tx is signed by user1 + const permit = await permitOK(oneHundred, me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const myBalanceBefore = await token.balanceOf(me.address) + const userBalanceBefore = await token.balanceOf(user1.address) + const connectorBalanceBefore = await token.balanceOf(billingConnector.address) + const bridgeBalanceBefore = await token.balanceOf(l1TokenGatewayMock.address) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + oneHundred, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + const expectedCallhookData = defaultAbiCoder.encode(['address'], [me.address]) + const expectedOutboundCalldata = l1TokenGatewayMock.interface.encodeFunctionData('finalizeInboundTransfer', [ + token.address, + billingConnector.address, + l2BillingMock.address, + oneHundred, + defaultAbiCoder.encode(['bytes', 'bytes'], ['0x', expectedCallhookData]), + ]) + // Real event emitted by BillingConnector + await expect(tx).emit(billingConnector, 'TokensSentToL2').withArgs(me.address, me.address, oneHundred) + // Mock event from the gateway mock to validate what would be sent to L2 + await expect(tx) + .emit(l1TokenGatewayMock, 'FakeTxToL2') + .withArgs( + billingConnector.address, + defaultMsgValue, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + expectedOutboundCalldata, + ) + const myBalanceAfter = await token.balanceOf(me.address) + const userBalanceAfter = await token.balanceOf(user1.address) + const connectorBalanceAfter = await token.balanceOf(billingConnector.address) + const bridgeBalanceAfter = await token.balanceOf(l1TokenGatewayMock.address) + expect(myBalanceAfter).eq(myBalanceBefore.sub(oneHundred)) + expect(userBalanceAfter).eq(userBalanceBefore) // unchanged + expect(connectorBalanceAfter).eq(connectorBalanceBefore) + expect(bridgeBalanceAfter).eq(bridgeBalanceBefore.add(oneHundred)) + }) + it("doesn't revert after a frontrunning permit transaction", async function () { + const permit = await permitOK(oneHundred, me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const myBalanceBefore = await token.balanceOf(me.address) + const userBalanceBefore = await token.balanceOf(user1.address) + const connectorBalanceBefore = await token.balanceOf(billingConnector.address) + const bridgeBalanceBefore = await token.balanceOf(l1TokenGatewayMock.address) + + // user2 frontruns transaction + const frontrunnerTx = await token + .connect(user2.signer) + .permit( + me.address, + billingConnector.address, + permit.value, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + ) + + // my original transaction with permit + const tx = await billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + + const expectedCallhookData = defaultAbiCoder.encode(['address'], [me.address]) + const expectedOutboundCalldata = l1TokenGatewayMock.interface.encodeFunctionData('finalizeInboundTransfer', [ + token.address, + billingConnector.address, + l2BillingMock.address, + oneHundred, + defaultAbiCoder.encode(['bytes', 'bytes'], ['0x', expectedCallhookData]), + ]) + + // frontrunnerTx approves funds + await expect(frontrunnerTx) + .to.emit(token, 'Approval') + .withArgs(me.address, billingConnector.address, permit.value) + + // Transaction doesn't revert, instead it uses the allowance created by frontrunnerTx + await expect(tx).not.to.be.reverted + + // Real event emitted by BillingConnector + await expect(tx).emit(billingConnector, 'TokensSentToL2').withArgs(me.address, me.address, oneHundred) + // Mock event from the gateway mock to validate what would be sent to L2 + await expect(tx) + .emit(l1TokenGatewayMock, 'FakeTxToL2') + .withArgs( + billingConnector.address, + defaultMsgValue, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + expectedOutboundCalldata, + ) + + // Balances are fine since transaction was executed fine + const myBalanceAfter = await token.balanceOf(me.address) + const userBalanceAfter = await token.balanceOf(user1.address) + const connectorBalanceAfter = await token.balanceOf(billingConnector.address) + const bridgeBalanceAfter = await token.balanceOf(l1TokenGatewayMock.address) + expect(myBalanceAfter).eq(myBalanceBefore.sub(oneHundred)) + expect(userBalanceAfter).eq(userBalanceBefore) // unchanged + expect(connectorBalanceAfter).eq(connectorBalanceBefore) + expect(bridgeBalanceAfter).eq(bridgeBalanceBefore.add(oneHundred)) + }) + it("relies on the token's validation when the sender has provided an expired permit", async function () { + const permit = await permitExpired(me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('GRT: expired permit') + }) + it("relies on the token's validation when the sender has provided an invalid permit", async function () { + const permit = await permitOK(oneHundred, user1.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('GRT: invalid permit') + }) + it('rejects calls if the sender is out of funds', async function () { + const permit = await permitOK(tenBillion, me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('ERC20: transfer amount exceeds balance') + }) + it('rejects sending to address zero', async function () { + const permit = await permitOK(oneHundred, me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + AddressZero, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('destination != 0') + }) + it('rejects sending a zero amount', async function () { + const permit = await permitOK(toBN(0), me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('Must add more than 0') + }) + it("relies on the gateway's validation for the L2 gas params", async function () { + const permit = await permitOK(oneHundred, me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(me.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid.add(1), + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('WRONG_ETH_VALUE') + }) + it('rejects calls if not tokens owner', async function () { + const permit = await permitOK(oneHundred, me.address, billingConnector.address) + const signedPermit = createSignedPermit(permit, myPrivateKey, SALT) + + const tx = billingConnector + .connect(user1.signer) + .addToL2WithPermit( + me.address, + permit.value, + defaultMaxGas, + defaultGasPriceBid, + defaultMaxSubmissionPrice, + permit.deadline, + signedPermit.v, + signedPermit.r, + signedPermit.s, + { + value: defaultMsgValue, + }, + ) + await expect(tx).revertedWith('Only tokens owner can call') + }) + }) + describe('rescueTokens', function () { + it('should rescue tokens', async function () { + // deploy token2 and accidentally send to the Billing contract + const token2 = await deployment.deployToken([tenBillion], me.signer, true) + await token2.connect(me.signer).transfer(user1.address, oneMillion) + await token2.connect(user1.signer).transfer(billingConnector.address, oneMillion) + + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billingConnector.address, oneMillion) + + const tokenBeforeUser = await token.balanceOf(user1.address) + const token2BeforeUser = await token2.balanceOf(user1.address) + const tokenBeforeBilling = await token.balanceOf(billingConnector.address) + const token2BeforeBilling = await token2.balanceOf(billingConnector.address) + + const tx = await billingConnector.connect(governor.signer).rescueTokens(user1.address, token.address, oneMillion) + await expect(tx).emit(billingConnector, 'TokensRescued').withArgs(user1.address, token.address, oneMillion) + await billingConnector.connect(governor.signer).rescueTokens(user1.address, token2.address, oneMillion) + + const tokenAfterUser = await token.balanceOf(user1.address) + const token2AfterUser = await token2.balanceOf(user1.address) + const tokenAfterBilling = await token.balanceOf(billingConnector.address) + const token2AfterBilling = await token2.balanceOf(billingConnector.address) + + expect(tokenAfterUser).eq(tokenBeforeUser.add(oneMillion)) + expect(token2AfterUser).eq(token2BeforeUser.add(oneMillion)) + expect(tokenAfterBilling).eq(tokenBeforeBilling.sub(oneMillion)) + expect(token2AfterBilling).eq(token2BeforeBilling.sub(oneMillion)) + }) + + it('should fail rescue tokens when not governor', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billingConnector.address, oneMillion) + const tx = billingConnector.connect(user1.signer).rescueTokens(user1.address, token.address, oneMillion) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should fail when trying to send to address zero', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billingConnector.address, oneMillion) + const tx = billingConnector.connect(governor.signer).rescueTokens(AddressZero, token.address, oneMillion) + await expect(tx).revertedWith('Cannot send to address(0)') + }) + + it('should fail when trying to send zero tokens', async function () { + // the bad transfer of GRT + await token.connect(user1.signer).transfer(billingConnector.address, oneMillion) + const tx = billingConnector.connect(governor.signer).rescueTokens(user1.address, token.address, toBN(0)) + await expect(tx).revertedWith('Cannot rescue 0 tokens') + }) + }) + describe('removeOnL2', function () { + const submitRetryableTxCode = 9 + // createMsgData and createInboxAccsEntry copied/adapted from @graphprotocol/contracts + const createMsgData = function (user: string, msgCalldata: string) { + const msgData = solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + toBN(l2BillingMock.address), + toBN('0'), + defaultMsgValue, + defaultMaxSubmissionPrice, + applyL1ToL2Alias(user), + applyL1ToL2Alias(user), + defaultMaxGas, + defaultGasPriceBid, + hexDataLength(msgCalldata), + msgCalldata, + ], + ) + return msgData + } + const createInboxAccsEntry = function (msgDataHash: string) { + // The real bridge would emit the InboxAccs entry that came before this one, but our mock + // emits this, making it easier for us to validate here that all the parameters we sent are correct + const expectedInboxAccsEntry = keccak256( + solidityPack( + ['address', 'uint8', 'address', 'bytes32'], + [inboxMock.address, submitRetryableTxCode, billingConnector.address, msgDataHash], + ), + ) + return expectedInboxAccsEntry + } + + it('sends a message through the Arbitrum inbox for the L2 Billing to remove tokens', async function () { + // "me" sends the tokens from L2 Billing to user2. + const tx = billingConnector + .connect(me.signer) + .removeOnL2(user2.address, oneHundred, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + const expectedCalldata = billingInterface.encodeFunctionData('removeFromL1', [ + me.address, + user2.address, + oneHundred, + ]) + await expect(tx).emit(billingConnector, 'RemovalRequestSentToL2').withArgs(me.address, user2.address, oneHundred) + // We set the refund address to the destination address, because it's assumed the source address might not exist in L2 + await expect(tx) + .emit(billingConnector, 'TxToL2') + .withArgs(user2.address, l2BillingMock.address, 1, expectedCalldata) + + const msgData = createMsgData(user2.address, expectedCalldata) + const msgDataHash = keccak256(msgData) + const expectedInboxAccsEntry = createInboxAccsEntry(msgDataHash) + + const expectedSeqNum = 1 + await expect(tx).emit(inboxMock, 'InboxMessageDelivered').withArgs(expectedSeqNum, msgData) + await expect(tx) + .emit(bridgeMock, 'MessageDelivered') + .withArgs( + expectedSeqNum, + expectedInboxAccsEntry, + inboxMock.address, + submitRetryableTxCode, + billingConnector.address, + msgDataHash, + ) + }) + it('rejects calls with a zero maxSubmissionCost', async function () { + // "me" sends the tokens from L2 Billing to user2. + const tx = billingConnector + .connect(me.signer) + .removeOnL2(user2.address, oneHundred, defaultMaxGas, defaultGasPriceBid, toBN(0), { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('Submission cost must be > 0') + }) + it('rejects calls to remove zero tokens', async function () { + // "me" sends the tokens from L2 Billing to user2. + const tx = billingConnector + .connect(me.signer) + .removeOnL2(user2.address, toBN(0), defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('Must remove more than 0') + }) + it('rejects calls to remove tokens to the zero address', async function () { + // "me" sends the tokens from L2 Billing to user2. + const tx = billingConnector + .connect(me.signer) + .removeOnL2(AddressZero, oneHundred, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('destination != 0') + }) + it('rejects calls to remove tokens to the sender address', async function () { + // "me" sends the tokens from L2 Billing to user2. + const tx = billingConnector + .connect(me.signer) + .removeOnL2(me.address, oneHundred, defaultMaxGas, defaultGasPriceBid, defaultMaxSubmissionPrice, { + value: defaultMsgValue, + }) + await expect(tx).revertedWith('destination != sender') + }) + }) +}) diff --git a/packages/billing/test/governed.test.ts b/packages/billing/test/governed.test.ts new file mode 100644 index 000000000..7d9b79119 --- /dev/null +++ b/packages/billing/test/governed.test.ts @@ -0,0 +1,59 @@ +import { expect } from 'chai' +import hre from 'hardhat' +import '@nomicfoundation/hardhat-chai-matchers' + +import { Governed } from '../build/types/contracts/Governed' + +import { getAccounts, Account } from '../utils/helpers' + +const { ethers } = hre +const { AddressZero } = ethers.constants + +describe('Governed', () => { + let me: Account + let governor: Account + + let governed: Governed + + beforeEach(async function () { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;[me, governor] = await getAccounts() + + const factory = await ethers.getContractFactory('Governed') + governed = (await factory.connect(governor.signer).deploy(governor.address)) as unknown as Governed + }) + + it('should reject deployment if governor is zero', async function () { + const factory = await ethers.getContractFactory('Governed') + const unsignedTx = await factory.getDeployTransaction(AddressZero) + const tx = governor.signer.sendTransaction(unsignedTx) + await expect(tx).revertedWith('Governor must not be 0') + }) + + it('should reject transfer if not allowed', async function () { + const tx = governed.connect(me.signer).transferOwnership(me.address) + await expect(tx).revertedWith('Only Governor can call') + }) + + it('should reject transfer to zero address', async function () { + // Transfer ownership + const tx1 = governed.connect(governor.signer).transferOwnership(AddressZero) + await expect(tx1).rejectedWith('Governor must be set') + }) + + it('should transfer and accept', async function () { + // Transfer ownership + const tx1 = governed.connect(governor.signer).transferOwnership(me.address) + await expect(tx1).emit(governed, 'NewPendingOwnership').withArgs(AddressZero, me.address) + + // Reject accept if not the pending governor + await expect(governed.connect(governor.signer).acceptOwnership()).revertedWith('Caller must be pending governor') + + // Accept ownership + const tx2 = governed.connect(me.signer).acceptOwnership() + await expect(tx2).emit(governed, 'NewOwnership').withArgs(governor.address, me.address) + + // Clean pending governor + expect(await governed.pendingGovernor()).eq(AddressZero) + }) +}) diff --git a/packages/billing/tsconfig.json b/packages/billing/tsconfig.json new file mode 100644 index 000000000..3e98a5651 --- /dev/null +++ b/packages/billing/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "lib": ["ES2018", "dom"], + "module": "commonjs", + "moduleResolution": "node", + "target": "ES2018", + "outDir": "dist", + "resolveJsonModule": true, + "esModuleInterop": true + }, + "exclude": ["dist", "node_modules"], + "include": ["./hardhat.config.ts", "./scripts/**/*.ts", "./test/**/*.ts", "./build/types"] +} diff --git a/packages/billing/utils/addressBook.ts b/packages/billing/utils/addressBook.ts new file mode 100644 index 000000000..ca987cead --- /dev/null +++ b/packages/billing/utils/addressBook.ts @@ -0,0 +1,14 @@ +import fs from 'fs' + +export const getAddressBook = (path: string, chainId: string): any => { + if (!path) throw new Error(`A path to the address book file is required.`) + if (!chainId) throw new Error(`A chainId is required.`) + + const addressBook = JSON.parse(fs.readFileSync(path, 'utf8') || '{}') + + if (!addressBook[chainId]) { + addressBook[chainId] = {} + } + + return addressBook +} diff --git a/packages/billing/utils/artifacts.ts b/packages/billing/utils/artifacts.ts new file mode 100644 index 000000000..048249777 --- /dev/null +++ b/packages/billing/utils/artifacts.ts @@ -0,0 +1,22 @@ +import path from 'path' +import { Artifacts } from 'hardhat/internal/artifacts' +import { LinkReferences } from 'hardhat/types' +import { utils } from 'ethers' + +type Abi = Array + +type Artifact = { + contractName: string + abi: Abi + bytecode: string + deployedBytecode: string + linkReferences?: LinkReferences + deployedLinkReferences?: LinkReferences +} + +const ARTIFACTS_PATH = path.resolve('build/artifacts') +const artifacts = new Artifacts(ARTIFACTS_PATH) + +export const loadArtifact = (name: string): Artifact => { + return artifacts.readArtifactSync(name) +} diff --git a/packages/billing/utils/contracts.ts b/packages/billing/utils/contracts.ts new file mode 100644 index 000000000..182e8b03a --- /dev/null +++ b/packages/billing/utils/contracts.ts @@ -0,0 +1,61 @@ +import { providers, Signer, Contract } from 'ethers' +import { logger } from './logging' +import { loadArtifact } from './artifacts' +import { Billing, BillingConnector, IERC20WithPermit } from '../build/types' + +export interface BillingContracts { + Billing?: Billing + BillingConnector?: BillingConnector + Token?: IERC20WithPermit +} + +export const getContractAt = ( + name: string, + address: string, + signerOrProvider?: Signer | providers.Provider, +): Contract => { + return new Contract(address, loadArtifact(name).abi, signerOrProvider) +} + +export const loadContracts = ( + billingAddress: string | undefined, + billingConnectorAddress: string | undefined, + tokenAddress: string | undefined, + banxaAddress: string | undefined, + signerOrProvider?: Signer | providers.Provider, +): BillingContracts => { + const contracts = {} + try { + if (billingAddress) { + const billing = getContractAt('Billing', billingAddress) + contracts['Billing'] = billing + if (signerOrProvider) { + contracts['Billing'] = contracts['Billing'].connect(signerOrProvider) + } + } + if (billingConnectorAddress) { + const billingConnector = getContractAt('BillingConnector', billingConnectorAddress) + contracts['BillingConnector'] = billingConnector + if (signerOrProvider) { + contracts['BillingConnector'] = contracts['BillingConnector'].connect(signerOrProvider) + } + } + if (tokenAddress) { + const token = getContractAt('Token', tokenAddress) + contracts['Token'] = token + if (signerOrProvider) { + contracts['Token'] = contracts['Token'].connect(signerOrProvider) + } + } + if (banxaAddress) { + const banxaWrapper = getContractAt('BanxaWrapper', banxaAddress) + contracts['BanxaWrapper'] = banxaWrapper + if (signerOrProvider) { + contracts['BanxaWrapper'] = contracts['BanxaWrapper'].connect(signerOrProvider) + } + } + } catch (err) { + logger.warn(`Could not load contracts`) + } + return contracts as BillingContracts +} diff --git a/packages/billing/utils/deploy.ts b/packages/billing/utils/deploy.ts new file mode 100644 index 000000000..7c40c03a6 --- /dev/null +++ b/packages/billing/utils/deploy.ts @@ -0,0 +1,97 @@ +import { Contract, Signer, ContractFactory, utils, BigNumber } from 'ethers' + +import { logger } from './logging' +import { loadArtifact } from './artifacts' +import { Billing } from '../build/types/contracts/Billing' +import { BanxaWrapper } from '../build/types/contracts/BanxaWrapper' + +const hash = (input: string): string => utils.keccak256(`0x${input.replace(/^0x/, '')}`) + +async function deployContract( + args: Array, + sender: Signer, + name: string, + disableLogging?: boolean, +): Promise { + // Disable logging for tests + if (disableLogging) logger.pause() + + // Deploy + const artifact = loadArtifact(name) + const factory = new ContractFactory(artifact.abi, artifact.bytecode) + const contract = await factory.connect(sender).deploy(...args) + const txHash = contract.deployTransaction.hash + logger.log(`> Deploy ${name}, txHash: ${txHash}`) + + // Receipt + const creationCodeHash = hash(factory.bytecode) + const runtimeCodeHash = hash(await sender.provider.getCode(contract.address)) + logger.log('= CreationCodeHash: ', creationCodeHash) + logger.log('= RuntimeCodeHash: ', runtimeCodeHash) + logger.success(`${name} has been deployed to address: ${contract.address}`) + + return contract as unknown as Promise +} + +// Pass the args in order to this func +export async function deployBilling( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'Billing', disableLogging) as unknown as Promise +} + +// Pass the args in order to this func +export async function deployBillingConnector( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'BillingConnector', disableLogging) as unknown as Promise +} + +// Pass the args in order to this func +export async function deployToken( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'Token', disableLogging) as unknown as Promise +} + +// Pass the args in order to this func +export async function deployBanxaWrapper( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'BanxaWrapper', disableLogging) as unknown as Promise +} + +// Pass the args in order to this func +export async function deployL1TokenGatewayMock( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'L1TokenGatewayMock', disableLogging) as unknown as Promise +} + +// Pass the args in order to this func +export async function deployInboxMock( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'InboxMock', disableLogging) as unknown as Promise +} + +// Pass the args in order to this func +export async function deployBridgeMock( + args: Array, + sender: Signer, + disableLogging?: boolean, +): Promise { + return deployContract(args, sender, 'BridgeMock', disableLogging) as unknown as Promise +} diff --git a/packages/billing/utils/helpers.ts b/packages/billing/utils/helpers.ts new file mode 100644 index 000000000..accd17d7f --- /dev/null +++ b/packages/billing/utils/helpers.ts @@ -0,0 +1,51 @@ +import hre from 'hardhat' +import { utils, BigNumber, Signer } from 'ethers' +import { formatUnits } from 'ethers/lib/utils' +import { EthereumProvider } from 'hardhat/types' + +const { parseUnits } = utils + +export const toBN = (value: string | number): BigNumber => BigNumber.from(value) +export const toGRT = (value: string | number): BigNumber => { + return parseUnits(typeof value === 'number' ? value.toString() : value, '18') +} +export const formatGRT = (value: BigNumber): string => formatUnits(value, '18') + +export const provider = (): EthereumProvider => hre.network.provider + +export interface Account { + readonly signer: Signer + readonly address: string +} + +export const getAccounts = async (): Promise => { + const accounts = [] + const signers: Signer[] = await hre.ethers.getSigners() + for (const signer of signers) { + accounts.push({ signer, address: await signer.getAddress() }) + } + return accounts +} + +// Adapted from: +// https://github.com/livepeer/arbitrum-lpt-bridge/blob/e1a81edda3594e434dbcaa4f1ebc95b7e67ecf2a/utils/arbitrum/messaging.ts#L118 +export const applyL1ToL2Alias = (l1Address: string): string => { + const offset = toBN('0x1111000000000000000000000000000000001111') + const l1AddressAsNumber = toBN(l1Address) + const l2AddressAsNumber = l1AddressAsNumber.add(offset) + + const mask = toBN(2).pow(160) + return l2AddressAsNumber.mod(mask).toHexString() +} + +export async function impersonateAccount(address: string): Promise { + await provider().send('hardhat_impersonateAccount', [address]) + return hre.ethers.getSigner(address) +} + +// Adapted from: +// https://github.com/livepeer/arbitrum-lpt-bridge/blob/e1a81edda3594e434dbcaa4f1ebc95b7e67ecf2a/test/utils/messaging.ts#L5 +export async function getL2SignerFromL1(l1Address: string): Promise { + const l2Address = applyL1ToL2Alias(l1Address) + return impersonateAccount(l2Address) +} diff --git a/packages/billing/utils/logging.ts b/packages/billing/utils/logging.ts new file mode 100644 index 000000000..af5d25e6d --- /dev/null +++ b/packages/billing/utils/logging.ts @@ -0,0 +1,3 @@ +import consola from 'consola' + +export const logger = consola.withDefaults({}) diff --git a/scripts/lint-staged-run.sh b/scripts/lint-staged-run.sh index a418c5ead..c10e78c23 100755 --- a/scripts/lint-staged-run.sh +++ b/scripts/lint-staged-run.sh @@ -21,6 +21,7 @@ FILES=("$@") IGNORE_SUBSTRINGS=( "/.graphclient-extracted/" "/packages/legacy/verified/" + "/packages/billing/" ) # Function to check if a file should be ignored From 9667c7c640e04a9910378222e3d0ca69d77f8bd9 Mon Sep 17 00:00:00 2001 From: Miguel de Elias Date: Fri, 6 Nov 2026 17:10:39 -0300 Subject: [PATCH 2/2] chore(billing): build and test in the monorepo --- CLAUDE.md | 2 + README.md | 1 + packages/billing/package.json | 50 +- packages/billing/scripts/build | 4 +- packages/billing/scripts/coverage | 4 +- packages/billing/scripts/deployAll | 6 +- packages/billing/scripts/prepublish | 4 +- packages/billing/scripts/security | 2 +- packages/billing/scripts/test | 2 +- pnpm-lock.yaml | 1678 +++++++++++++++++++- scripts/contracts-migration/manifest.json | 8 +- scripts/contracts-migration/reference.json | 24 + 12 files changed, 1691 insertions(+), 94 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1d34785ec..691030abd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -92,6 +92,8 @@ pnpm lint:yaml # YAML files - Test fixtures - Hardhat extensions +6. **billing** - Graph Studio billing contracts (Billing, BillingConnector, BanxaWrapper), private, Hardhat 2 with ethers v5 + ### Key Architectural Patterns - **Proxy Upgradeable Pattern**: Most contracts use OpenZeppelin's upgradeable proxy pattern diff --git a/README.md b/README.md index 53fa8f86e..779c1d964 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ This repository is a pnpm workspaces monorepo containing the following packages: | Package | Latest version | Description | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | +| [billing](./packages/billing) | Private, not published | Graph Studio billing contracts: Billing, BillingConnector and BanxaWrapper. | | [data-edge](./packages/data-edge) | [![npm version](https://badge.fury.io/js/@graphprotocol%2Fdata-edge.svg)](https://badge.fury.io/js/@graphprotocol%2Fdata-edge) | Data edge testing and utilities for The Graph protocol. | | [hardhat-graph-protocol](./packages/hardhat-graph-protocol) | [![npm version](https://badge.fury.io/js/hardhat-graph-protocol.svg)](https://badge.fury.io/js/hardhat-graph-protocol) | A Hardhat plugin that extends the runtime environment with functionality for The Graph protocol. | | [horizon](./packages/horizon) | [![npm version](https://badge.fury.io/js/@graphprotocol%2Fhorizon.svg)](https://badge.fury.io/js/@graphprotocol%2Fhorizon) | Contracts for Graph Horizon, the next iteration of The Graph protocol. | diff --git a/packages/billing/package.json b/packages/billing/package.json index c6536dbff..ca7c4ca42 100644 --- a/packages/billing/package.json +++ b/packages/billing/package.json @@ -1,29 +1,22 @@ { - "name": "graph-studio-billing-contracts", + "name": "@graphprotocol/billing", "version": "1.1.0", + "private": true, "description": "Graph Studio Billing Contracts", "main": "index.js", "scripts": { - "prepare": "husky install", "prepublishOnly": "scripts/prepublish", - "build": "SKIP_LOAD=true scripts/build", - "clean": "rm -rf build/ cache/ dist/", - "clean:all": "rm -rf node_modules && yarn clean", + "build": "pnpm build:self", + "build:self": "SKIP_LOAD=true scripts/build", + "clean": "rm -rf build/ cache/ dist/ coverage/ reports/", + "clean:all": "rm -rf node_modules && pnpm clean", "compile": "hardhat compile", "deploy:all": "scripts/deployAll", - "test": "scripts/test", - "test:upgrade": "scripts/upgradeForkTest", + "test": "pnpm build && pnpm test:self", + "test:self": "SKIP_LOAD=true hardhat test", "test:gas": "RUN_EVM=true REPORT_GAS=true scripts/test", - "test:coverage": "scripts/coverage", - "lint": "yarn run lint:ts && yarn run lint:sol", - "lint:fix": "yarn run lint:ts:fix && yarn run lint:sol:fix", - "lint:ts": "eslint '*/**/*.{js,ts}'", - "lint:ts:fix": "yarn prettier:ts && eslint '*/**/*.{js,ts}' --fix", - "lint:sol": "solhint 'contracts/**/*.sol'", - "lint:sol:fix": "yarn prettier:sol && solhint --fix 'contracts/**/*.sol'", - "prettier": "yarn prettier:ts && yarn prettier:sol", - "prettier:ts": "prettier --write '**/*.{js,ts,json}'", - "prettier:sol": "prettier --write 'contracts/**/*.sol'", + "test:coverage": "pnpm build && pnpm test:coverage:self", + "test:coverage:self": "SKIP_LOAD=true scripts/coverage", "security": "scripts/security", "flatten": "scripts/flatten", "typechain": "hardhat typechain", @@ -37,43 +30,24 @@ "author": "", "license": "MIT", "devDependencies": { - "@commitlint/cli": "^17.1.2", - "@commitlint/config-conventional": "^17.1.0", "@graphprotocol/common-ts": "^1.8.6", "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", "@nomiclabs/hardhat-ethers": "*", "@nomiclabs/hardhat-etherscan": "^3.1.2", - "@openzeppelin/contracts": "^4.3.0", + "@openzeppelin/contracts": "4.7.3", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.2", "@types/mocha": "^9.1.1", "@types/node": "^18.0.5", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "arb-bridge-peripherals": "^1.0.11", + "arb-bridge-peripherals": "1.0.11", "axios": "^0.21.1", "chai": "^4.2.0", "consola": "^2.15.3", "dotenv": "^10.0.0", - "eslint": "^8.19.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^17.0.0", - "eslint-plugin-import": "^2.22.0", - "eslint-plugin-mocha-no-only": "^1.1.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-standard": "^5.0.0", "ethers": "^5.6.0", "hardhat": "^2.10.1", "hardhat-abi-exporter": "^2.2.1", - "husky": "^8.0.1", "inquirer": "^8.1.2", - "lint-staged": "^13.0.3", - "prettier": "^2.1.1", - "prettier-plugin-solidity": "^1.0.0-alpha.56", - "solhint": "^3.3.7", - "solhint-plugin-prettier": "^0.0.5", "solidity-coverage": "^0.8.2", "ts-node": "^10.0.0", "typechain": "^8.1.0", diff --git a/packages/billing/scripts/build b/packages/billing/scripts/build index a82a79aae..8f4d201d3 100755 --- a/packages/billing/scripts/build +++ b/packages/billing/scripts/build @@ -2,5 +2,5 @@ set -eo pipefail -yarn run compile -yarn run flatten \ No newline at end of file +pnpm run compile +pnpm run flatten \ No newline at end of file diff --git a/packages/billing/scripts/coverage b/packages/billing/scripts/coverage index dd120cba9..3f79f94f8 100755 --- a/packages/billing/scripts/coverage +++ b/packages/billing/scripts/coverage @@ -2,6 +2,6 @@ set -eo pipefail -yarn run compile -yarn run typechain +pnpm run compile +pnpm run typechain npx hardhat coverage $@ diff --git a/packages/billing/scripts/deployAll b/packages/billing/scripts/deployAll index 11ba83a7f..45f05f365 100755 --- a/packages/billing/scripts/deployAll +++ b/packages/billing/scripts/deployAll @@ -2,9 +2,9 @@ set -eo pipefail -yarn clean:all -yarn -yarn build +pnpm clean:all +pnpm install +pnpm build source .env diff --git a/packages/billing/scripts/prepublish b/packages/billing/scripts/prepublish index a24463117..237883d31 100755 --- a/packages/billing/scripts/prepublish +++ b/packages/billing/scripts/prepublish @@ -5,8 +5,8 @@ TYPECHAIN_DIR=dist/types set -eo pipefail # Build contracts -yarn clean -yarn build +pnpm clean +pnpm build # Refresh distribution folder rm -rf dist && mkdir -p dist/types/_src diff --git a/packages/billing/scripts/security b/packages/billing/scripts/security index 90cb4c2f0..9d91f39f2 100755 --- a/packages/billing/scripts/security +++ b/packages/billing/scripts/security @@ -9,7 +9,7 @@ mkdir -p reports pip3 install --user slither-analyzer && \ -yarn run build && \ +pnpm run build && \ echo "Analyzing contracts..." slither . &> reports/analyzer-report.log && \ diff --git a/packages/billing/scripts/test b/packages/billing/scripts/test index dfb460b41..59344c3bc 100755 --- a/packages/billing/scripts/test +++ b/packages/billing/scripts/test @@ -37,7 +37,7 @@ fi mkdir -p reports -yarn run build +pnpm run build if [ "$RUN_EVM" = true ]; then # Run using the standalone evm instance diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8baaa6c4..1a76c35fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -220,6 +220,75 @@ importers: specifier: 'catalog:' version: 3.8.1 + packages/billing: + devDependencies: + '@graphprotocol/common-ts': + specifier: ^1.8.6 + version: 1.8.7(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) + '@nomicfoundation/hardhat-chai-matchers': + specifier: ^1.0.3 + version: 1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': + specifier: '*' + version: 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-etherscan': + specifier: ^3.1.2 + version: 3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)) + '@openzeppelin/contracts': + specifier: 4.7.3 + version: 4.7.3 + '@typechain/ethers-v5': + specifier: ^10.1.0 + version: 10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5))(typescript@4.9.5) + '@typechain/hardhat': + specifier: ^6.1.2 + version: 6.1.6(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5))(typescript@4.9.5))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5)) + '@types/mocha': + specifier: ^9.1.1 + version: 9.1.1 + '@types/node': + specifier: ^20.17.50 + version: 20.19.41 + arb-bridge-peripherals: + specifier: 1.0.11 + version: 1.0.11(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + axios: + specifier: ^0.21.1 + version: 0.21.4(debug@4.4.3) + chai: + specifier: ^4.2.0 + version: 4.5.0 + consola: + specifier: ^2.15.3 + version: 2.15.3 + dotenv: + specifier: ^10.0.0 + version: 10.0.0 + ethers: + specifier: ^5.6.0 + version: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: + specifier: ^2.10.1 + version: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat-abi-exporter: + specifier: ^2.2.1 + version: 2.11.0(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)) + inquirer: + specifier: ^8.1.2 + version: 8.2.7(@types/node@20.19.41) + solidity-coverage: + specifier: ^0.8.2 + version: 0.8.17(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)) + ts-node: + specifier: ^10.0.0 + version: 10.9.2(@types/node@20.19.41)(typescript@4.9.5) + typechain: + specifier: ^8.1.0 + version: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5) + typescript: + specifier: ^4.0.2 + version: 4.9.5 + packages/data-edge: devDependencies: '@nomicfoundation/hardhat-chai-matchers': @@ -2328,24 +2397,36 @@ packages: '@ethereumjs/vm@5.6.0': resolution: {integrity: sha512-J2m/OgjjiGdWF2P9bj/4LnZQ1zRoZhY8mRNVw/N3tXliGI8ai1sI1mlDPkLpeUUM4vq54gH6n0ZlSpz8U/qlYQ==} + '@ethersproject/abi@5.6.0': + resolution: {integrity: sha512-AhVByTwdXCc2YQ20v300w6KVHle9g2OFc28ZAFCPnJyEpkv1xKXjZcSTgWOlv1i+0dqlgF8RCF2Rn2KC1t+1Vg==} + '@ethersproject/abi@5.7.0': resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} '@ethersproject/abi@5.8.0': resolution: {integrity: sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==} + '@ethersproject/abstract-provider@5.6.0': + resolution: {integrity: sha512-oPMFlKLN+g+y7a79cLK3WiLcjWFnZQtXWgnLAbHZcN3s7L4v90UHpTOrLk+m3yr0gt+/h9STTM6zrr7PM8uoRw==} + '@ethersproject/abstract-provider@5.7.0': resolution: {integrity: sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==} '@ethersproject/abstract-provider@5.8.0': resolution: {integrity: sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==} + '@ethersproject/abstract-signer@5.6.0': + resolution: {integrity: sha512-WOqnG0NJKtI8n0wWZPReHtaLkDByPL67tn4nBaDAhmVq8sjHTPbCdz4DRhVu/cfTOvfy9w3iq5QZ7BX7zw56BQ==} + '@ethersproject/abstract-signer@5.7.0': resolution: {integrity: sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==} '@ethersproject/abstract-signer@5.8.0': resolution: {integrity: sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==} + '@ethersproject/address@5.6.0': + resolution: {integrity: sha512-6nvhYXjbXsHPS+30sHZ+U4VMagFC/9zAk6Gd/h3S21YW4+yfb0WfRtaAIZ4kfM4rrVwqiy284LP0GtL5HXGLxQ==} + '@ethersproject/address@5.6.1': resolution: {integrity: sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==} @@ -2355,36 +2436,54 @@ packages: '@ethersproject/address@5.8.0': resolution: {integrity: sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==} + '@ethersproject/base64@5.6.0': + resolution: {integrity: sha512-2Neq8wxJ9xHxCF9TUgmKeSh9BXJ6OAxWfeGWvbauPh8FuHEjamgHilllx8KkSd5ErxyHIX7Xv3Fkcud2kY9ezw==} + '@ethersproject/base64@5.7.0': resolution: {integrity: sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==} '@ethersproject/base64@5.8.0': resolution: {integrity: sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==} + '@ethersproject/basex@5.6.0': + resolution: {integrity: sha512-qN4T+hQd/Md32MoJpc69rOwLYRUXwjTlhHDIeUkUmiN/JyWkkLLMoG0TqvSQKNqZOMgN5stbUYN6ILC+eD7MEQ==} + '@ethersproject/basex@5.7.0': resolution: {integrity: sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==} '@ethersproject/basex@5.8.0': resolution: {integrity: sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==} + '@ethersproject/bignumber@5.6.0': + resolution: {integrity: sha512-VziMaXIUHQlHJmkv1dlcd6GY2PmT0khtAqaMctCIDogxkrarMzA9L94KN1NeXqqOfFD6r0sJT3vCTOFSmZ07DA==} + '@ethersproject/bignumber@5.7.0': resolution: {integrity: sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==} '@ethersproject/bignumber@5.8.0': resolution: {integrity: sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==} + '@ethersproject/bytes@5.6.1': + resolution: {integrity: sha512-NwQt7cKn5+ZE4uDn+X5RAXLp46E1chXoaMmrxAyA0rblpxz8t58lVkrHXoRIn0lz1joQElQ8410GqhTqMOwc6g==} + '@ethersproject/bytes@5.7.0': resolution: {integrity: sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==} '@ethersproject/bytes@5.8.0': resolution: {integrity: sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==} + '@ethersproject/constants@5.6.0': + resolution: {integrity: sha512-SrdaJx2bK0WQl23nSpV/b1aq293Lh0sUaZT/yYKPDKn4tlAbkH96SPJwIhwSwTsoQQZxuh1jnqsKwyymoiBdWA==} + '@ethersproject/constants@5.7.0': resolution: {integrity: sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==} '@ethersproject/constants@5.8.0': resolution: {integrity: sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==} + '@ethersproject/contracts@5.6.0': + resolution: {integrity: sha512-74Ge7iqTDom0NX+mux8KbRUeJgu1eHZ3iv6utv++sLJG80FVuU9HnHeKVPfjd9s3woFhaFoQGf3B3iH/FrQmgw==} + '@ethersproject/contracts@5.7.0': resolution: {integrity: sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==} @@ -2397,120 +2496,180 @@ packages: '@ethersproject/hardware-wallets@5.8.0': resolution: {integrity: sha512-bsGrIs3CnsphjB+0/8bcoBm3ttJInUTSC1f2bA5Gjf8KPyA1DlIAr3x/RKQdg0a0EWygtY9HNRJgosec5mvZ7Q==} + '@ethersproject/hash@5.6.0': + resolution: {integrity: sha512-fFd+k9gtczqlr0/BruWLAu7UAOas1uRRJvOR84uDf4lNZ+bTkGl366qvniUZHKtlqxBRU65MkOobkmvmpHU+jA==} + '@ethersproject/hash@5.7.0': resolution: {integrity: sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==} '@ethersproject/hash@5.8.0': resolution: {integrity: sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==} + '@ethersproject/hdnode@5.6.0': + resolution: {integrity: sha512-61g3Jp3nwDqJcL/p4nugSyLrpl/+ChXIOtCEM8UDmWeB3JCAt5FoLdOMXQc3WWkc0oM2C0aAn6GFqqMcS/mHTw==} + '@ethersproject/hdnode@5.7.0': resolution: {integrity: sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==} '@ethersproject/hdnode@5.8.0': resolution: {integrity: sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==} + '@ethersproject/json-wallets@5.6.0': + resolution: {integrity: sha512-fmh86jViB9r0ibWXTQipxpAGMiuxoqUf78oqJDlCAJXgnJF024hOOX7qVgqsjtbeoxmcLwpPsXNU0WEe/16qPQ==} + '@ethersproject/json-wallets@5.7.0': resolution: {integrity: sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==} '@ethersproject/json-wallets@5.8.0': resolution: {integrity: sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==} + '@ethersproject/keccak256@5.6.0': + resolution: {integrity: sha512-tk56BJ96mdj/ksi7HWZVWGjCq0WVl/QvfhFQNeL8fxhBlGoP+L80uDCiQcpJPd+2XxkivS3lwRm3E0CXTfol0w==} + '@ethersproject/keccak256@5.7.0': resolution: {integrity: sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==} '@ethersproject/keccak256@5.8.0': resolution: {integrity: sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==} + '@ethersproject/logger@5.6.0': + resolution: {integrity: sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg==} + '@ethersproject/logger@5.7.0': resolution: {integrity: sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==} '@ethersproject/logger@5.8.0': resolution: {integrity: sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==} + '@ethersproject/networks@5.6.1': + resolution: {integrity: sha512-b2rrupf3kCTcc3jr9xOWBuHylSFtbpJf79Ga7QR98ienU2UqGimPGEsYMgbI29KHJfA5Us89XwGVmxrlxmSrMg==} + '@ethersproject/networks@5.7.1': resolution: {integrity: sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==} '@ethersproject/networks@5.8.0': resolution: {integrity: sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==} + '@ethersproject/pbkdf2@5.6.0': + resolution: {integrity: sha512-Wu1AxTgJo3T3H6MIu/eejLFok9TYoSdgwRr5oGY1LTLfmGesDoSx05pemsbrPT2gG4cQME+baTSCp5sEo2erZQ==} + '@ethersproject/pbkdf2@5.7.0': resolution: {integrity: sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==} '@ethersproject/pbkdf2@5.8.0': resolution: {integrity: sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==} + '@ethersproject/properties@5.6.0': + resolution: {integrity: sha512-szoOkHskajKePTJSZ46uHUWWkbv7TzP2ypdEK6jGMqJaEt2sb0jCgfBo0gH0m2HBpRixMuJ6TBRaQCF7a9DoCg==} + '@ethersproject/properties@5.7.0': resolution: {integrity: sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==} '@ethersproject/properties@5.8.0': resolution: {integrity: sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==} + '@ethersproject/providers@5.6.2': + resolution: {integrity: sha512-6/EaFW/hNWz+224FXwl8+HdMRzVHt8DpPmu5MZaIQqx/K/ELnC9eY236SMV7mleCM3NnEArFwcAAxH5kUUgaRg==} + '@ethersproject/providers@5.7.2': resolution: {integrity: sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==} '@ethersproject/providers@5.8.0': resolution: {integrity: sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==} + '@ethersproject/random@5.6.0': + resolution: {integrity: sha512-si0PLcLjq+NG/XHSZz90asNf+YfKEqJGVdxoEkSukzbnBgC8rydbgbUgBbBGLeHN4kAJwUFEKsu3sCXT93YMsw==} + '@ethersproject/random@5.7.0': resolution: {integrity: sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==} '@ethersproject/random@5.8.0': resolution: {integrity: sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==} + '@ethersproject/rlp@5.6.0': + resolution: {integrity: sha512-dz9WR1xpcTL+9DtOT/aDO+YyxSSdO8YIS0jyZwHHSlAmnxA6cKU3TrTd4Xc/bHayctxTgGLYNuVVoiXE4tTq1g==} + '@ethersproject/rlp@5.7.0': resolution: {integrity: sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==} '@ethersproject/rlp@5.8.0': resolution: {integrity: sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==} + '@ethersproject/sha2@5.6.0': + resolution: {integrity: sha512-1tNWCPFLu1n3JM9t4/kytz35DkuF9MxqkGGEHNauEbaARdm2fafnOyw1s0tIQDPKF/7bkP1u3dbrmjpn5CelyA==} + '@ethersproject/sha2@5.7.0': resolution: {integrity: sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==} '@ethersproject/sha2@5.8.0': resolution: {integrity: sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==} + '@ethersproject/signing-key@5.6.0': + resolution: {integrity: sha512-S+njkhowmLeUu/r7ir8n78OUKx63kBdMCPssePS89So1TH4hZqnWFsThEd/GiXYp9qMxVrydf7KdM9MTGPFukA==} + '@ethersproject/signing-key@5.7.0': resolution: {integrity: sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==} '@ethersproject/signing-key@5.8.0': resolution: {integrity: sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==} + '@ethersproject/solidity@5.6.0': + resolution: {integrity: sha512-YwF52vTNd50kjDzqKaoNNbC/r9kMDPq3YzDWmsjFTRBcIF1y4JCQJ8gB30wsTfHbaxgxelI5BfxQSxD/PbJOww==} + '@ethersproject/solidity@5.7.0': resolution: {integrity: sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==} '@ethersproject/solidity@5.8.0': resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} + '@ethersproject/strings@5.6.0': + resolution: {integrity: sha512-uv10vTtLTZqrJuqBZR862ZQjTIa724wGPWQqZrofaPI/kUsf53TBG0I0D+hQ1qyNtllbNzaW+PDPHHUI6/65Mg==} + '@ethersproject/strings@5.7.0': resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} '@ethersproject/strings@5.8.0': resolution: {integrity: sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==} + '@ethersproject/transactions@5.6.0': + resolution: {integrity: sha512-4HX+VOhNjXHZyGzER6E/LVI2i6lf9ejYeWD6l4g50AdmimyuStKc39kvKf1bXWQMg7QNVh+uC7dYwtaZ02IXeg==} + '@ethersproject/transactions@5.7.0': resolution: {integrity: sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==} '@ethersproject/transactions@5.8.0': resolution: {integrity: sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==} + '@ethersproject/units@5.6.0': + resolution: {integrity: sha512-tig9x0Qmh8qbo1w8/6tmtyrm/QQRviBh389EQ+d8fP4wDsBrJBf08oZfoiz1/uenKK9M78yAP4PoR7SsVoTjsw==} + '@ethersproject/units@5.7.0': resolution: {integrity: sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==} '@ethersproject/units@5.8.0': resolution: {integrity: sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==} + '@ethersproject/wallet@5.6.0': + resolution: {integrity: sha512-qMlSdOSTyp0MBeE+r7SUhr1jjDlC1zAXB8VD84hCnpijPQiSNbxr6GdiLXxpUs8UKzkDiNYYC5DRI3MZr+n+tg==} + '@ethersproject/wallet@5.7.0': resolution: {integrity: sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==} '@ethersproject/wallet@5.8.0': resolution: {integrity: sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==} + '@ethersproject/web@5.6.0': + resolution: {integrity: sha512-G/XHj0hV1FxI2teHRfCGvfBUHFmU+YOSbCxlAMqJklxSa7QMiHFQfAxvwY2PFqgvdkxEKwRNr/eCjfAPEm2Ctg==} + '@ethersproject/web@5.7.1': resolution: {integrity: sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==} '@ethersproject/web@5.8.0': resolution: {integrity: sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==} + '@ethersproject/wordlists@5.6.0': + resolution: {integrity: sha512-q0bxNBfIX3fUuAo9OmjlEYxP40IB8ABgb7HjEZCL5IKubzV3j30CWi2rqQbjTS2HfoyQbfINoKcTVWP4ejwR7Q==} + '@ethersproject/wordlists@5.7.0': resolution: {integrity: sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==} @@ -2589,6 +2748,16 @@ packages: '@graphql-tools/merge': ^8.3.14 graphql: ^15.2.0 || ^16.0.0 + '@graphprotocol/common-ts@1.8.7': + resolution: {integrity: sha512-B1LZHbWnP7HOjkycN/y0dB47yZ8PuhOrZHGLsRo+4qhpMJx55NzJBfPOPnivnrfbM7wL69l158NpSHhpr9Mgug==} + + '@graphprotocol/contracts@2.1.0': + resolution: {integrity: sha512-SeymJCUxBp488K/KNi77EKvrkPT0t/7rERGp8zFkmf5KByerjihhE9Ty9oXJAU9RzbUpxsU0JkPBSmiw9/2DYQ==} + + '@graphprotocol/pino-sentry-simple@0.7.1': + resolution: {integrity: sha512-iccKFdFBjarSp8/liXuK1EtGq8Vwn118tqymbOJBxblecRsi4rOebk63qnL+dK/a0IvxH6h2+RjjWDbRt7UsUA==} + engines: {node: '>=10'} + '@graphql-codegen/core@3.1.0': resolution: {integrity: sha512-DH1/yaR7oJE6/B+c6ZF2Tbdh7LixF1K8L+8BoSubjNyQ8pNwR4a70mvc1sv6H7qgp6y1bPQ9tKE+aazRRshysw==} peerDependencies: @@ -3213,6 +3382,14 @@ packages: resolution: {integrity: sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==} engines: {node: '>= 20'} + '@nomicfoundation/hardhat-chai-matchers@1.0.6': + resolution: {integrity: sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ==} + peerDependencies: + '@nomiclabs/hardhat-ethers': ^2.0.0 + chai: ^4.2.0 + ethers: ^5.0.0 + hardhat: ^2.9.4 + '@nomicfoundation/hardhat-chai-matchers@2.1.0': resolution: {integrity: sha512-GPhBNafh1fCnVD9Y7BYvoLnblnvfcq3j8YDbO1gGe/1nOFWzGmV7gFu5DkwFXF+IpYsS+t96o9qc/mPu3V3Vfw==} peerDependencies: @@ -3414,6 +3591,9 @@ packages: '@openzeppelin/contracts@3.4.2': resolution: {integrity: sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA==} + '@openzeppelin/contracts@4.7.3': + resolution: {integrity: sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==} + '@openzeppelin/contracts@5.4.0': resolution: {integrity: sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==} @@ -3939,6 +4119,9 @@ packages: '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -4007,6 +4190,16 @@ packages: resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@urql/core@2.4.4': + resolution: {integrity: sha512-TD+OS7jG1Ts6QkpU0TZ85i/vu40r71GF0QQFDhnWFtgkHcNwnpkIwWBMa72AR3j2imBTPpk61e/xb39uM/t37A==} + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + + '@urql/exchange-execute@1.2.2': + resolution: {integrity: sha512-KebdnKWMKI1NkRtIxp8YIouynOaFnhcdaMNCcJEtp+kmY4vGZUgdxT/SIzTPXXYJvk5G2aiQ/JMr97I+wM/EHA==} + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + '@wagmi/cli@2.5.1': resolution: {integrity: sha512-X8cFOy9CtVZOiixqW+Y8/EbL91MLWfzV1Wd+M88LErxLBtP8K0Z4P6lS8I5nrH64zm5dC2ZN9SlrZMSbwQqbOQ==} hasBin: true @@ -4101,6 +4294,10 @@ packages: engines: {node: '>=6'} deprecated: Superseded by abstract-level (https://github.com/Level/community#faq) + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -4242,6 +4439,14 @@ packages: aproba@1.2.0: resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} + arb-bridge-eth@0.7.8: + resolution: {integrity: sha512-69ByA+0/vEZNOP21h0XINuwtgnqUQ291mBoj60PhnpLlQgSbm93ZzapEeSKvX829gjRa/96EQ09m56l8XtE5Yg==} + deprecated: This package is no longer maintained, instead look into @arbitrum/nitro-contracts + + arb-bridge-peripherals@1.0.11: + resolution: {integrity: sha512-dfl4NgGIgjMvpw8sBhHSHeEZp5qXX7YeqJ5Bu2m+efK3dDZhBv5QnFnbTW597B14a+qZ6VuKijD0KMNkBLyBrg==} + deprecated: This package is no longer maintained, instead look into @arbitrum/token-bridge-contracts + arbos-precompiles@1.0.2: resolution: {integrity: sha512-1dOFYFJUN0kKoofh6buZJ8qCqTs+oLGSsGzHI0trA/Pka/TCERflCRsNVxez2lihOvK7MT/a2RA8AepKtBXdPQ==} deprecated: This package is no longer maintained, instead look into @arbitrum/nitro-contracts @@ -4271,6 +4476,9 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} @@ -4359,6 +4567,10 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + auto-bind@4.0.0: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} engines: {node: '>=8'} @@ -4438,6 +4650,10 @@ packages: resolution: {integrity: sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==} hasBin: true + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -4464,6 +4680,9 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + bip39@3.0.4: resolution: {integrity: sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==} @@ -4485,6 +4704,14 @@ packages: bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + body-parser@1.19.1: + resolution: {integrity: sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==} + engines: {node: '>= 0.8'} + + body-parser@1.19.2: + resolution: {integrity: sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==} + engines: {node: '>= 0.8'} + bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -4535,6 +4762,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + buffer-xor@1.0.3: resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} @@ -4565,6 +4796,10 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + bytes@3.1.1: + resolution: {integrity: sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==} + engines: {node: '>= 0.8'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -4762,6 +4997,10 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + cli-table3@0.5.1: resolution: {integrity: sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==} engines: {node: '>=6'} @@ -4791,6 +5030,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + code-point-at@1.1.0: resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} engines: {node: '>=0.10.0'} @@ -4890,9 +5133,20 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + console-table-printer@2.16.1: + resolution: {integrity: sha512-Sc9FRJ4O9xKGNrvulNdPfK5SyBcZ6lcaRnDE4AQ/uw6IDtjHhsqyzzqcnMikjyGaiOOF2tNOKoBhbVjRvFy9Lw==} + constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + conventional-changelog-angular@7.0.0: resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} engines: {node: '>=16'} @@ -4909,6 +5163,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} @@ -4922,6 +5179,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cosmiconfig-typescript-loader@6.1.0: resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} engines: {node: '>=v18'} @@ -4965,6 +5226,9 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-fetch@3.1.5: + resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} + cross-fetch@3.2.0: resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} @@ -5073,6 +5337,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -5106,6 +5373,10 @@ packages: engines: {node: '>=10'} hasBin: true + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -5118,6 +5389,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.0.4: + resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -5167,10 +5441,18 @@ packages: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} + dotenv@10.0.0: + resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==} + engines: {node: '>=10'} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dottie@2.0.7: + resolution: {integrity: sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + dset@3.1.4: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} @@ -5179,6 +5461,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5492,6 +5777,9 @@ packages: resolution: {integrity: sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==} engines: {node: '>=10.0.0'} + ethers@5.6.2: + resolution: {integrity: sha512-EzGCbns24/Yluu7+ToWnMca3SXJ1Jk1BvWB7CCmVNxyOeM4LLvw2OLuIHhlkhQk1dtOcj9UMsdkxUh8RiG1dxQ==} + ethers@5.7.2: resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==} @@ -5538,6 +5826,10 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + express@4.17.3: + resolution: {integrity: sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==} + engines: {node: '>= 0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -5585,6 +5877,10 @@ packages: fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -5598,6 +5894,10 @@ packages: resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==} hasBin: true + fastify-warning@0.2.0: + resolution: {integrity: sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==} + deprecated: This module renamed to process-warning + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -5725,6 +6025,10 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fp-ts@1.19.3: resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} @@ -5992,6 +6296,10 @@ packages: resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + graphql@16.3.0: + resolution: {integrity: sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==} + engines: {node: ^12.22.0 || ^14.16.0 || >=16.0.0} + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -6128,6 +6436,10 @@ packages: heap@0.2.7: resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} + helmet@5.0.2: + resolution: {integrity: sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==} + engines: {node: '>=12.0.0'} + hermes-estree@0.29.1: resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} @@ -6160,6 +6472,10 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -6274,6 +6590,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + inflection@1.13.4: + resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} + engines: {'0': node >= 0.4.0} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -6300,6 +6620,10 @@ packages: resolution: {integrity: sha512-ON8pEJPPCdyjxj+cxsYRe6XfCJepTxANdNnTebsTuQgXpRyZRRT9t4dJwjRubgmvn20CLSEnozRUayXyM9VTXA==} engines: {node: '>=8.0.0'} + inquirer@8.2.7: + resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} + engines: {node: '>=12.0.0'} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -6322,6 +6646,10 @@ packages: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-absolute@1.0.0: resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} engines: {node: '>=0.10.0'} @@ -6418,6 +6746,10 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} @@ -6616,10 +6948,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -7024,6 +7352,10 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + memdown@5.1.0: resolution: {integrity: sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==} engines: {node: '>=6'} @@ -7040,6 +7372,9 @@ packages: resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} engines: {node: '>=16.10'} + merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -7059,6 +7394,10 @@ packages: '@types/node': optional: true + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + metro-babel-transformer@0.83.7: resolution: {integrity: sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==} engines: {node: '>=20.19.4'} @@ -7350,6 +7689,16 @@ packages: engines: {node: '>= 14.0.0'} hasBin: true + moment-timezone@0.5.48: + resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} + + moment@2.31.0: + resolution: {integrity: sha512-0acOTfMiWOheYS4eoWb80yYMb/JLvVv9SHbs2PehaDzfUG0Bw855SKyk0IKTnPGa5+U2bmi3W68l1+sGLX/pvw==} + + morgan@1.10.0: + resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} + engines: {node: '>= 0.8.0'} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -7400,6 +7749,10 @@ packages: engines: {node: '>=10'} hasBin: true + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + negotiator@0.6.4: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} @@ -7414,6 +7767,10 @@ packages: neoqs@6.13.0: resolution: {integrity: sha512-IysBpjrEG9qiUb/IT6XrXSz2ASzBxLebp4s8/GBm7STYC315vMNqH0aWdRR+f7KvXK4aRlLcf5r2Z6dOTxQSrQ==} + ngeohash@0.6.3: + resolution: {integrity: sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==} + engines: {node: '>=v0.2.0'} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -7435,6 +7792,15 @@ packages: node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + node-fetch@2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -7562,6 +7928,9 @@ packages: obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + on-exit-leak-free@0.2.0: + resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -7570,6 +7939,10 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -7600,6 +7973,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + ordinal@1.0.3: resolution: {integrity: sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==} @@ -7704,6 +8081,9 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} @@ -7793,6 +8173,9 @@ packages: resolution: {integrity: sha512-wZ3AeiRBRlNwkdUxvBANh0+esnt38DLffHDujZyRHkqkaKHTglnY2EP5UX3b8rdeiSutgO4y9NEJwXezNP5vHg==} engines: {node: '>=8'} + path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-type@1.1.0: resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} engines: {node: '>=0.10.0'} @@ -7818,6 +8201,41 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + pg-connection-string@2.14.1: + resolution: {integrity: sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==} + + pg-hstore@2.3.4: + resolution: {integrity: sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==} + engines: {node: '>= 0.8.x'} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.1: + resolution: {integrity: sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.7.3: + resolution: {integrity: sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=2.0.0' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.6: + resolution: {integrity: sha512-lqIfH7bdgsxHAY/ZnUOwm+aCFKrsHBDhSFuk9O0B9uCqJAIkrKTo/+LQqLPLUS4e04+jCmQVikxE3QipH5chPw==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -7858,6 +8276,20 @@ packages: resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} engines: {node: '>=0.10.0'} + pino-abstract-transport@0.5.0: + resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} + + pino-multi-stream@6.0.0: + resolution: {integrity: sha512-oCuTtaDSUB5xK1S45r9oWE0Dj8RWdHVvaGTft5pO/rmzgIqQRkilf5Ooilz3uRm0IYj8sPRho3lVx48LCmXjvQ==} + deprecated: No longer supported. Use the multi-stream support in the latest core Pino + + pino-std-serializers@4.0.0: + resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} + + pino@7.6.0: + resolution: {integrity: sha512-CCCdryvM/chT0CDt9jQ1//z62RpSXPrzUFUpY4b8eKCVq3T2T3UF6DomoczkPze9d6VFiTyVF6Y8A6F9iAyAxg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -7870,6 +8302,22 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prebuild-install@5.3.6: resolution: {integrity: sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==} engines: {node: '>=6'} @@ -7917,6 +8365,11 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prom-client@14.0.1: + resolution: {integrity: sha512-HxTArb6fkOntQHoRGvv4qd/BkorjliiuO2uSWC2KC17MUTKYttWdDoXX/vxOhQdkoECEM9BBH0pj2l8G8kev6w==} + engines: {node: '>=10'} + deprecated: prom-client has been replaced by @prometheus-io/client + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} @@ -7943,6 +8396,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -7956,6 +8413,9 @@ packages: pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pumpify@2.0.1: + resolution: {integrity: sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -7986,6 +8446,14 @@ packages: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} + qs@6.9.6: + resolution: {integrity: sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==} + engines: {node: '>=0.6'} + + qs@6.9.7: + resolution: {integrity: sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -7995,6 +8463,9 @@ packages: queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -8006,6 +8477,14 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@2.4.2: + resolution: {integrity: sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==} + engines: {node: '>= 0.8'} + + raw-body@2.4.3: + resolution: {integrity: sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==} + engines: {node: '>= 0.8'} + raw-body@2.5.2: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} @@ -8078,6 +8557,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.1.0: + resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} + engines: {node: '>= 12.13.0'} + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -8191,6 +8674,9 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + retry-as-promised@5.0.0: + resolution: {integrity: sha512-6S+5LvtTl2ggBumk04hBo/4Uf6fRJUwIgunGZ7CYEBCeufGFW1Pu6ucUf/UskHeWOIsUcLOGLFXPig5tR5V1nA==} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -8255,6 +8741,9 @@ packages: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -8273,6 +8762,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -8323,6 +8816,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.17.2: + resolution: {integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==} + engines: {node: '>= 0.8.0'} + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -8330,6 +8827,40 @@ packages: sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} + sequelize-pool@7.1.0: + resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} + engines: {node: '>= 10.0.0'} + + sequelize@6.19.0: + resolution: {integrity: sha512-B3oGIdpYBERDjRDm74h7Ky67f6ZLcmBXOA7HscYObiOSo4pD7VBc9mtm44wNV7unc0uk8I1d30nbZBTQCE377A==} + engines: {node: '>=10.0.0'} + peerDependencies: + ibm_db: '*' + mariadb: '*' + mysql2: '*' + pg: '*' + pg-hstore: '*' + snowflake-sdk: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + ibm_db: + optional: true + mariadb: + optional: true + mysql2: + optional: true + pg: + optional: true + pg-hstore: + optional: true + snowflake-sdk: + optional: true + sqlite3: + optional: true + tedious: + optional: true + serialize-error@2.1.0: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} @@ -8337,6 +8868,10 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serve-static@1.14.2: + resolution: {integrity: sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==} + engines: {node: '>= 0.8.0'} + serve-static@1.16.3: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} @@ -8422,6 +8957,9 @@ packages: simple-get@3.1.1: resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + simple-wcswidth@1.1.2: + resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -8571,6 +9109,9 @@ packages: peerDependencies: hardhat: ^2.8.0 + sonic-boom@2.8.0: + resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -8650,6 +9191,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -8796,6 +9340,9 @@ packages: engines: {node: '>=10'} deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tdigest@0.1.3: + resolution: {integrity: sha512-zbRt+lT+/H4fRItHshczHErVCQnitJk8MfMT24MqFJf3YL7SJJPqGIGeuOdvxXxM/AHFzKBl7WoyaYwqO9s3Kw==} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -8824,9 +9371,15 @@ packages: resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==} engines: {node: '>=6.0.0'} + thread-stream@0.13.2: + resolution: {integrity: sha512-woZFt0cLFkPdhsa+IGpRo1jiSouaHxMIljzTgt30CMjBWoUYbbcHqnunW5Yv+BXko9H05MVIcxMipI3Jblallw==} + throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + through2@3.0.2: + resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -8866,6 +9419,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toposort-class@1.0.1: + resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} + tough-cookie@2.5.0: resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} engines: {node: '>=0.8'} @@ -8980,6 +9536,10 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + typechain@8.3.2: resolution: {integrity: sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==} hasBin: true @@ -9012,6 +9572,11 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -9048,6 +9613,9 @@ packages: resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} engines: {node: '>=0.10.0'} + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -9157,10 +9725,18 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + validator@13.15.35: + resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} + engines: {node: '>= 0.10'} + value-or-promise@1.0.12: resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==} engines: {node: '>=12'} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} @@ -9187,6 +9763,9 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -9254,6 +9833,12 @@ packages: engines: {node: '>= 0.10.0'} hasBin: true + wkx@0.5.0: + resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} + + wonka@4.0.15: + resolution: {integrity: sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -10816,7 +11401,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@ethersproject/abi@5.7.0': + '@ethersproject/abi@5.6.0': dependencies: '@ethersproject/address': 5.8.0 '@ethersproject/bignumber': 5.8.0 @@ -10828,7 +11413,7 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@ethersproject/abi@5.8.0': + '@ethersproject/abi@5.7.0': dependencies: '@ethersproject/address': 5.8.0 '@ethersproject/bignumber': 5.8.0 @@ -10840,12 +11425,34 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@ethersproject/abstract-provider@5.7.0': + '@ethersproject/abi@5.8.0': dependencies: + '@ethersproject/address': 5.8.0 '@ethersproject/bignumber': 5.8.0 '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/keccak256': 5.8.0 '@ethersproject/logger': 5.8.0 - '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + + '@ethersproject/abstract-provider@5.6.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + + '@ethersproject/abstract-provider@5.7.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 '@ethersproject/properties': 5.8.0 '@ethersproject/transactions': 5.8.0 '@ethersproject/web': 5.8.0 @@ -10860,6 +11467,14 @@ snapshots: '@ethersproject/transactions': 5.8.0 '@ethersproject/web': 5.8.0 + '@ethersproject/abstract-signer@5.6.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/abstract-signer@5.7.0': dependencies: '@ethersproject/abstract-provider': 5.8.0 @@ -10876,6 +11491,14 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 + '@ethersproject/address@5.6.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/address@5.6.1': dependencies: '@ethersproject/bignumber': 5.8.0 @@ -10900,6 +11523,10 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/rlp': 5.8.0 + '@ethersproject/base64@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/base64@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -10908,6 +11535,11 @@ snapshots: dependencies: '@ethersproject/bytes': 5.8.0 + '@ethersproject/basex@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/basex@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -10918,6 +11550,12 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/properties': 5.8.0 + '@ethersproject/bignumber@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + bn.js: 4.12.2 + '@ethersproject/bignumber@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -10930,6 +11568,10 @@ snapshots: '@ethersproject/logger': 5.8.0 bn.js: 5.2.2 + '@ethersproject/bytes@5.6.1': + dependencies: + '@ethersproject/logger': 5.8.0 + '@ethersproject/bytes@5.7.0': dependencies: '@ethersproject/logger': 5.8.0 @@ -10938,6 +11580,10 @@ snapshots: dependencies: '@ethersproject/logger': 5.8.0 + '@ethersproject/constants@5.6.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/constants@5.7.0': dependencies: '@ethersproject/bignumber': 5.8.0 @@ -10946,6 +11592,19 @@ snapshots: dependencies: '@ethersproject/bignumber': 5.8.0 + '@ethersproject/contracts@5.6.0': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/contracts@5.7.0': dependencies: '@ethersproject/abi': 5.8.0 @@ -10993,6 +11652,17 @@ snapshots: - bufferutil - utf-8-validate + '@ethersproject/hash@5.6.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/hash@5.7.0': dependencies: '@ethersproject/abstract-signer': 5.8.0 @@ -11017,6 +11687,21 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 + '@ethersproject/hdnode@5.6.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + '@ethersproject/hdnode@5.7.0': dependencies: '@ethersproject/abstract-signer': 5.8.0 @@ -11047,6 +11732,22 @@ snapshots: '@ethersproject/transactions': 5.8.0 '@ethersproject/wordlists': 5.8.0 + '@ethersproject/json-wallets@5.6.0': + dependencies: + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + aes-js: 3.0.0 + scrypt-js: 3.0.1 + '@ethersproject/json-wallets@5.7.0': dependencies: '@ethersproject/abstract-signer': 5.8.0 @@ -11079,6 +11780,11 @@ snapshots: aes-js: 3.0.0 scrypt-js: 3.0.1 + '@ethersproject/keccak256@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + js-sha3: 0.8.0 + '@ethersproject/keccak256@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11089,10 +11795,16 @@ snapshots: '@ethersproject/bytes': 5.8.0 js-sha3: 0.8.0 + '@ethersproject/logger@5.6.0': {} + '@ethersproject/logger@5.7.0': {} '@ethersproject/logger@5.8.0': {} + '@ethersproject/networks@5.6.1': + dependencies: + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks@5.7.1': dependencies: '@ethersproject/logger': 5.8.0 @@ -11101,6 +11813,11 @@ snapshots: dependencies: '@ethersproject/logger': 5.8.0 + '@ethersproject/pbkdf2@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/pbkdf2@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11111,6 +11828,10 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/sha2': 5.8.0 + '@ethersproject/properties@5.6.0': + dependencies: + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties@5.7.0': dependencies: '@ethersproject/logger': 5.8.0 @@ -11119,6 +11840,31 @@ snapshots: dependencies: '@ethersproject/logger': 5.8.0 + '@ethersproject/providers@5.6.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/basex': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/networks': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/web': 5.8.0 + bech32: 1.1.4 + ws: 7.4.6(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@ethersproject/providers@5.7.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@ethersproject/abstract-provider': 5.8.0 @@ -11171,6 +11917,11 @@ snapshots: - bufferutil - utf-8-validate + '@ethersproject/random@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/random@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11181,6 +11932,11 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/rlp@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11191,6 +11947,12 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 + '@ethersproject/sha2@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + hash.js: 1.1.7 + '@ethersproject/sha2@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11203,6 +11965,15 @@ snapshots: '@ethersproject/logger': 5.8.0 hash.js: 1.1.7 + '@ethersproject/signing-key@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + bn.js: 4.12.2 + elliptic: 6.5.4 + hash.js: 1.1.7 + '@ethersproject/signing-key@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11221,6 +11992,15 @@ snapshots: elliptic: 6.6.1 hash.js: 1.1.7 + '@ethersproject/solidity@5.6.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/sha2': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/solidity@5.7.0': dependencies: '@ethersproject/bignumber': 5.8.0 @@ -11239,6 +12019,12 @@ snapshots: '@ethersproject/sha2': 5.8.0 '@ethersproject/strings': 5.8.0 + '@ethersproject/strings@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/strings@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11251,6 +12037,18 @@ snapshots: '@ethersproject/constants': 5.8.0 '@ethersproject/logger': 5.8.0 + '@ethersproject/transactions@5.6.0': + dependencies: + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/rlp': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/transactions@5.7.0': dependencies: '@ethersproject/address': 5.8.0 @@ -11275,6 +12073,12 @@ snapshots: '@ethersproject/rlp': 5.8.0 '@ethersproject/signing-key': 5.8.0 + '@ethersproject/units@5.6.0': + dependencies: + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/constants': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/units@5.7.0': dependencies: '@ethersproject/bignumber': 5.8.0 @@ -11287,6 +12091,24 @@ snapshots: '@ethersproject/constants': 5.8.0 '@ethersproject/logger': 5.8.0 + '@ethersproject/wallet@5.6.0': + dependencies: + '@ethersproject/abstract-provider': 5.8.0 + '@ethersproject/abstract-signer': 5.8.0 + '@ethersproject/address': 5.8.0 + '@ethersproject/bignumber': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/hdnode': 5.8.0 + '@ethersproject/json-wallets': 5.8.0 + '@ethersproject/keccak256': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/random': 5.8.0 + '@ethersproject/signing-key': 5.8.0 + '@ethersproject/transactions': 5.8.0 + '@ethersproject/wordlists': 5.8.0 + '@ethersproject/wallet@5.7.0': dependencies: '@ethersproject/abstract-provider': 5.8.0 @@ -11323,6 +12145,14 @@ snapshots: '@ethersproject/transactions': 5.8.0 '@ethersproject/wordlists': 5.8.0 + '@ethersproject/web@5.6.0': + dependencies: + '@ethersproject/base64': 5.8.0 + '@ethersproject/bytes': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/web@5.7.1': dependencies: '@ethersproject/base64': 5.8.0 @@ -11339,6 +12169,14 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 + '@ethersproject/wordlists@5.6.0': + dependencies: + '@ethersproject/bytes': 5.8.0 + '@ethersproject/hash': 5.8.0 + '@ethersproject/logger': 5.8.0 + '@ethersproject/properties': 5.8.0 + '@ethersproject/strings': 5.8.0 + '@ethersproject/wordlists@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -11487,6 +12325,59 @@ snapshots: graphql: 16.11.0 tslib: 2.8.1 + '@graphprotocol/common-ts@1.8.7(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10)': + dependencies: + '@graphprotocol/contracts': 2.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@graphprotocol/pino-sentry-simple': 0.7.1 + '@urql/core': 2.4.4(graphql@16.3.0) + '@urql/exchange-execute': 1.2.2(graphql@16.3.0) + body-parser: 1.19.1 + bs58: 4.0.1 + cors: 2.8.5 + cross-fetch: 3.1.5(encoding@0.1.13) + ethers: 5.6.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) + express: 4.17.3 + graphql: 16.3.0 + graphql-tag: 2.12.6(graphql@16.3.0) + helmet: 5.0.2 + morgan: 1.10.0 + ngeohash: 0.6.3 + pg: 8.7.3 + pg-hstore: 2.3.4 + pino: 7.6.0 + pino-multi-stream: 6.0.0 + prom-client: 14.0.1 + sequelize: 6.19.0(pg-hstore@2.3.4)(pg@8.7.3) + transitivePeerDependencies: + - bufferutil + - encoding + - ibm_db + - mariadb + - mysql2 + - pg-native + - snowflake-sdk + - sqlite3 + - supports-color + - tedious + - utf-8-validate + + '@graphprotocol/contracts@2.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + console-table-printer: 2.16.1 + ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@graphprotocol/pino-sentry-simple@0.7.1': + dependencies: + '@sentry/node': 5.30.0 + pumpify: 2.0.1 + split2: 3.2.2 + through2: 3.0.2 + transitivePeerDependencies: + - supports-color + '@graphql-codegen/core@3.1.0(graphql@16.11.0)': dependencies: '@graphql-codegen/plugin-helpers': 4.2.0(graphql@16.11.0) @@ -12160,6 +13051,10 @@ snapshots: dependencies: graphql: 16.11.0 + '@graphql-typed-document-node/core@3.2.0(graphql@16.3.0)': + dependencies: + graphql: 16.3.0 + '@graphql-yoga/logger@0.0.1': dependencies: tslib: 2.8.1 @@ -12394,7 +13289,7 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.28.4 - '@types/node': 20.19.14 + '@types/node': 20.19.41 find-up: 4.1.0 fs-extra: 8.1.0 @@ -12503,6 +13398,18 @@ snapshots: '@nomicfoundation/edr-linux-x64-musl': 0.12.0-next.23 '@nomicfoundation/edr-win32-x64-msvc': 0.12.0-next.23 + '@nomicfoundation/hardhat-chai-matchers@1.0.6(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10))': + dependencies: + '@ethersproject/abi': 5.8.0 + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)) + '@types/chai-as-promised': 7.1.8 + chai: 4.5.0 + chai-as-promised: 7.1.2(chai@4.5.0) + deep-eql: 4.1.4 + ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + ordinal: 1.0.3 + '@nomicfoundation/hardhat-chai-matchers@2.1.0(@nomicfoundation/hardhat-ethers@3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(chai@4.5.0)(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: '@nomicfoundation/hardhat-ethers': 3.1.0(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)) @@ -12847,6 +13754,11 @@ snapshots: ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10))': + dependencies: + ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + '@nomiclabs/hardhat-ethers@2.2.3(ethers@6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: ethers: 6.16.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -12868,6 +13780,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10))': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/address': 5.8.0 + cbor: 8.1.0 + chalk: 2.4.2 + debug: 4.4.3(supports-color@8.1.1) + fs-extra: 7.0.1 + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + lodash: 4.17.21 + semver: 6.3.1 + table: 6.9.0 + undici: 5.29.0 + transitivePeerDependencies: + - supports-color + '@nomiclabs/hardhat-waffle@2.0.6(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)))(@types/sinon-chai@3.2.12)(ethereum-waffle@4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(encoding@0.1.13)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))': dependencies: '@ethereum-waffle/chai': 4.0.10(@ensdomains/ens@0.4.5)(@ensdomains/resolver@0.2.4)(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -12906,6 +13834,8 @@ snapshots: '@openzeppelin/contracts@3.4.2': {} + '@openzeppelin/contracts@4.7.3': {} + '@openzeppelin/contracts@5.4.0': {} '@openzeppelin/defender-base-client@1.54.6(debug@4.4.3)(encoding@0.1.13)': @@ -12913,7 +13843,7 @@ snapshots: amazon-cognito-identity-js: 6.3.15(encoding@0.1.13) async-retry: 1.3.3 axios: 1.17.0(debug@4.4.3) - lodash: 4.17.21 + lodash: 4.18.1 node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - debug @@ -12989,7 +13919,7 @@ snapshots: '@ethersproject/abi': 5.8.0 '@openzeppelin/defender-base-client': 1.54.6(debug@4.4.3)(encoding@0.1.13) axios: 0.21.4(debug@4.4.3) - lodash: 4.17.21 + lodash: 4.18.1 node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - debug @@ -13532,6 +14462,16 @@ snapshots: typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 + '@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5))(typescript@4.9.5)': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/providers': 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + lodash: 4.18.1 + ts-essentials: 7.0.3(typescript@4.9.5) + typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5) + typescript: 4.9.5 + '@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@ethersproject/abi': 5.8.0 @@ -13550,6 +14490,16 @@ snapshots: typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3) typescript: 5.9.3 + '@typechain/hardhat@6.1.6(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5))(typescript@4.9.5))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5))': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/providers': 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@typechain/ethers-v5': 10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5))(typescript@4.9.5) + ethers: 5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + fs-extra: 9.1.0 + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + typechain: 8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5) + '@typechain/hardhat@6.1.6(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(@typechain/ethers-v5@10.2.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))(typescript@5.9.3))(ethers@5.8.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10))(typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3))': dependencies: '@ethersproject/abi': 5.8.0 @@ -13611,11 +14561,11 @@ snapshots: '@types/bn.js@4.11.6': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/bn.js@5.2.0': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/chai-as-promised@7.1.8': dependencies: @@ -13629,11 +14579,11 @@ snapshots: '@types/concat-stream@1.6.1': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/debug@4.1.12': dependencies: @@ -13643,17 +14593,17 @@ snapshots: '@types/form-data@0.0.33': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/graceful-fs@4.1.9': dependencies: @@ -13681,7 +14631,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/katex@0.16.7': {} @@ -13691,7 +14641,7 @@ snapshots: dependencies: '@types/abstract-leveldown': 7.2.5 '@types/level-errors': 3.0.2 - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/minimatch@6.0.0': dependencies: @@ -13699,7 +14649,7 @@ snapshots: '@types/mkdirp@0.5.2': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/mocha@10.0.10': {} @@ -13709,7 +14659,7 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 form-data: 4.0.5 '@types/node@20.19.14': @@ -13722,20 +14672,20 @@ snapshots: '@types/pbkdf2@3.1.2': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/prettier@2.7.3': {} '@types/prompts@2.4.9': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 kleur: 3.0.3 '@types/qs@6.14.0': {} '@types/secp256k1@4.0.6': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/sinon-chai@3.2.12': dependencies: @@ -13752,9 +14702,11 @@ snapshots: '@types/unist@2.0.11': {} + '@types/validator@13.15.10': {} + '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/yargs-parser@21.0.3': {} @@ -13853,6 +14805,18 @@ snapshots: '@typescript-eslint/types': 8.53.1 eslint-visitor-keys: 4.2.1 + '@urql/core@2.4.4(graphql@16.3.0)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.3.0) + graphql: 16.3.0 + wonka: 4.0.15 + + '@urql/exchange-execute@1.2.2(graphql@16.3.0)': + dependencies: + '@urql/core': 2.4.4(graphql@16.3.0) + graphql: 16.3.0 + wonka: 4.0.15 + '@wagmi/cli@2.5.1(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: abitype: 1.1.0(typescript@5.9.3)(zod@3.25.76) @@ -13979,6 +14943,11 @@ snapshots: level-supports: 1.0.1 xtend: 4.0.2 + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -14115,6 +15084,45 @@ snapshots: aproba@1.2.0: optional: true + arb-bridge-eth@0.7.8(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10): + dependencies: + '@openzeppelin/contracts': 3.4.2 + '@openzeppelin/contracts-0.8': '@openzeppelin/contracts@4.7.3' + '@openzeppelin/contracts-upgradeable': 3.4.2 + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - supports-color + - ts-node + - typescript + - utf-8-validate + + arb-bridge-peripherals@1.0.11(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10): + dependencies: + '@openzeppelin/contracts': 3.4.2 + '@openzeppelin/contracts-upgradeable': 3.4.2 + arb-bridge-eth: 0.7.8(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + arbos-precompiles: 1.0.2(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + optionalDependencies: + '@openzeppelin/upgrades-core': 1.44.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - ts-node + - typescript + - utf-8-validate + + arbos-precompiles@1.0.2(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10): + dependencies: + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - supports-color + - ts-node + - typescript + - utf-8-validate + arbos-precompiles@1.0.2(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10): dependencies: hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -14148,6 +15156,8 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-flatten@1.1.1: {} + array-ify@1.0.0: {} array-includes@3.1.9: @@ -14245,6 +15255,8 @@ snapshots: at-least-node@1.0.0: {} + atomic-sleep@1.0.0: {} + auto-bind@4.0.0: {} available-typed-arrays@1.0.7: @@ -14386,6 +15398,10 @@ snapshots: baseline-browser-mapping@2.8.4: {} + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 @@ -14414,9 +15430,11 @@ snapshots: file-uri-to-path: 1.0.0 optional: true + bintrees@1.0.2: {} + bip39@3.0.4: dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 create-hash: 1.2.0 pbkdf2: 3.1.3 randombytes: 2.1.0 @@ -14426,7 +15444,6 @@ snapshots: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - optional: true blakejs@1.2.1: {} @@ -14438,6 +15455,36 @@ snapshots: bn.js@5.2.2: {} + body-parser@1.19.1: + dependencies: + bytes: 3.1.1 + content-type: 1.0.5 + debug: 2.6.9 + depd: 1.1.2 + http-errors: 1.8.1 + iconv-lite: 0.4.24 + on-finished: 2.3.0 + qs: 6.9.6 + raw-body: 2.4.2 + type-is: 1.6.18 + transitivePeerDependencies: + - supports-color + + body-parser@1.19.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 1.1.2 + http-errors: 1.8.1 + iconv-lite: 0.4.24 + on-finished: 2.3.0 + qs: 6.9.7 + raw-body: 2.4.3 + type-is: 1.6.18 + transitivePeerDependencies: + - supports-color + bowser@2.14.1: {} boxen@5.1.2: @@ -14510,6 +15557,8 @@ snapshots: buffer-from@1.1.2: {} + buffer-writer@2.0.0: {} + buffer-xor@1.0.3: {} buffer-xor@2.0.2: @@ -14546,6 +15595,8 @@ snapshots: dependencies: streamsearch: 1.1.0 + bytes@3.1.1: {} + bytes@3.1.2: {} cac@6.7.14: {} @@ -14804,6 +15855,8 @@ snapshots: dependencies: restore-cursor: 5.1.0 + cli-spinners@2.9.2: {} + cli-table3@0.5.1: dependencies: object-assign: 4.1.1 @@ -14848,6 +15901,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone@1.0.4: {} + code-point-at@1.1.0: {} coingecko-api@1.0.10: {} @@ -14939,12 +15994,22 @@ snapshots: console-control-strings@1.1.0: optional: true + console-table-printer@2.16.1: + dependencies: + simple-wcswidth: 1.1.2 + constant-case@3.0.4: dependencies: no-case: 3.0.4 tslib: 2.8.1 upper-case: 2.0.2 + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + conventional-changelog-angular@7.0.0: dependencies: compare-func: 2.0.0 @@ -14962,6 +16027,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-signature@1.0.6: {} + cookie@0.4.2: {} core-js-pure@3.45.1: {} @@ -14970,6 +16037,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig-typescript-loader@6.1.0(@types/node@20.19.41)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: '@types/node': 20.19.41 @@ -15023,6 +16095,12 @@ snapshots: create-require@1.1.1: {} + cross-fetch@3.1.5(encoding@0.1.13): + dependencies: + node-fetch: 2.6.7(encoding@0.1.13) + transitivePeerDependencies: + - encoding + cross-fetch@3.2.0(encoding@0.1.13): dependencies: node-fetch: 2.7.0(encoding@0.1.13) @@ -15116,6 +16194,10 @@ snapshots: deep-is@0.1.4: {} + defaults@1.0.4: + dependencies: + clone: 1.0.4 + defer-to-connect@2.0.1: {} deferred-leveldown@5.3.0: @@ -15149,12 +16231,16 @@ snapshots: path-starts-with: 2.0.1 rimraf: 2.7.1 + depd@1.1.2: {} + depd@2.0.0: {} dependency-graph@0.11.0: {} dequal@2.0.3: {} + destroy@1.0.4: {} + destroy@1.2.0: {} detect-indent@6.1.0: {} @@ -15198,8 +16284,12 @@ snapshots: dotenv-expand@10.0.0: {} + dotenv@10.0.0: {} + dotenv@16.6.1: {} + dottie@2.0.7: {} + dset@3.1.4: {} dunder-proto@1.0.1: @@ -15208,6 +16298,13 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + eastasianwidth@0.2.0: {} ecc-jsbn@0.1.2: @@ -15275,7 +16372,6 @@ snapshots: end-of-stream@1.4.5: dependencies: once: 1.4.0 - optional: true enquirer@2.4.1: dependencies: @@ -15712,6 +16808,42 @@ snapshots: ethereum-cryptography: 0.1.3 rlp: 2.2.7 + ethers@5.6.2(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + '@ethersproject/abi': 5.6.0 + '@ethersproject/abstract-provider': 5.6.0 + '@ethersproject/abstract-signer': 5.6.0 + '@ethersproject/address': 5.6.0 + '@ethersproject/base64': 5.6.0 + '@ethersproject/basex': 5.6.0 + '@ethersproject/bignumber': 5.6.0 + '@ethersproject/bytes': 5.6.1 + '@ethersproject/constants': 5.6.0 + '@ethersproject/contracts': 5.6.0 + '@ethersproject/hash': 5.6.0 + '@ethersproject/hdnode': 5.6.0 + '@ethersproject/json-wallets': 5.6.0 + '@ethersproject/keccak256': 5.6.0 + '@ethersproject/logger': 5.6.0 + '@ethersproject/networks': 5.6.1 + '@ethersproject/pbkdf2': 5.6.0 + '@ethersproject/properties': 5.6.0 + '@ethersproject/providers': 5.6.2(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@ethersproject/random': 5.6.0 + '@ethersproject/rlp': 5.6.0 + '@ethersproject/sha2': 5.6.0 + '@ethersproject/signing-key': 5.6.0 + '@ethersproject/solidity': 5.6.0 + '@ethersproject/strings': 5.6.0 + '@ethersproject/transactions': 5.6.0 + '@ethersproject/units': 5.6.0 + '@ethersproject/wallet': 5.6.0 + '@ethersproject/web': 5.6.0 + '@ethersproject/wordlists': 5.6.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + ethers@5.7.2(bufferutil@4.0.9)(utf-8-validate@5.0.10): dependencies: '@ethersproject/abi': 5.7.0 @@ -15789,7 +16921,7 @@ snapshots: '@adraffy/ens-normalize': 1.10.1 '@noble/curves': 1.2.0 '@noble/hashes': 1.3.2 - '@types/node': 20.19.14 + '@types/node': 20.19.41 aes-js: 4.0.0-beta.5 tslib: 2.7.0 ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -15802,7 +16934,7 @@ snapshots: '@adraffy/ens-normalize': 1.10.1 '@noble/curves': 1.2.0 '@noble/hashes': 1.3.2 - '@types/node': 20.19.14 + '@types/node': 20.19.41 aes-js: 4.0.0-beta.5 tslib: 2.7.0 ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -15838,6 +16970,41 @@ snapshots: exponential-backoff@3.1.3: {} + express@4.17.3: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.19.2 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.4.2 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 1.1.2 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.1.2 + fresh: 0.5.2 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.3.0 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.8 + qs: 6.9.7 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.17.2 + serve-static: 1.14.2 + setprototypeof: 1.2.0 + statuses: 1.5.0 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} extendable-error@0.1.7: {} @@ -15878,6 +17045,8 @@ snapshots: dependencies: fast-decode-uri-component: 1.0.1 + fast-redact@3.5.0: {} + fast-uri@3.1.0: {} fast-url-parser@1.1.3: @@ -15896,6 +17065,8 @@ snapshots: path-expression-matcher: 1.5.0 strnum: 2.3.0 + fastify-warning@0.2.0: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -16060,6 +17231,8 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + forwarded@0.2.0: {} + fp-ts@1.19.3: {} fresh@0.5.2: {} @@ -16353,6 +17526,11 @@ snapshots: graphql: 16.11.0 tslib: 2.8.1 + graphql-tag@2.12.6(graphql@16.3.0): + dependencies: + graphql: 16.3.0 + tslib: 2.8.1 + graphql-ws@5.12.1(graphql@16.11.0): dependencies: graphql: 16.11.0 @@ -16396,6 +17574,8 @@ snapshots: graphql@16.11.0: {} + graphql@16.3.0: {} + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -16418,6 +17598,12 @@ snapshots: delete-empty: 3.0.0 hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) + hardhat-abi-exporter@2.11.0(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)): + dependencies: + '@ethersproject/abi': 5.8.0 + delete-empty: 3.0.0 + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + hardhat-contract-sizer@2.10.1(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: chalk: 4.1.2 @@ -16554,7 +17740,56 @@ snapshots: transitivePeerDependencies: - supports-color - hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10): + hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10): + dependencies: + '@ethereumjs/util': 9.1.0 + '@ethersproject/abi': 5.8.0 + '@nomicfoundation/edr': 0.12.0-next.23 + '@nomicfoundation/solidity-analyzer': 0.1.2 + '@sentry/node': 5.30.0 + adm-zip: 0.4.16 + aggregate-error: 3.1.0 + ansi-escapes: 4.3.2 + boxen: 5.1.2 + chokidar: 4.0.3 + ci-info: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + enquirer: 2.4.1 + env-paths: 2.2.1 + ethereum-cryptography: 1.2.0 + find-up: 5.0.0 + fp-ts: 1.19.3 + fs-extra: 7.0.1 + immutable: 4.3.7 + io-ts: 1.10.4 + json-stream-stringify: 3.1.6 + keccak: 3.0.4 + lodash: 4.18.1 + micro-eth-signer: 0.14.0 + mnemonist: 0.38.5 + mocha: 10.8.2 + p-map: 4.0.0 + picocolors: 1.1.1 + raw-body: 2.5.2 + resolve: 1.17.0 + semver: 6.3.1 + solc: 0.8.26(debug@4.4.3) + source-map-support: 0.5.21 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.15 + tsort: 0.0.1 + undici: 5.29.0 + uuid: 8.3.2 + ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) + optionalDependencies: + ts-node: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10): dependencies: '@ethereumjs/util': 9.1.0 '@ethersproject/abi': 5.8.0 @@ -16596,8 +17831,8 @@ snapshots: uuid: 8.3.2 ws: 7.5.11(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - ts-node: 10.9.2(@types/node@20.19.14)(typescript@5.9.3) - typescript: 5.9.3 + ts-node: 10.9.2(@types/node@20.19.41)(typescript@4.9.5) + typescript: 4.9.5 transitivePeerDependencies: - bufferutil - supports-color @@ -16787,6 +18022,8 @@ snapshots: heap@0.2.7: {} + helmet@5.0.2: {} + hermes-estree@0.29.1: {} hermes-estree@0.35.0: {} @@ -16822,6 +18059,14 @@ snapshots: http-cache-semantics@4.2.0: {} + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -16847,7 +18092,7 @@ snapshots: http-response-object@3.0.2: dependencies: - '@types/node': 20.19.14 + '@types/node': 20.19.41 http-signature@1.2.0: dependencies: @@ -16932,6 +18177,8 @@ snapshots: indent-string@4.0.0: {} + inflection@1.13.4: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -16963,6 +18210,26 @@ snapshots: strip-ansi: 6.0.1 through: 2.3.8 + inquirer@8.2.7(@types/node@20.19.41): + dependencies: + '@inquirer/external-editor': 1.0.2(@types/node@20.19.41) + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + figures: 3.2.0 + lodash: 4.18.1 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + transitivePeerDependencies: + - '@types/node' + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -16983,6 +18250,8 @@ snapshots: ip-address@10.0.1: {} + ipaddr.js@1.9.1: {} + is-absolute@1.0.0: dependencies: is-relative: 1.0.0 @@ -17078,6 +18347,8 @@ snapshots: is-hexadecimal@2.0.1: {} + is-interactive@1.0.0: {} + is-lambda@1.0.1: {} is-lower-case@2.0.2: @@ -17303,11 +18574,6 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -17753,6 +19019,8 @@ snapshots: mdurl@2.0.0: {} + media-typer@0.3.0: {} + memdown@5.1.0: dependencies: abstract-leveldown: 6.2.3 @@ -17768,6 +19036,8 @@ snapshots: meow@12.1.1: {} + merge-descriptors@1.0.1: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -17785,6 +19055,8 @@ snapshots: optionalDependencies: '@types/node': 20.19.14 + methods@1.1.2: {} + metro-babel-transformer@0.83.7: dependencies: '@babel/core': 7.28.4 @@ -18289,6 +19561,22 @@ snapshots: yargs-parser: 20.2.9 yargs-unparser: 2.0.0 + moment-timezone@0.5.48: + dependencies: + moment: 2.31.0 + + moment@2.31.0: {} + + morgan@1.10.0: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.0.2 + transitivePeerDependencies: + - supports-color + mri@1.2.0: {} ms@2.0.0: {} @@ -18340,6 +19628,8 @@ snapshots: split2: 3.2.2 through2: 4.0.2 + negotiator@0.6.3: {} + negotiator@0.6.4: {} negotiator@1.0.0: {} @@ -18348,6 +19638,8 @@ snapshots: neoqs@6.13.0: {} + ngeohash@0.6.3: {} + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -18372,6 +19664,12 @@ snapshots: dependencies: lodash: 4.18.1 + node-fetch@2.6.7(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -18512,6 +19810,8 @@ snapshots: obliterator@2.0.5: {} + on-exit-leak-free@0.2.0: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -18520,6 +19820,8 @@ snapshots: dependencies: ee-first: 1.1.1 + on-headers@1.0.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -18563,6 +19865,18 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + ordinal@1.0.3: {} os-locale@1.4.0: @@ -18673,6 +19987,8 @@ snapshots: dependencies: quansync: 0.2.11 + packet-reader@1.0.0: {} + param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -18764,6 +20080,8 @@ snapshots: path-starts-with@2.0.1: {} + path-to-regexp@0.1.7: {} + path-type@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -18789,6 +20107,40 @@ snapshots: performance-now@2.1.0: {} + pg-connection-string@2.14.1: {} + + pg-hstore@2.3.4: + dependencies: + underscore: 1.13.8 + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.7.3): + dependencies: + pg: 8.7.3 + + pg-protocol@1.16.1: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.7.3: + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.14.1 + pg-pool: 3.14.0(pg@8.7.3) + pg-protocol: 1.16.1 + pg-types: 2.2.0 + pgpass: 1.0.6 + + pgpass@1.0.6: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -18811,12 +20163,46 @@ snapshots: pinkie@2.0.4: {} + pino-abstract-transport@0.5.0: + dependencies: + duplexify: 4.1.3 + split2: 4.2.0 + + pino-multi-stream@6.0.0: + dependencies: + pino: 7.6.0 + + pino-std-serializers@4.0.0: {} + + pino@7.6.0: + dependencies: + fast-redact: 3.5.0 + fastify-warning: 0.2.0 + on-exit-leak-free: 0.2.0 + pino-abstract-transport: 0.5.0 + pino-std-serializers: 4.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.1.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 2.8.0 + thread-stream: 0.13.2 + pirates@4.0.7: {} pluralize@8.0.0: {} possible-typed-array-names@1.1.0: {} + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prebuild-install@5.3.6: dependencies: detect-libc: 1.0.3 @@ -18878,6 +20264,10 @@ snapshots: process-nextick-args@2.0.1: {} + prom-client@14.0.1: + dependencies: + tdigest: 0.1.3 + promise-retry@2.0.1: dependencies: err-code: 2.0.3 @@ -18910,6 +20300,11 @@ snapshots: proto-list@1.2.4: {} + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@2.1.0: {} prr@1.0.1: {} @@ -18922,7 +20317,12 @@ snapshots: dependencies: end-of-stream: 1.4.5 once: 1.4.0 - optional: true + + pumpify@2.0.1: + dependencies: + duplexify: 4.1.3 + inherits: 2.0.4 + pump: 3.0.3 punycode.js@2.3.1: {} @@ -18944,6 +20344,10 @@ snapshots: qs@6.5.3: {} + qs@6.9.6: {} + + qs@6.9.7: {} + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -18952,6 +20356,8 @@ snapshots: dependencies: inherits: 2.0.4 + quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} randombytes@2.1.0: @@ -18960,6 +20366,20 @@ snapshots: range-parser@1.2.1: {} + raw-body@2.4.2: + dependencies: + bytes: 3.1.1 + http-errors: 1.8.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + raw-body@2.4.3: + dependencies: + bytes: 3.1.2 + http-errors: 1.8.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + raw-body@2.5.2: dependencies: bytes: 3.1.2 @@ -19081,13 +20501,15 @@ snapshots: readdirp@4.1.2: {} + real-require@0.1.0: {} + rechoir@0.6.2: dependencies: resolve: 1.22.12 recursive-readdir@2.2.3: dependencies: - minimatch: 3.1.2 + minimatch: 3.1.5 reduce-flatten@2.0.0: {} @@ -19211,6 +20633,8 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + retry-as-promised@5.0.0: {} + retry@0.12.0: {} retry@0.13.1: {} @@ -19284,6 +20708,10 @@ snapshots: dependencies: tslib: 1.14.1 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -19307,6 +20735,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sc-istanbul@0.4.6: @@ -19317,7 +20747,7 @@ snapshots: esprima: 2.7.3 glob: 5.0.15 handlebars: 4.7.8 - js-yaml: 3.14.1 + js-yaml: 3.14.2 mkdirp: 0.5.6 nopt: 3.0.6 once: 1.4.0 @@ -19352,6 +20782,24 @@ snapshots: semver@7.8.2: {} + send@0.17.2: + dependencies: + debug: 2.6.9 + depd: 1.1.2 + destroy: 1.0.4 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 1.8.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.3.0 + range-parser: 1.2.1 + statuses: 1.5.0 + transitivePeerDependencies: + - supports-color + send@0.19.2: dependencies: debug: 2.6.9 @@ -19376,12 +20824,47 @@ snapshots: tslib: 2.8.1 upper-case-first: 2.0.2 + sequelize-pool@7.1.0: {} + + sequelize@6.19.0(pg-hstore@2.3.4)(pg@8.7.3): + dependencies: + '@types/debug': 4.1.12 + '@types/validator': 13.15.10 + debug: 4.4.3(supports-color@8.1.1) + dottie: 2.0.7 + inflection: 1.13.4 + lodash: 4.18.1 + moment: 2.31.0 + moment-timezone: 0.5.48 + pg-connection-string: 2.14.1 + retry-as-promised: 5.0.0 + semver: 7.8.2 + sequelize-pool: 7.1.0 + toposort-class: 1.0.1 + uuid: 8.3.2 + validator: 13.15.35 + wkx: 0.5.0 + optionalDependencies: + pg: 8.7.3 + pg-hstore: 2.3.4 + transitivePeerDependencies: + - supports-color + serialize-error@2.1.0: {} serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 + serve-static@1.14.2: + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.17.2 + transitivePeerDependencies: + - supports-color + serve-static@1.16.3: dependencies: encodeurl: 2.0.0 @@ -19490,6 +20973,8 @@ snapshots: simple-concat: 1.0.1 optional: true + simple-wcswidth@1.1.2: {} + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -19656,6 +21141,29 @@ snapshots: shelljs: 0.8.5 web3-utils: 1.10.4 + solidity-coverage@0.8.17(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10)): + dependencies: + '@ethersproject/abi': 5.8.0 + '@solidity-parser/parser': 0.20.2 + chalk: 2.4.2 + death: 1.1.0 + difflib: 0.2.4 + fs-extra: 8.1.0 + ghost-testrpc: 0.0.2 + global-modules: 2.0.0 + globby: 10.0.2 + hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5))(typescript@4.9.5)(utf-8-validate@5.0.10) + jsonschema: 1.5.0 + lodash: 4.18.1 + mocha: 10.8.2 + node-emoji: 1.11.0 + pify: 4.0.1 + recursive-readdir: 2.2.3 + sc-istanbul: 0.4.6 + semver: 7.8.2 + shelljs: 0.8.5 + web3-utils: 1.10.4 + solidity-coverage@0.8.17(hardhat@2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10)): dependencies: '@ethersproject/abi': 5.8.0 @@ -19685,6 +21193,10 @@ snapshots: hardhat: 2.28.6(bufferutil@4.0.9)(ts-node@10.9.2(@types/node@20.19.14)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@5.0.10) solidity-ast: 0.4.61 + sonic-boom@2.8.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -19767,6 +21279,8 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + stream-shift@1.0.3: {} + streamsearch@1.1.0: {} string-argv@0.3.2: {} @@ -19947,6 +21461,10 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tdigest@0.1.3: + dependencies: + bintrees: 1.0.2 + term-size@2.2.1: {} terser@5.48.0: @@ -19972,7 +21490,7 @@ snapshots: dependencies: '@types/concat-stream': 1.6.1 '@types/form-data': 0.0.33 - '@types/node': 20.19.14 + '@types/node': 20.19.41 '@types/qs': 6.14.0 caseless: 0.12.0 concat-stream: 1.6.2 @@ -19982,8 +21500,17 @@ snapshots: promise: 8.3.0 qs: 6.14.0 + thread-stream@0.13.2: + dependencies: + real-require: 0.1.0 + throat@5.0.0: {} + through2@3.0.2: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + through2@4.0.2: dependencies: readable-stream: 3.6.2 @@ -20021,6 +21548,8 @@ snapshots: toidentifier@1.0.1: {} + toposort-class@1.0.1: {} + tough-cookie@2.5.0: dependencies: psl: 1.15.0 @@ -20041,6 +21570,10 @@ snapshots: command-line-usage: 6.1.3 string-format: 2.0.0 + ts-essentials@7.0.3(typescript@4.9.5): + dependencies: + typescript: 4.9.5 + ts-essentials@7.0.3(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -20063,6 +21596,24 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-node@10.9.2(@types/node@20.19.41)(typescript@4.9.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.41 + acorn: 8.16.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + ts-node@10.9.2(@types/node@20.19.41)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -20148,6 +21699,27 @@ snapshots: type-fest@0.7.1: {} + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@4.9.5): + dependencies: + '@types/prettier': 2.7.3 + debug: 4.4.3(supports-color@8.1.1) + fs-extra: 7.0.1 + glob: 7.1.7 + js-sha3: 0.8.0 + lodash: 4.18.1 + mkdirp: 1.0.4 + prettier: 2.8.8 + ts-command-line-args: 2.5.1 + ts-essentials: 7.0.3(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + typechain@8.3.2(patch_hash=b34ed6afcf99760666fdc85ecb2094fdd20ce509f947eb09cef21665a2a6a1d6)(typescript@5.9.3): dependencies: '@types/prettier': 2.7.3 @@ -20210,6 +21782,8 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@4.9.5: {} + typescript@5.9.3: {} typical@4.0.0: {} @@ -20234,6 +21808,8 @@ snapshots: unc-path-regex@0.1.2: {} + underscore@1.13.8: {} + undici-types@6.21.0: {} undici@5.29.0: @@ -20326,8 +21902,12 @@ snapshots: validate-npm-package-name@5.0.1: {} + validator@13.15.35: {} + value-or-promise@1.0.12: {} + vary@1.1.2: {} + verror@1.10.0: dependencies: assert-plus: 1.0.0 @@ -20374,6 +21954,10 @@ snapshots: dependencies: makeerror: 1.0.12 + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + web-streams-polyfill@3.3.3: {} web3-utils@1.10.4: @@ -20471,6 +22055,12 @@ snapshots: window-size@0.2.0: {} + wkx@0.5.0: + dependencies: + '@types/node': 20.19.41 + + wonka@4.0.15: {} + word-wrap@1.2.5: {} wordwrap@1.0.0: {} diff --git a/scripts/contracts-migration/manifest.json b/scripts/contracts-migration/manifest.json index 32237d239..6f603d7c4 100644 --- a/scripts/contracts-migration/manifest.json +++ b/scripts/contracts-migration/manifest.json @@ -16,5 +16,11 @@ "PaymentsEscrow": "packages/horizon/build/contracts/contracts/payments/PaymentsEscrow.sol/PaymentsEscrow.json", "SubgraphService": "packages/subgraph-service/build/contracts/contracts/SubgraphService.sol/SubgraphService.json", "DisputeManager": "packages/subgraph-service/build/contracts/contracts/DisputeManager.sol/DisputeManager.json", - "AllocationHandler": "packages/subgraph-service/build/contracts/contracts/libraries/AllocationHandler.sol/AllocationHandler.json" + "AllocationHandler": "packages/subgraph-service/build/contracts/contracts/libraries/AllocationHandler.sol/AllocationHandler.json", + "Billing": "packages/billing/build/artifacts/contracts/Billing.sol/Billing.json", + "BillingConnector": "packages/billing/build/artifacts/contracts/BillingConnector.sol/BillingConnector.json", + "BanxaWrapper": "packages/billing/build/artifacts/contracts/BanxaWrapper.sol/BanxaWrapper.json", + "Governed": "packages/billing/build/artifacts/contracts/Governed.sol/Governed.json", + "Rescuable": "packages/billing/build/artifacts/contracts/Rescuable.sol/Rescuable.json", + "AddressAliasHelper": "packages/billing/build/artifacts/contracts/arbitrum/AddressAliasHelper.sol/AddressAliasHelper.json" } diff --git a/scripts/contracts-migration/reference.json b/scripts/contracts-migration/reference.json index 9f27982f0..b352c60dd 100644 --- a/scripts/contracts-migration/reference.json +++ b/scripts/contracts-migration/reference.json @@ -1,8 +1,24 @@ { + "AddressAliasHelper": { + "bytecode": "f66d4b1dc59dcabe26442384c59819b8a63e08cbf21d9283655de14a276cc9ba", + "deployedBytecode": "ea10e79b42a6d5d0a19bf59f9eb4df0d210ddb233340020ce474f78eefe57c85" + }, "AllocationHandler": { "bytecode": "32c496212c35507775978c944588ba0fc72bf48edfad06b207b02cad2a4b4308", "deployedBytecode": "5e1aae4006573c7e7780a8b32abdc442cc12559b788be8461edc81508a4fb034" }, + "BanxaWrapper": { + "bytecode": "2388e166f6ae19c5c670d2487b5abea1753bd0361e2f379518f3082eb33bfed3", + "deployedBytecode": "dc4e927bd3872b1afefc7ec66abfa783adfa23a7e5329ee8b4563d743e219483" + }, + "Billing": { + "bytecode": "a61962d7d326fff60cc34073906d72f1d1d5b71cfce7b4bf20d1099d64e5d605", + "deployedBytecode": "709dee34ebdefd0cdd2859e793cbd2d646d70c595c569a305692c39516c3c415" + }, + "BillingConnector": { + "bytecode": "a7a493ee14bbd0f9ca0883b30a80b2916e2ec43e21b4f7b0995948ef4852df17", + "deployedBytecode": "6de4fc80472253b00daa29c1dcd05c3670d48a0e3b9548eddcec71229d63899a" + }, "BridgeEscrow": { "bytecode": "5c106df052caf18fcabdacbef3255435a7e561053b528bf30233088d3a2caba0", "deployedBytecode": "e2eaccad0ebf55b4e40026baeb6a72e612180d49d745f6a43154f8d2477a8b74" @@ -19,6 +35,10 @@ "bytecode": "fb46ad844725360c5048cd427bd4f2c230b62ce22673c1b8edd3574fb2f90eac", "deployedBytecode": "bfd994416a46234114f88f2d28a0d40d60441ff305a105908bb1d1089b2dac91" }, + "Governed": { + "bytecode": "123b237c048f3597915f2989c98d535bfbc0b0e5f81dbca8b733a5c1af926c86", + "deployedBytecode": "8fb64825ce4793603584873f5b2a8c9e2642c07165bbffd03076d25955128bed" + }, "GraphCurationToken": { "bytecode": "c88b7af1a3200b97603a7c30352662b80882ef467bcee3991a0b3c40981bb874", "deployedBytecode": "7eef834bd6fb58360c7349aa645e5a41fcbdd5c7a2783eb85556e93b51ceda3a" @@ -59,6 +79,10 @@ "bytecode": "c5ee87225cd0c023d298d810457736f2a6fcf011d8e9be8bfeea96c4c34936f5", "deployedBytecode": "07e1e1055195ed396e9ac8df7d84d98a37644bea8b48f9ccab0f31f677d53d99" }, + "Rescuable": { + "bytecode": "c92b188473494f49c52c80869859a8247da794f2f5795e73f87b53afff1029ce", + "deployedBytecode": "b232687b253af14b616e69f6d2ea289e6be5cd9c9965f967159da4e1f63994ae" + }, "SubgraphNFT": { "bytecode": "830d22ac49214e1f6ba157c5cc7794d69277e96d5c93a773272ab3055a13426a", "deployedBytecode": "9718d61570d90867068568f9b0f33a9633e55bb3cf8e8cea1f11ab5e8298f36a"