diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 7f7979a86..c76b7260b 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -3,6 +3,14 @@ name: Run E2E tests on: push: branches: [dev] + paths: + - contracts/** + - config/** + - e2e/** + - cli/** + - tasks/** + - scripts/** + - hardhat.config.ts pull_request: {} jobs: @@ -25,9 +33,7 @@ jobs: run: | git clone https://github.com/edgeandnode/nitro pushd nitro - git checkout ci git submodule update --init --recursive ./test-node.bash --init --no-blockscout --detach popd - NETWORK=localnitrol1 ADDRESS_BOOK=addresses.json GRAPH_CONFIG=config/graph.localhost.yml yarn test:e2e - NETWORK=localnitrol2 ADDRESS_BOOK=addresses.json GRAPH_CONFIG=config/graph.arbitrum-localhost.yml yarn test:e2e + L1_NETWORK=localnitrol1 L2_NETWORK=localnitrol2 yarn test:e2e \ No newline at end of file diff --git a/.github/workflows/gre.yml b/.github/workflows/gre.yml index 2e5ade371..3a2de02fc 100644 --- a/.github/workflows/gre.yml +++ b/.github/workflows/gre.yml @@ -3,6 +3,8 @@ name: Run GRE tests on: push: branches: [dev] + paths: + - gre/** pull_request: {} jobs: diff --git a/.gitignore b/.gitignore index 1fcf808ed..96fb37310 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,12 @@ bin/ # Coverage and other reports /reports coverage.json + +# Local test files +addresses-local.json +localNetwork.json +arbitrum-addresses-local.json +tx-*.log + +# Keys +.keystore diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index d3222acaa..50c8fee59 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -40,7 +40,7 @@ To deploy to a different network execute: yarn deploy -- --network {networkName} # Example -yarn deploy -- --network kovan +yarn deploy -- --network goerli ``` The network must be configured in the `hardhat.config.ts` as explained in https://hardhat.org/config. @@ -100,7 +100,7 @@ Some contracts require the address from previously deployed contracts. For that ### Deploying a new testnet 1. Make sure contracts are up to date as you please. -2. `yarn deploy-rinkeby` to deploy to Rinkeby. This will create new contracts with new addresses in `addresses.json`. +2. `yarn deploy-goerli` to deploy to Goerli. This will create new contracts with new addresses in `addresses.json`. 3. Update the `package.json` and `package-lock.json` files with the new package version and publish a new npm package with `npm publish`. You can dry-run the files to be uploaded by running `npm publish --dry-run`. 4. Merge this update into master, branch off and save for whatever version of the testnet is going on, and then tag this on the github repo, pointing to your branch (ex. at `testnet-phase-1` branch). This way we can always get the contract code for testnet, while continuing to do work on mainnet. 5. Pull the updated package into the subgraph, and other apps that depend on the package.json. diff --git a/README.md b/README.md index 66d20f9fc..a7cc801d7 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ yarn add @graphprotocol/contracts # Contract Addresses -The testnet runs on Rinkeby, while mainnet is on Ethereum Mainnet. The addresses for both of these can be found in `./addresses.json`. +The testnet runs on Goerli, while mainnet is on Ethereum Mainnet. The addresses for both of these can be found in `./addresses.json`. # Local Setup @@ -92,7 +92,7 @@ The most straightforward way to interact with the contracts is through the hardh ``` # A console to interact with testnet contracts -npx hardhat console --network rinkeby +npx hardhat console --network goerli ``` ### Hardhat Tasks diff --git a/TESTING.md b/TESTING.md index 88b72a31c..08b89c1a5 100644 --- a/TESTING.md +++ b/TESTING.md @@ -33,23 +33,38 @@ There are several types of e2e tests which can be run separately: - Read and write interactions with the blockchain. _Requires an account with sufficient balance!_ - Example: a test validating that a user can add signal to a subgraph. -### Hardhat local node +### Hardhat local node (L1) -To run all e2e tests against a hardhat local node run: +It can be useful to run E2E tests against a fresh protocol deployment on L1, this can be done with the following: ```bash -yarn test:e2e +L1_NETWORK=localhost yarn test:e2e ``` -The command will perform the following actions: +The command will: +- start a hardhat local node +- deploy the L1 protocol +- configure the new L1 deployment +- Run all L1 e2e tests -- Start a hardhat node (localhost) -- Run `migrate:accounts` hardhat task to create keys for all protocol roles (deployer, governor, arbiter, etc). This currently doesn't support multisig accounts. -- Run `migrate` hardhat task to deploy the protocol -- Run `migrate:ownership` hardhat task to transfer ownership of governed contracts to the governor -- Run `migrate:unpause` to unpause the protocol -- Run `e2e` hardhat task to run all deployment tests (config and init) -- Run `e2e:scenario` hardhat task to run a scenario +### Arbitrum Nitro testnodes (L1/L2) + +If you want to test the protocol on an L1/L2 setup, you can run: + +```bash +L1_NETWORK=localnitrol1 L2_NETWORK=localnitrol2 yarn test:e2e +``` + +In this case the command will: +- deploy the L1 protocol +- configure the new L1 deployment +- deploy the L2 protocol +- configure the new L2 deployment +- configure the L1/L2 bridge +- Run all L1 e2e tests +- Run all L2 e2e tests + +Note that you'll need to setup the testnodes before running the tests. See [Quick Setup](https://github.com/edgeandnode/nitro#quick-setup) for details on how to do this. ### Other networks @@ -57,13 +72,13 @@ To run tests against a live testnet or even mainnet run: ```bash # All e2e tests -npx hardhat e2e --network --graph-config config/graph..yml +ARBITRUM_ADDRESS_BOOK= npx hardhat e2e --network --l1-graph-config config/graph..yml --l2-graph-config config/graph..yml # Only deployment config tests -npx hardhat e2e:config --network --graph-config config/graph..yml +ARBITRUM_ADDRESS_BOOK= npx hardhat e2e:config --network --l1-graph-config config/graph..yml --l2-graph-config config/graph..yml # Only deployment init tests -npx hardhat e2e:init --network --graph-config config/graph..yml +ARBITRUM_ADDRESS_BOOK= npx hardhat e2e:init --network --l1-graph-config config/graph..yml --l2-graph-config config/graph..yml # Only a specific scenario npx hardhat e2e:scenario --network --graph-config config/graph..yml @@ -82,26 +97,4 @@ Scenarios are defined by an optional script and a test file: - They run before the test file. - Test file - Should be named e2e/scenarios/{scenario-name}.test.ts. - - Standard chai/mocha/hardhat/ethers test file. - -## Setting up Arbitrum's testnodes - -Arbitrum provides a quick way of setting up L1 and L2 testnodes for local development and testing. The following steps will guide you through the process of setting them up. Note that a local installation of Docker and Docker Compose is required. - -```bash -git clone https://github.com/offchainlabs/nitro -cd nitro -git submodule update --init --recursive - -# Apply any changes you might want, see below for more info, and then start the testnodes -./test-node.bash --init -``` - -**Useful information** -- L1 RPC: [http://localhost:8545](http://localhost:8545/) -- L2 RPC: [http://localhost:8547](http://localhost:8547/) -- Blockscout explorer (L2 only): [http://localhost:4000/](http://localhost:4000/) -- Prefunded genesis key (L1 and L2): `e887f7d17d07cc7b8004053fb8826f6657084e88904bb61590e498ca04704cf2` - -**Enable automine on L1** -In `docker-compose.yml` file edit the `geth` service command by removing the `--dev.period 1` flag. \ No newline at end of file + - Standard chai/mocha/hardhat/ethers test file. \ No newline at end of file diff --git a/addresses.json b/addresses.json index 3855f2d1f..24307ea8d 100644 --- a/addresses.json +++ b/addresses.json @@ -303,324 +303,6 @@ "txHash": "0x106c31f2c24a5285c47a766422823766f1c939034513e85613d70d99ef697173" } }, - "4": { - "IENS": { - "address": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" - }, - "IEthereumDIDRegistry": { - "address": "0xdCa7EF03e98e0DC2B855bE647C39ABe984fcF21B" - }, - "EpochManager": { - "address": "0x23090b246Ad47dB85352b666cAff36760D087a69", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - }, - { - "name": "lengthInBlocks", - "value": 1108 - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0xcf271ece428e4e0539337bc2eb79c97a9d5337fae86d76f824ce6f92abb367ae", - "proxy": true, - "implementation": { - "address": "0x5fDB51B4126D28b176AC1FE41dd47f72F047eF63", - "creationCodeHash": "0xf03074bb7f026a2574b6ffb5d0f63f0c4fee81e004e1c46ef262dd5802d3384f", - "runtimeCodeHash": "0x0d078a0bf778c6c713c46979ac668161a0a0466356252e47082f80912e4495b2", - "txHash": "0xcc086cfaa1441412731ca073dfa68ca76d634b129b416dc75af7cea1b5292e52" - } - }, - "GraphToken": { - "address": "0x54Fe55d5d255b8460fB3Bc52D5D676F9AE5697CD", - "constructorArgs": [ - { - "name": "initialSupply", - "value": "10000000000000000000000000000" - } - ], - "creationCodeHash": "0x30da7a30d71fbd41d3327e4d0183401f257af3e905a0c68ebfd18b590b27b530", - "runtimeCodeHash": "0xb964f76194a04272e7582382a4d6bd6271bbb90deb5c1fd3ae3913504ea3a830", - "txHash": "0xfd3da9962b88397134b71259287bb056b60cdd092f91c114dcd7c794e6237c78" - }, - "ServiceRegistry": { - "address": "0xB2cD4D1205A55303ef0DeC2e4EfB5338f9c182bc", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0x230cde8f618e46f42299e19dced8dcbe1f99b4ee8423441fc7572dea0f457935", - "proxy": true, - "implementation": { - "address": "0x4BaED79823A50c2b3387211f178235FEe8Cc7Ab7", - "creationCodeHash": "0xf5fa541b43d15fade518feb63a95a73b9c67626108ead259e444af3a7ae1804f", - "runtimeCodeHash": "0x9856d2c2985f410f2f77e456fe6163827ea5251eb5e3f3768d3d4f8868187882", - "txHash": "0xd17bdc8e05933c7146105e0e22ab5390677544cad224eb0dcd56c8088585d29a" - } - }, - "GraphCurationToken": { - "address": "0x37014c7b321da7927b6662859dc9a929543386c6", - "creationCodeHash": "0x7e9a56b6fc05d428d1c1116eaa88a658f05487b493d847bfe5c69e35ec34f092", - "runtimeCodeHash": "0x587f9d4e9ecf9e7048d9f42f027957ca34ee6a95ca37d9758d8cd0ee16e89818", - "txHash": "0xd108447808e285fcde0b9aaee9e8446af91e9983041aa47ff9410c2c2862938b" - }, - "Curation": { - "address": "0x5cCaB32d30Ca0969a8f3D495e1F67b3A90d39b14", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - }, - { - "name": "bondingCurve", - "value": "0xB4B6857dFcE1a31851c0fAde87E94Fc05053916D" - }, - { - "name": "reserveRatio", - "value": 500000 - }, - { - "name": "curationTaxPercentage", - "value": 25000 - }, - { - "name": "minimumCurationDeposit", - "value": "1000000000000000000" - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0x1d8707dc7b85d4fc7fe83819acc40b8e4b3ad0b3e8257a44edcc8fb587768dfb", - "proxy": true, - "implementation": { - "address": "0x6cafc4eb2be922505ff8dedcb73e2c1599d7e352", - "creationCodeHash": "0x4aea53d73a1b7b00db3ba36023a70f4e53df68f9b42cb8932afb9cf1837a8cf7", - "runtimeCodeHash": "0x6e5cb73148de597888b628c2e0d97fa0f66ee4867ee0905314034f9031d52872", - "txHash": "0x9188f422354f01613cac4e270fb053235f9071c3c987c7eaf120601d16fa6d6e" - } - }, - "GNS": { - "address": "0x4beb7299221807Cd47C2fa118c597C51Cc2fEC99", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - }, - { - "name": "bondingCurve", - "value": "0xB4B6857dFcE1a31851c0fAde87E94Fc05053916D" - }, - { - "name": "didRegistry", - "value": "0xdca7ef03e98e0dc2b855be647c39abe984fcf21b" - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0x4b3882c3ed81f8b937f207e303d7c16074369da9c72e47212c8004ed799332d7", - "proxy": true, - "implementation": { - "address": "0xA05B12aa4913082562b79ceCcDDe27E19fA00972", - "creationCodeHash": "0x86499a1c90a73b062c0d25777379cdf52085e36c7f4ce44016adc7775ea24355", - "runtimeCodeHash": "0x85cc02c86b4ee2c1b080c6f70500f775bb0fab7960ce62444a8018f3af07af75", - "txHash": "0xfc5f3fe67f9c72e88dc5ab99debe9e30e79fddf5ff4f8d06a66180759eb72177" - } - }, - "RewardsManager": { - "address": "0x460cA3721131BC978e3CF3A49EfC545A2901A828", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - }, - { - "name": "issuanceRate", - "value": "1000000012184945188" - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0xfff3ccaaeb64a173f9461416d6e3f37ae1e6d8beaeb17ca1e23c264c1d3bbc02", - "proxy": true, - "implementation": { - "address": "0x7Cd54459a1B92c14554b857325AfeE1d2B065bbe", - "creationCodeHash": "0xfec6d35d9de8a7234e77484ee4fa5d6867697d607b591ed5a8e01679fa1f0394", - "runtimeCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "txHash": "0x0a89f7f97368815af26d0fc00cd0157a57717db97bbb734b66c894c5a65e8df6" - } - }, - "Staking": { - "address": "0x2d44C0e097F6cD0f514edAC633d82E01280B4A5c", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - }, - { - "name": "minimumIndexerStake", - "value": "100000000000000000000000" - }, - { - "name": "thawingPeriod", - "value": 6646 - }, - { - "name": "protocolPercentage", - "value": 10000 - }, - { - "name": "curationPercentage", - "value": 100000 - }, - { - "name": "channelDisputeEpochs", - "value": 2 - }, - { - "name": "maxAllocationEpochs", - "value": 6 - }, - { - "name": "delegationUnbondingPeriod", - "value": 6 - }, - { - "name": "delegationRatio", - "value": 16 - }, - { - "name": "rebateAlphaNumerator", - "value": 77 - }, - { - "name": "rebateAlphaDenominator", - "value": 100 - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0x9febc188c44c0d9b1162c100833e59e72c1795d999c2ff906f723bfca118c12d", - "proxy": true, - "implementation": { - "address": "0xcf464bf68d3183ae631c1f61c3f66792f9c05da9", - "creationCodeHash": "0x4844ebc5802b1a2171f41f8dac6f83c50421bbf5a334e5a251d9ec257d45554d", - "runtimeCodeHash": "0x4844ebc5802b1a2171f41f8dac6f83c50421bbf5a334e5a251d9ec257d45554d", - "txHash": "0xef6d48956c31adced59d6ee6954c921b645021100dec573e30255fa6b2e35d2b", - "libraries": { - "LibCobbDouglas": "0x504dc6069d7307c3fba5caa7674a927a0a511563" - } - } - }, - "DisputeManager": { - "address": "0x4e4B04008C0f7875CDe80e7546d8b3b03Cf1eCf1", - "initArgs": [ - { - "name": "controller", - "value": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5" - }, - { - "name": "arbitrator", - "value": "0xE1FDD398329C6b74C14cf19100316f0826a492d3" - }, - { - "name": "minimumDeposit", - "value": "10000000000000000000000" - }, - { - "name": "fishermanRewardPercentage", - "value": 500000 - }, - { - "name": "slashingPercentage", - "value": 25000 - } - ], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0xb629195f313e8214e48f5a3fc741072d710f9f9ed6079331908341761f582760", - "proxy": true, - "implementation": { - "address": "0x9CD121b6018A78c08Fc72b9948A6a43E6CEfBC07", - "creationCodeHash": "0x7508a0ca0cb8f88ef54aa2cd1a464dc93a51b5cf500f763f7192a704985d91f3", - "runtimeCodeHash": "0xc8f0b38c011925dbf847c05e73e2b23e8e14553121d58600173ba61731b0a807", - "txHash": "0x7679f2bfab93883af93bdf1166a42d1b3a87c1cbb3fb3cff5c7f860644409fd5" - } - }, - "BancorFormula": { - "address": "0xB4B6857dFcE1a31851c0fAde87E94Fc05053916D", - "creationCodeHash": "0x17f6de9ab8a9bcf03a548c01d620a32caf1f29be8d90a9688ebee54295f857ef", - "runtimeCodeHash": "0x97a57f69b0029383398d02587a3a357168950d61622fe9f9710bf02b59740d63", - "txHash": "0x6cf07aed737aee71fa67c40864495124d7c6c6f203c3ed482cbdcc12f998dbb1" - }, - "Controller": { - "address": "0x23B1aD50996ff1b21552cDD6430868D60bF342c5", - "creationCodeHash": "0x7f37a1844c38fffd5390d2114804ffc4e5cf66dfb5c7bd67a32a4f5d10eebd2d", - "runtimeCodeHash": "0x929c62381fbed59483f832611752177cc2642e1e35fedeeb6cd9703e278448a0", - "txHash": "0x8cbcfe1f800f88c501f8bdeab8aaf36134e88757c28cbbe74d58c13be36832da" - }, - "GraphProxyAdmin": { - "address": "0xC96d71957bbB06f75e1b0ae9d22AC751BE14975C", - "creationCodeHash": "0x26a6f47e71ad242e264768571ce7223bf5a86fd0113ab6cb8200f65820232904", - "runtimeCodeHash": "0xd5330527cfb09df657adc879d8ad704ce6b8d5917265cabbd3eb073d1399f122", - "txHash": "0xca463b34d7967c4351d24b2af779817bd1da75e53a48957cfa32abd1ebf3a56c" - }, - "GraphGovernance": { - "address": "0x47241861A3918eaa9097c0345bb5A91660D7AEE1", - "initArgs": ["0x1679a1d1caf1252ba43fb8fc17ebf914a0c725ae"], - "creationCodeHash": "0xa02709eb59b9cca8bee1271845b42db037dc1d042dad93410ba532d378a7c79f", - "runtimeCodeHash": "0xdb307489fd9a4a438b5b48909e12020b209280ad777561c0a7451655db097e75", - "txHash": "0x5101e33eb13504780b225a2557a7062bef93cada0838937e02e879fb3d5c2c01", - "proxy": true, - "implementation": { - "address": "0xa96F8468362e6A109ABFaAF6BBfDa303347B450e", - "creationCodeHash": "0x5bd7ee7fbf6eb49914ffc91c747d18c0909ca18c495a8b163499ebfdd82b29d2", - "runtimeCodeHash": "0xd77099bdfc3f66aec158303be46e92f8e434271d6b0c7643753cd8ac96b460b9", - "txHash": "0xb12705249777b5d955dd25ea7aebf46c5d1e3062b10bc9a0a5755b40f55e11e9" - } - }, - "AllocationExchange": { - "address": "0x58ce0a0f41449e349c1a91dd9f3d9286bf32c161", - "initArgs": [ - { - "name": "graphToken", - "value": "0x54fe55d5d255b8460fb3bc52d5d676f9ae5697cd" - }, - { - "name": "staking", - "value": "0x2d44c0e097f6cd0f514edac633d82e01280b4a5c" - }, - { - "name": "governor", - "value": "0x87d11bd744b882b7bc5a6b5450cba8c35d90eb10" - }, - { - "name": "authority", - "value": "0x2c0c3c48190d7c69f469eb586aa75e4705cfa203" - } - ], - "creationCodeHash": "0x2f052eda2faa00c8dd54ec9bffab95be79075e528f24c3fa9f722c77dcc26d20", - "runtimeCodeHash": "0xec837eb756268aa8a18c5d3182a5c2bf89bd0369f1de07ffa33b1ec5d3bef41a", - "txHash": "0x359cf3945b2584f45633c6f6f37ce5a46129b462232107bb29c456d7fdcb66d0" - }, - "SubgraphNFTDescriptor": { - "address": "0x08400283bCCa108d95e25dC2A7498c236134C8a4", - "creationCodeHash": "0x7ac0757e66857e512df199569ee11c47a61b00a8d812469b79afa5dafa98c0ed", - "runtimeCodeHash": "0x9a34ad6b202bdfa95ea85654ea2e0dd40a4b8b10847f1c3d3d805fa95a078a3d", - "txHash": "0x1bf820c2defccdb8a125b8b417463a2577b255b208e7d815045dcee188786f5a" - }, - "SubgraphNFT": { - "address": "0xcF1Cd42a28caEE626b6A6Cc62b6d0e9a290bAe20", - "creationCodeHash": "0x8c9929ec6293458209f9cbadd96821604765e3656fe3c7b289b99194ede15336", - "runtimeCodeHash": "0x6309a51754b6bec245685c7a81059dc28e3756f1045f18d059abc9294f454a6a", - "txHash": "0xe16a77593a05fdf02bc61bdf61393327e38bc055dcf57832bed308dff8249ef0" - } - }, "5": { "GraphProxyAdmin": { "address": "0x6D47902c3358E0BCC06171DE935cB23D8E276fdd", @@ -814,6 +496,34 @@ }, "IEthereumDIDRegistry": { "address": "0xdCa7EF03e98e0DC2B855bE647C39ABe984fcF21B" + }, + "BridgeEscrow": { + "address": "0x8e4145358af77516B886D865e2EcacC0Fd832B75", + "initArgs": ["0x48eD7AfbaB432d1Fc6Ea84EEC70E745d9DAcaF3B"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x190ea3c8f731a77a8fd1cbce860f9561f233adeafe559b33201b7d21ccd298cf", + "proxy": true, + "implementation": { + "address": "0xDD569E05D54fBF5d02fE4a26aC03Ea00317A0A2e", + "creationCodeHash": "0x6a1fc897c0130a1c99221cde1938d247de13a0861111ac47ad81c691f323df1a", + "runtimeCodeHash": "0xc8e31a4ebea0c3e43ceece974071ba0b6db2bed6725190795e07a2d369d2a8ab", + "txHash": "0x369038dcc8d8e70d40782dd761a82cc453c7a4f1939284c724a5a72119e3e566" + } + }, + "L1GraphTokenGateway": { + "address": "0xc82fF7b51c3e593D709BA3dE1b3a0d233D1DEca1", + "initArgs": ["0x48eD7AfbaB432d1Fc6Ea84EEC70E745d9DAcaF3B"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x4a06731591df5c5f77c11bf8df7851234873eb6727fbbc93f5595a223f7cf3fc", + "proxy": true, + "implementation": { + "address": "0x06A7A68d0D0D496693508ad3f50A8EA962333B7D", + "creationCodeHash": "0x9dac8130793923c7f35f3943b755b7a88e2de9a25d0ae5c0b8cb020b6479151a", + "runtimeCodeHash": "0xcd798129b77d26c0b29369138d2d8dd413fcf6cb9b3838c5f95f50d9839a388a", + "txHash": "0xa4d75169094cd8601ec507234695d83042e888ec2ab49b0ce150d7aae908d895" + } } }, "1337": { @@ -1010,5 +720,215 @@ "runtimeCodeHash": "0x07012b5692ec6cbeb7a6986e061fc5026a2f76545b07bfd9182985de002fa281", "txHash": "0xe3d870434e38ee37142a86e0fc54063df59c02c3b70135f070c3a1025c5e8246" } + }, + "421613": { + "GraphProxyAdmin": { + "address": "0x4037466bb242f51575d32E8B1be693b3E5Cd1386", + "creationCodeHash": "0x68b304ac6bce7380d5e0f6b14a122f628bffebcc75f8205cb60f0baf578b79c3", + "runtimeCodeHash": "0x8d9ba87a745cf82ab407ebabe6c1490197084d320efb6c246d94bcc80e804417", + "txHash": "0x9c4d5f8c0ab5a5bc36b0a063ab1ff04372ce7d917c0b200b94544b5da4f0230d" + }, + "BancorFormula": { + "address": "0x71319060b9fdeD6174b6368bE04F9A1b7c9aCe48", + "creationCodeHash": "0x7ae36017eddb326ddd79c7363781366121361d42fdb201cf57c57424eede46f4", + "runtimeCodeHash": "0xed6701e196ad93718e28c2a2a44d110d9e9906085bcfe4faf4f6604b08f0116c", + "txHash": "0x7fe8cabb7a4fe56311591aa8d68d6c82cb0d5c232fc5aaf28bed4d1ece0e42e5" + }, + "Controller": { + "address": "0x7f734E995010Aa8d28b912703093d532C37b6EAb", + "creationCodeHash": "0x798f913fbaa1b2547c917e3dc31679089ab27cba442c511c159803acdba28c15", + "runtimeCodeHash": "0x00ae0824f79c4e48d2d23a8d4e6d075f04f44f3ea30a4f4305c345bb98117c62", + "txHash": "0x6213da3e6367ef47cd6e1fe23e4d83296f16153a64236a5c91f865f2ec84c089" + }, + "EpochManager": { + "address": "0x8ECedc7631f4616D7f4074f9fC9D0368674794BE", + "initArgs": ["0x7f734E995010Aa8d28b912703093d532C37b6EAb", "554"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x62b0d6b8556be9443397ad1f6030fdc47b1a4a3ebcc63f34cdf4091420aec84b", + "proxy": true, + "implementation": { + "address": "0xAaB195Ed1B445A2A0E357494d9036bC746227AE2", + "creationCodeHash": "0x83bc0b08dbe1a9259666ec209f06223863f7bb9cfbf917a2d4b795c771a727fe", + "runtimeCodeHash": "0xed60261c6dc84ebc16830c36f3ee370a92802601d5a2fe1c3c19f5120dcbc2eb", + "txHash": "0xd4f8780490f63432580e3dd5b2b4d9b39e904e8b4ac5cfd23540658cbafe449d" + } + }, + "L2GraphToken": { + "address": "0x18C924BD5E8b83b47EFaDD632b7178E2Fd36073D", + "initArgs": ["0xEfc519BEd6a43a14f1BBBbA9e796C4931f7A5540"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x7ec14b524141af953959b537c1acbea9b49b12ee906563a6172123b09ab3d1f6", + "proxy": true, + "implementation": { + "address": "0x5dcAcF820D7b9F0640e8a23a5a857675A774C34a", + "creationCodeHash": "0x6c4146427aafa7375a569154be95c8c931bf83aab0315706dd78bdf79c889e4c", + "runtimeCodeHash": "0x004371d1d80011906953dcba17c648503fc94b94e1e0365c8d8c706ff91f93e9", + "txHash": "0xb748498a2ebc90e20dc8da981be832f4e00f08ea9ff289880738705e45d6aeca" + } + }, + "GraphCurationToken": { + "address": "0x2B757ad83e4ed51ecaE8D4dC9AdE8E3Fa29F7BdC", + "creationCodeHash": "0x1ee42ee271cefe20c33c0de904501e618ac4b56debca67c634d0564cecea9ff2", + "runtimeCodeHash": "0x340e8f378c0117b300f3ec255bc5c3a273f9ab5bd2940fa8eb3b5065b21f86dc", + "txHash": "0x1aa753cd01fa4505c71f6866dae35faee723d181141ed91b6e5cf3082ee90f9b" + }, + "ServiceRegistry": { + "address": "0x07ECDD4278D83Cd2425cA86256634f666b659e53", + "initArgs": ["0x7f734E995010Aa8d28b912703093d532C37b6EAb"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x8a13420fdc91139297ab1497fbf5b443c156bbc7b9d2a1ac97fb9f23abde2723", + "proxy": true, + "implementation": { + "address": "0xd18D4B4e84eA4713E04060c93bD079A974BE6C4a", + "creationCodeHash": "0x50808e8cce93cf78a23c9e6dd7984356bd2bd93be30b358982909140dd61f6ff", + "runtimeCodeHash": "0xaef79c87f7e80107c0dc568cf1f8950459b5174ee3aa565ec487556a655e71db", + "txHash": "0x2d6043d89a5f5c4f3d0df0f50264ab7efebc898be0b5d358a00715ba9f657a89" + } + }, + "Curation": { + "address": "0x7080AAcC4ADF4b1E72615D6eb24CDdE40a04f6Ca", + "initArgs": [ + "0x7f734E995010Aa8d28b912703093d532C37b6EAb", + "0x71319060b9fdeD6174b6368bE04F9A1b7c9aCe48", + "0x2B757ad83e4ed51ecaE8D4dC9AdE8E3Fa29F7BdC", + "1000000", + "10000", + "1000000000000000000" + ], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x2e5744fa4eca56cf6902e27fcc0509487f39bdb0d29b9eb0181db986235289a0", + "proxy": true, + "implementation": { + "address": "0xDA6c9d39b49c3d41CaC2030c6B75b40Efea09817", + "creationCodeHash": "0xa5fa77df71a72c5aadba812345978c291c5fa1a3a23129b6eba3a38ac85d8b5d", + "runtimeCodeHash": "0x1d265e9f658778b48a0247cfef79bfc9304d1faa1f1e085f2fea85629f68e2d5", + "txHash": "0x815eda87a2599d6f2c7458c7b164e7307d05018f0dd72073a50971d424313377" + } + }, + "SubgraphNFTDescriptor": { + "address": "0x30545f313bD2eb0F85E4f808Ae4D2C016efE78b2", + "creationCodeHash": "0xf16e8ff11d852eea165195ac9e0dfa00f98e48f6ce3c77c469c7df9bf195b651", + "runtimeCodeHash": "0x39583196f2bcb85789b6e64692d8c0aa56f001c46f0ca3d371abbba2c695860f", + "txHash": "0x060839a09e89cbd47adbb8c04cc76b21a00785600a4e8b44939dd928391777e1" + }, + "SubgraphNFT": { + "address": "0x5571D8FE183AD1367dF21eE9968690f0Eabdc593", + "constructorArgs": ["0xEfc519BEd6a43a14f1BBBbA9e796C4931f7A5540"], + "creationCodeHash": "0xc1e58864302084de282dffe54c160e20dd96c6cfff45e00e6ebfc15e04136982", + "runtimeCodeHash": "0x7216e736a8a8754e88688fbf5c0c7e9caf35c55ecc3a0c5a597b951c56cf7458", + "txHash": "0xc11917ffedda6867648fa2cb62cca1df3c0ed485a0a0885284e93a2c5d33455c" + }, + "GNS": { + "address": "0x6bf9104e054537301cC23A1023Ca30A6Df79eB21", + "initArgs": [ + "0x7f734E995010Aa8d28b912703093d532C37b6EAb", + "0x71319060b9fdeD6174b6368bE04F9A1b7c9aCe48", + "0x5571D8FE183AD1367dF21eE9968690f0Eabdc593" + ], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x3c2509730e06249d970818319bb507185d4fdea13d5600cef87928a718950c19", + "proxy": true, + "implementation": { + "address": "0x7eCb82A9Cf9B370d3fC2Ef66E38F38EDFAeaa125", + "creationCodeHash": "0xb0be24e926bb24420bb5a8d3f7bd0b70a545fdddbf8cb177a42478adf4435aae", + "runtimeCodeHash": "0x4cb62b9def5b691e43ed06808b18efe682fcefb7739909be0d6c87f1eda724cd", + "txHash": "0xf1d41fc99ed716a0c890ea62e13ee108ddcb4ecfc74efb715a4ef05605ce449b" + } + }, + "Staking": { + "address": "0xcd549d0C43d915aEB21d3a331dEaB9B7aF186D26", + "initArgs": [ + "0x7f734E995010Aa8d28b912703093d532C37b6EAb", + "100000000000000000000000", + "6646", + "10000", + "100000", + "2", + "4", + "12", + "16", + "77", + "100" + ], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0xc98ebdd0a80b97ef8f6305903ef6496a7781db76a5b1b3c3c3b2b10dbd9a7af5", + "proxy": true, + "implementation": { + "address": "0x8E56ee65Ed613f2AecA8898D19497D288601bdeb", + "creationCodeHash": "0x75b63ef816627315c635cae7f95917764e2cb797496280cdeaa9b3230bf7f7bc", + "runtimeCodeHash": "0x461ccf91c7c6188c94c6df430b6954dfd9c5cc2a79a5e4db21422e11b663d319", + "txHash": "0xb9ce53dafab3dcaad25b24d9f998888225103265bd2d84cb1545b4e06e96e3b6", + "libraries": { + "LibCobbDouglas": "0x86f0f6cd9a38A851E3AB8f110be06B77C199eC1F" + } + } + }, + "RewardsManager": { + "address": "0x5F06ABd1CfAcF7AE99530D7Fed60E085f0B15e8D", + "initArgs": ["0x7f734E995010Aa8d28b912703093d532C37b6EAb"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0xd4cfa95475e9e867fb24babd6a00a5b6b01d2267533e2412986aa1ff94d51c02", + "proxy": true, + "implementation": { + "address": "0x80b54Ba64d8a207785969d9ae0dA984EfE8D10dF", + "creationCodeHash": "0x98aaabec491a17401ca37209db0613c91285de061e859574526f841a4dd60c4a", + "runtimeCodeHash": "0x2795a83531898957014373bd4595f1f9a381ecfaf787bdfc64380563af06f06a", + "txHash": "0xb4bc7ae32ec98394c448f8773bdd3049ab83e236acb6823a7a322d88ecfbfd99" + } + }, + "DisputeManager": { + "address": "0x16DEF7E0108A5467A106dbD7537f8591f470342E", + "initArgs": [ + "0x7f734E995010Aa8d28b912703093d532C37b6EAb", + "0xF89688d5d44d73cc4dE880857A3940487076e5A4", + "10000000000000000000000", + "500000", + "25000", + "25000" + ], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x70188c9243c2226ac793ac8c0a9eecd76c9b44e53f7f6f97fa177a34808421a0", + "proxy": true, + "implementation": { + "address": "0x39aEdA1d6ea3B62b76C7c439beBfFCb5369a175C", + "creationCodeHash": "0x2e77ad7a1627b6e04bece0fe18b3ab543ef4a2d6914f2e5e640b2c8175aca3a8", + "runtimeCodeHash": "0x0186afe711eff4ceea28620d091e3c6034fd15be05894119c74a38b020e3a554", + "txHash": "0x4efbd28e55866c0292309964f47bd805922ad417e5980e14e055ad693024582d" + } + }, + "AllocationExchange": { + "address": "0x61809D6Cde07f27D2fcDCb67a42d0Af1988Be5e8", + "constructorArgs": [ + "0x18C924BD5E8b83b47EFaDD632b7178E2Fd36073D", + "0xcd549d0C43d915aEB21d3a331dEaB9B7aF186D26", + "0x05F359b1319f1Ca9b799CB6386F31421c2c49dBA", + "0xD06f366678AE139a94b2AaC2913608De568F1D03" + ], + "creationCodeHash": "0x96c5b59557c161d80f1617775a7b9537a89b0ecf2258598b3a37724be91ae80a", + "runtimeCodeHash": "0xed3d9cce65ddfa8a237d4d7d294ffdb13a082e0adcda3bbd313029cfae1365f3", + "txHash": "0x1df63329a21dca69d20e03c076dd89c350970d35319eeefab028cebbc78d29dc" + }, + "L2GraphTokenGateway": { + "address": "0xef2757855d2802bA53733901F90C91645973f743", + "initArgs": ["0x7f734E995010Aa8d28b912703093d532C37b6EAb"], + "creationCodeHash": "0xcdd28bb3db05f1267ca0f5ea29536c61841be5937ce711b813924f8ff38918cc", + "runtimeCodeHash": "0x4ca8c37c807bdfda1d6dcf441324b7ea14c6ddec5db37c20c2bf05aeae49bc0d", + "txHash": "0x47bde4e3ad0bc077897a3de65058c4b7dd710aa447ec25942f716321cbdc590d", + "proxy": true, + "implementation": { + "address": "0xc68cd0d2ca533232Fd86D6e48b907338B2E0a74A", + "creationCodeHash": "0xbd52455bd8b14bfc27af623388fe2f9e06ddd4c4be3fc06c51558a912de91770", + "runtimeCodeHash": "0x29e47f693053f978d6b2ac0a327319591bf5b5e8a6e6c0744b8afcc0250bf667", + "txHash": "0xf68a5e1e516ee9a646f19bbe4d58336fdfcf5fc859f84cdac5e68b00bcd3a09a" + } + } } } diff --git a/audits/OpenZeppelin/2022-07-graph-arbitrum-bridge-audit.pdf b/audits/OpenZeppelin/2022-07-graph-arbitrum-bridge-audit.pdf new file mode 100644 index 000000000..850c9a80b Binary files /dev/null and b/audits/OpenZeppelin/2022-07-graph-arbitrum-bridge-audit.pdf differ diff --git a/audits/OpenZeppelin/2022-07-pr552-summary.pdf b/audits/OpenZeppelin/2022-07-pr552-summary.pdf new file mode 100644 index 000000000..d77364c2a Binary files /dev/null and b/audits/OpenZeppelin/2022-07-pr552-summary.pdf differ diff --git a/audits/OpenZeppelin/2022-07-pr568-summary.pdf b/audits/OpenZeppelin/2022-07-pr568-summary.pdf new file mode 100644 index 000000000..fb01c454a Binary files /dev/null and b/audits/OpenZeppelin/2022-07-pr568-summary.pdf differ diff --git a/audits/OpenZeppelin/2022-07-pr569-summary.pdf b/audits/OpenZeppelin/2022-07-pr569-summary.pdf new file mode 100644 index 000000000..e40726fc9 Binary files /dev/null and b/audits/OpenZeppelin/2022-07-pr569-summary.pdf differ diff --git a/audits/OpenZeppelin/2022-07-pr571-summary.pdf b/audits/OpenZeppelin/2022-07-pr571-summary.pdf new file mode 100644 index 000000000..80fddb783 Binary files /dev/null and b/audits/OpenZeppelin/2022-07-pr571-summary.pdf differ diff --git a/audits/OpenZeppelin/2022-09-graph-drip-keeper-reward-audit.pdf b/audits/OpenZeppelin/2022-09-graph-drip-keeper-reward-audit.pdf new file mode 100644 index 000000000..de1b2ffbb Binary files /dev/null and b/audits/OpenZeppelin/2022-09-graph-drip-keeper-reward-audit.pdf differ diff --git a/cli/commands/bridge/to-l1.ts b/cli/commands/bridge/to-l1.ts index 42f319eb4..0913aa2e1 100644 --- a/cli/commands/bridge/to-l1.ts +++ b/cli/commands/bridge/to-l1.ts @@ -102,11 +102,16 @@ export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Prom const l2Receipt = new L2TransactionReceipt(receipt) const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0] - logger.info(`The transaction generated an L2 to L1 message in outbox with eth block number:`) - logger.info(l2ToL1Message.event.ethBlockNum.toString()) - logger.info( - `After the dispute period is finalized (in ~1 week), you can finalize this by calling`, - ) + const ethBlockNum = l2ToL1Message.getFirstExecutableBlock(l2Provider) + if (ethBlockNum === null) { + logger.info(`L2 to L1 message can or already has been executed. If not finalized call`) + } else { + logger.info(`The transaction generated an L2 to L1 message in outbox with eth block number:`) + logger.info(ethBlockNum.toString()) + logger.info( + `After the dispute period is finalized (in ~1 week), you can finalize this by calling`, + ) + } logger.info(`finish-send-to-l1 with the following txhash:`) logger.info(l2Receipt.transactionHash) } diff --git a/cli/commands/bridge/to-l2.ts b/cli/commands/bridge/to-l2.ts index ffacee1c8..01b3f65bc 100644 --- a/cli/commands/bridge/to-l2.ts +++ b/cli/commands/bridge/to-l2.ts @@ -27,7 +27,7 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => { logger.warn('Funds were deposited on L2 but the retryable ticket was not redeemed') logAutoRedeemReason(autoRedeemRec) logger.info('Attempting to redeem...') - await l1ToL2Message.redeem() + await l1ToL2Message.redeem(process.env.CI ? { gasLimit: 2_000_000 } : {}) const redeemAttempt = await l1ToL2Message.getSuccessfulRedeem() if (redeemAttempt.status == L1ToL2MessageStatus.REDEEMED) { l2TxHash = redeemAttempt.l2TxReceipt ? redeemAttempt.l2TxReceipt.transactionHash : 'null' @@ -45,7 +45,8 @@ export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { const graphAccount = cliArgs.graphAccount @@ -102,6 +102,44 @@ export const withdraw = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise => { + // parse args + const ipfs = cliArgs.ipfs + const subgraphDeploymentID = cliArgs.subgraphDeploymentID + const versionPath = cliArgs.versionPath + const subgraphPath = cliArgs.subgraphPath + const tokens = parseGRT(cliArgs.tokens) + + // pin to IPFS + const subgraphDeploymentIDBytes = IPFS.ipfsHashToBytes32(subgraphDeploymentID) + const versionHashBytes = await pinMetadataToIPFS(ipfs, 'version', versionPath) + const subgraphHashBytes = await pinMetadataToIPFS(ipfs, 'subgraph', subgraphPath) + + // craft transaction + const GNS = cli.contracts.GNS + + // build publish tx + const publishTx = await GNS.populateTransaction.publishNewSubgraph( + subgraphDeploymentIDBytes, + versionHashBytes, + subgraphHashBytes, + ) + + // build mint tx + const subgraphID = buildSubgraphID( + cli.walletAddress, + await GNS.nextAccountSeqID(cli.walletAddress), + ) + const mintTx = await GNS.populateTransaction.mintSignal(subgraphID, tokens, 0) + + // ensure approval + await ensureGRTAllowance(cli.wallet, GNS.address, tokens, cli.contracts.GraphToken) + + // send multicall transaction + logger.info(`Publishing and minting on new subgraph for ${cli.walletAddress}...`) + await sendTransaction(cli.wallet, GNS, 'multicall', [[publishTx.data, mintTx.data]]) +} + export const gnsCommand = { command: 'gns', describe: 'GNS contract calls', @@ -172,7 +210,7 @@ export const gnsCommand = { }) .command({ command: 'publishNewVersion', - describe: 'Withdraw unlocked GRT', + describe: 'Publish a new subgraph version', builder: (yargs: Argv) => { return yargs .option('subgraphID', { @@ -307,6 +345,53 @@ export const gnsCommand = { return withdraw(await loadEnv(argv), argv) }, }) + .command({ + command: 'publishAndSignal', + describe: 'Publish a new subgraph and add initial signal', + builder: (yargs: Argv) => { + return yargs + .option('ipfs', { + description: 'ipfs endpoint. ex. https://api.thegraph.com/ipfs/', + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('subgraphDeploymentID', { + description: 'subgraph deployment ID in base58', + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('versionPath', { + description: ` filepath to metadata. With JSON format:\n + "description": "", + "label": ""`, + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('subgraphPath', { + description: ` filepath to metadata. With JSON format:\n + "description": "", + "displayName": "", + "image": "", + "codeRepository": "", + "website": "",`, + type: 'string', + requiresArg: true, + demandOption: true, + }) + .option('tokens', { + description: 'Amount of tokens to deposit', + type: 'string', + requiresArg: true, + demandOption: true, + }) + }, + handler: async (argv: CLIArgs): Promise => { + return publishAndSignal(await loadEnv(argv), argv) + }, + }) }, handler: (): void => { yargs.showHelp() diff --git a/cli/contracts.ts b/cli/contracts.ts index b01727c20..c36e83567 100644 --- a/cli/contracts.ts +++ b/cli/contracts.ts @@ -1,4 +1,15 @@ -import { BaseContract, providers, Signer } from 'ethers' +import { + BaseContract, + Contract, + ContractFunction, + ContractReceipt, + ContractTransaction, + providers, + Signer, +} from 'ethers' +import { Provider } from '@ethersproject/providers' +import lodash from 'lodash' +import fs from 'fs' import { AddressBook } from './address-book' import { chainIdIsL2 } from './cross-chain' @@ -68,13 +79,26 @@ export const loadContracts = ( addressBook: AddressBook, chainId: number | string, signerOrProvider?: Signer | providers.Provider, + enableTXLogging = false, ): NetworkContracts => { const contracts = {} for (const contractName of addressBook.listEntries()) { + const contractEntry = addressBook.getEntry(contractName) + try { - contracts[contractName] = loadAddressBookContract(contractName, addressBook, signerOrProvider) + let contract = getContractAt(contractName, contractEntry.address) + if (enableTXLogging) { + contract.connect = getWrappedConnect(contract, contractName) + contract = wrapCalls(contract, contractName) + } + contracts[contractName] = contract + + if (signerOrProvider) { + contracts[contractName] = contracts[contractName].connect(signerOrProvider) + } + // On L2 networks, we alias L2GraphToken as GraphToken - if (signerOrProvider && chainIdIsL2(chainId) && contractName == 'L2GraphToken') { + if (chainIdIsL2(chainId) && contractName == 'L2GraphToken') { contracts['GraphToken'] = contracts[contractName] } } catch (err) { @@ -83,3 +107,77 @@ export const loadContracts = ( } return contracts as NetworkContracts } + +// Returns a contract connect function that wrapps contract calls with wrapCalls +function getWrappedConnect( + contract: Contract, + contractName: string, +): (signerOrProvider: string | Provider | Signer) => Contract { + const call = contract.connect.bind(contract) + const override = (signerOrProvider: string | Provider | Signer): Contract => { + const connectedContract = call(signerOrProvider) + connectedContract.connect = getWrappedConnect(connectedContract, contractName) + return wrapCalls(connectedContract, contractName) + } + return override +} + +// Returns a contract with wrapped calls +// The wrapper will run the tx, wait for confirmation and log the details +function wrapCalls(contract: Contract, contractName: string): Contract { + const wrappedContract = lodash.cloneDeep(contract) + + for (const fn of Object.keys(contract.functions)) { + const call: ContractFunction = contract.functions[fn] + const override = async (...args: Array): Promise => { + // Make the call + const tx = await call(...args) + logContractCall(tx, contractName, fn, args) + + // Wait for confirmation + const receipt = await contract.provider.waitForTransaction(tx.hash) + logContractReceipt(tx, receipt) + return tx + } + + wrappedContract.functions[fn] = override + wrappedContract[fn] = override + } + + return wrappedContract +} + +function logContractCall( + tx: ContractTransaction, + contractName: string, + fn: string, + args: Array, +) { + const msg = [] + msg.push(`> Sent transaction ${contractName}.${fn}`) + msg.push(` sender: ${tx.from}`) + msg.push(` contract: ${tx.to}`) + msg.push(` params: [ ${args} ]`) + msg.push(` txHash: ${tx.hash}`) + + logToConsoleAndFile(msg) +} + +function logContractReceipt(tx: ContractTransaction, receipt: ContractReceipt) { + const msg = [] + msg.push( + receipt.status ? `✔ Transaction succeeded: ${tx.hash}` : `✖ Transaction failed: ${tx.hash}`, + ) + + logToConsoleAndFile(msg) +} + +function logToConsoleAndFile(msg: string[]) { + const isoDate = new Date().toISOString() + const fileName = `tx-${isoDate.substring(0, 10)}.log` + + msg.map((line) => { + console.log(line) + fs.appendFileSync(fileName, `[${isoDate}] ${line}\n`) + }) +} diff --git a/cli/cross-chain.ts b/cli/cross-chain.ts index ea3b081ac..a0b674624 100644 --- a/cli/cross-chain.ts +++ b/cli/cross-chain.ts @@ -1,4 +1,6 @@ import { L1ToL2MessageGasEstimator } from '@arbitrum/sdk' +import { L1ToL2MessageNoGasParams } from '@arbitrum/sdk/dist/lib/message/L1ToL2MessageCreator' +import { GasOverrides } from '@arbitrum/sdk/dist/lib/message/L1ToL2MessageGasEstimator' import { BigNumber, providers } from 'ethers' import { parseEther } from 'ethers/lib/utils' @@ -44,20 +46,31 @@ export const estimateRetryableTxGas = async ( logger.info('Estimating retryable ticket gas:') const baseFee = (await l1Provider.getBlock('latest')).baseFeePerGas const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider) + const retryableEstimateData: L1ToL2MessageNoGasParams = { + from: gatewayAddress, + to: l2Dest, + data: depositCalldata, + l2CallValue: parseEther('0'), + excessFeeRefundAddress: gatewayAddress, + callValueRefundAddress: gatewayAddress, + } + + const estimateOpts: GasOverrides = {} + if (opts.maxGas) estimateOpts.gasLimit = { base: opts.maxGas } + if (opts.maxSubmissionCost) estimateOpts.maxSubmissionFee = { base: opts.maxSubmissionCost } + if (opts.gasPriceBid) estimateOpts.maxFeePerGas = { base: opts.gasPriceBid } + const gasParams = await gasEstimator.estimateAll( - gatewayAddress, - l2Dest, - depositCalldata, - parseEther('0'), + retryableEstimateData, baseFee as BigNumber, - gatewayAddress, - gatewayAddress, l1Provider, + estimateOpts, ) + // override fixed values return { maxGas: opts.maxGas ?? gasParams.gasLimit, gasPriceBid: opts.gasPriceBid ?? gasParams.maxFeePerGas, - maxSubmissionCost: opts.maxSubmissionCost ?? gasParams.maxSubmissionFee, + maxSubmissionCost: opts.maxSubmissionCost ?? gasParams.maxSubmissionCost, } } diff --git a/cli/helpers.ts b/cli/helpers.ts index 37f241fc4..115336eca 100644 --- a/cli/helpers.ts +++ b/cli/helpers.ts @@ -1,7 +1,8 @@ import fs from 'fs' +import path from 'path' import * as dotenv from 'dotenv' -import { utils, providers, Wallet } from 'ethers' +import { utils, BigNumber, BigNumberish, Signer } from 'ethers' import ipfsHttpClient from 'ipfs-http-client' import inquirer from 'inquirer' @@ -15,6 +16,8 @@ import { jsonToSubgraphMetadata, jsonToVersionMetadata, } from './metadata' +import { solidityKeccak256 } from 'ethers/lib/utils' +import { GraphToken } from '../build/types/GraphToken' dotenv.config() @@ -46,12 +49,14 @@ export class IPFS { export const pinMetadataToIPFS = async ( ipfs: string, type: string, - path?: string, // Only pass path or metadata, not both + filepath?: string, // Only pass path or metadata, not both metadata?: SubgraphMetadata | VersionMetadata, ): Promise => { - if (metadata == undefined && path != undefined) { + if (metadata == undefined && filepath != undefined) { if (type == 'subgraph') { - metadata = jsonToSubgraphMetadata(JSON.parse(fs.readFileSync(__dirname + path).toString())) + metadata = jsonToSubgraphMetadata( + JSON.parse(fs.readFileSync(path.join(__dirname, filepath)).toString()), + ) logger.info('Meta data:') logger.info(` Subgraph Description: ${metadata.description}`) logger.info(`Subgraph Display Name: ${metadata.displayName}`) @@ -59,7 +64,9 @@ export const pinMetadataToIPFS = async ( logger.info(` Subgraph Code Repository: ${metadata.codeRepository}`) logger.info(` Subgraph Website: ${metadata.website}`) } else if (type == 'version') { - metadata = jsonToVersionMetadata(JSON.parse(fs.readFileSync(__dirname + path).toString())) + metadata = jsonToVersionMetadata( + JSON.parse(fs.readFileSync(path.join(__dirname, filepath)).toString()), + ) logger.info('Meta data:') logger.info(` Version Description: ${metadata.description}`) logger.info(` Version Label: ${metadata.label}`) @@ -104,3 +111,23 @@ export const confirm = async (message: string, skip: boolean): Promise } return true } + +export const buildSubgraphID = (account: string, seqID: BigNumber): string => + solidityKeccak256(['address', 'uint256'], [account, seqID]) + +export const ensureGRTAllowance = async ( + owner: Signer, + spender: string, + amount: BigNumberish, + grt: GraphToken, +): Promise => { + const ownerAddress = await owner.getAddress() + const allowance = await grt.allowance(ownerAddress, spender) + const allowTokens = BigNumber.from(amount).sub(allowance) + if (allowTokens.gt(0)) { + console.log( + `\nApproving ${spender} to spend ${allowTokens} tokens on ${ownerAddress} behalf...`, + ) + await grt.connect(owner).approve(spender, amount) + } +} diff --git a/cli/network.ts b/cli/network.ts index 6667a960d..9a0618ff3 100644 --- a/cli/network.ts +++ b/cli/network.ts @@ -93,7 +93,7 @@ export const waitTransaction = async ( ): Promise => { const receipt = await sender.provider.waitForTransaction(tx.hash) const networkName = (await sender.provider.getNetwork()).name - if (networkName === 'kovan' || networkName === 'rinkeby') { + if (networkName === 'goerli') { receipt.status // 1 = success, 0 = failure ? logger.info(`Transaction succeeded: 'https://${networkName}.etherscan.io/tx/${tx.hash}'`) : logger.warn(`Transaction failed: 'https://${networkName}.etherscan.io/tx/${tx.hash}'`) diff --git a/config/graph.arbitrum-goerli.yml b/config/graph.arbitrum-goerli.yml index fa1a66ad3..5b41e4382 100644 --- a/config/graph.arbitrum-goerli.yml +++ b/config/graph.arbitrum-goerli.yml @@ -1,10 +1,10 @@ general: - arbitrator: &arbitrator "0x113DC95e796836b8F0Fa71eE7fB42f221740c3B0" # Arbitration Council (TODO: update) - governor: &governor "0x3e43EF77fAAd296F65eF172E8eF06F8231c9DeAd" # Graph Council (TODO: update) - authority: &authority "0x79fd74da4c906509862c8fe93e87a9602e370bc4" # Authority that signs payment vouchers (TODO: update) - availabilityOracle: &availabilityOracle "0x5d3B6F98F1cCdF873Df0173CDE7335874a396c4d" # Subgraph Availability Oracle (TODO: update) - pauseGuardian: &pauseGuardian "0x8290362Aba20D17c51995085369E001Bad99B21c" # Protocol pause guardian (TODO: update) - allocationExchangeOwner: &allocationExchangeOwner "0x74Db79268e63302d3FC69FB5a7627F7454a41732" # Allocation Exchange owner (TODO: update) + arbitrator: &arbitrator "0xF89688d5d44d73cc4dE880857A3940487076e5A4" # Arbitration Council (TODO: update) + governor: &governor "0x5CeeeE16F30357d49c50bcd7F520ca6527cf388a" # Graph Council (TODO: update) + authority: &authority "0xD06f366678AE139a94b2AaC2913608De568F1D03" # Authority that signs payment vouchers (TODO: update) + availabilityOracle: &availabilityOracle "0xA99A56fA38a6B9553853c84E11458AeCcdad509B" # Subgraph Availability Oracle (TODO: update) + pauseGuardian: &pauseGuardian "0x4B6C90B9fE29dfa521188B6547989C23d613b79B" # Protocol pause guardian (TODO: update) + allocationExchangeOwner: &allocationExchangeOwner "0x05F359b1319f1Ca9b799CB6386F31421c2c49dBA" # Allocation Exchange owner (TODO: update) contracts: Controller: @@ -66,7 +66,7 @@ contracts: controller: "${{Controller.address}}" bondingCurve: "${{BancorFormula.address}}" curationTokenMaster: "${{GraphCurationToken.address}}" - reserveRatio: 500000 # in parts per million + reserveRatio: 1000000 # in parts per million curationTaxPercentage: 10000 # in parts per million minimumCurationDeposit: "1000000000000000000" # in wei calls: diff --git a/config/graph.arbitrum-localhost.yml b/config/graph.arbitrum-localhost.yml index 1061044be..a17755fbc 100644 --- a/config/graph.arbitrum-localhost.yml +++ b/config/graph.arbitrum-localhost.yml @@ -1,10 +1,10 @@ general: - arbitrator: &arbitrator "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0" # Arbitration Council - governor: &governor "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b" # Graph Council - authority: &authority "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d" # Authority that signs payment vouchers - availabilityOracle: &availabilityOracle "0xd03ea8624C8C5987235048901fB614fDcA89b117" # Subgraph Availability Oracle - pauseGuardian: &pauseGuardian "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC" # Protocol pause guardian - allocationExchangeOwner: &allocationExchangeOwner "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9" # Allocation Exchange owner + arbitrator: &arbitrator "0x4237154FE0510FdE3575656B60c68a01B9dCDdF8" # Arbitration Council + governor: &governor "0x1257227a2ECA34834940110f7B5e341A5143A2c4" # Graph Council + authority: &authority "0x12B8D08b116E1E3cc29eE9Cf42bB0AA8129C3215" # Authority that signs payment vouchers + availabilityOracle: &availabilityOracle "0x7694a48065f063a767a962610C6717c59F36b445" # Subgraph Availability Oracle + pauseGuardian: &pauseGuardian "0x601060e0DC5349AA55EC73df5A58cB0FC1cD2e3C" # Protocol pause guardian + allocationExchangeOwner: &allocationExchangeOwner "0xbD38F7b67a591A5cc7D642e1026E5095B819d952" # Allocation Exchange owner contracts: Controller: @@ -66,7 +66,7 @@ contracts: controller: "${{Controller.address}}" bondingCurve: "${{BancorFormula.address}}" curationTokenMaster: "${{GraphCurationToken.address}}" - reserveRatio: 500000 # in parts per million + reserveRatio: 1000000 # in parts per million curationTaxPercentage: 10000 # in parts per million minimumCurationDeposit: "1000000000000000000" # in wei calls: diff --git a/config/graph.arbitrum-one.yml b/config/graph.arbitrum-one.yml index e68d67521..49c7b0b13 100644 --- a/config/graph.arbitrum-one.yml +++ b/config/graph.arbitrum-one.yml @@ -1,10 +1,10 @@ general: arbitrator: &arbitrator "0x113DC95e796836b8F0Fa71eE7fB42f221740c3B0" # Arbitration Council - governor: &governor "0x3e43EF77fAAd296F65eF172E8eF06F8231c9DeAd" # Graph Council - authority: &authority "0x79fd74da4c906509862c8fe93e87a9602e370bc4" # Authority that signs payment vouchers - availabilityOracle: &availabilityOracle "0x5d3B6F98F1cCdF873Df0173CDE7335874a396c4d" # Subgraph Availability Oracle (TODO: update) - pauseGuardian: &pauseGuardian "0x8290362Aba20D17c51995085369E001Bad99B21c" # Protocol pause guardian (TODO: update) - allocationExchangeOwner: &allocationExchangeOwner "0x74Db79268e63302d3FC69FB5a7627F7454a41732" # Allocation Exchange owner (TODO: update) + governor: &governor "0x8C6de8F8D562f3382417340A6994601eE08D3809" # Graph Council + authority: &authority "0x79f2212de27912bCb25a452fC102C85c142E3eE3" # Authority that signs payment vouchers + availabilityOracle: &availabilityOracle "0xbCAEE36Ce38Ec534c7078db1f90118E72173645B" # Subgraph Availability Oracle + pauseGuardian: &pauseGuardian "0xB0aD33a21b98bCA1761729A105e2E34e27153aAE" # Protocol pause guardian + allocationExchangeOwner: &allocationExchangeOwner "0x270Ea4ea9e8A699f8fE54515E3Bb2c418952623b" # Allocation Exchange owner contracts: Controller: @@ -66,7 +66,7 @@ contracts: controller: "${{Controller.address}}" bondingCurve: "${{BancorFormula.address}}" curationTokenMaster: "${{GraphCurationToken.address}}" - reserveRatio: 500000 # in parts per million + reserveRatio: 1000000 # in parts per million curationTaxPercentage: 10000 # in parts per million minimumCurationDeposit: "1000000000000000000" # in wei calls: diff --git a/e2e/deployment/config/controller.test.ts b/e2e/deployment/config/controller.test.ts index 8fbdf4834..647cb19f5 100644 --- a/e2e/deployment/config/controller.test.ts +++ b/e2e/deployment/config/controller.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import hre, { ethers } from 'hardhat' import { NamedAccounts } from '../../../gre/type-extensions' -import GraphChain from '../../../gre/helpers/network' +import GraphChain from '../../../gre/helpers/chain' describe('Controller configuration', () => { const graph = hre.graph() diff --git a/e2e/deployment/config/l1/bridgeEscrow.test.ts b/e2e/deployment/config/l1/bridgeEscrow.test.ts index 2304a2063..1aeba6357 100644 --- a/e2e/deployment/config/l1/bridgeEscrow.test.ts +++ b/e2e/deployment/config/l1/bridgeEscrow.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L1] BridgeEscrow configuration', function () { const graph = hre.graph() diff --git a/e2e/deployment/config/l1/graphToken.test.ts b/e2e/deployment/config/l1/graphToken.test.ts index 41e6322d0..0d222c263 100644 --- a/e2e/deployment/config/l1/graphToken.test.ts +++ b/e2e/deployment/config/l1/graphToken.test.ts @@ -1,7 +1,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L1] GraphToken', () => { const graph = hre.graph() diff --git a/e2e/deployment/config/l1/l1GraphTokenGateway.test.ts b/e2e/deployment/config/l1/l1GraphTokenGateway.test.ts index 88abc6c1c..a957f6882 100644 --- a/e2e/deployment/config/l1/l1GraphTokenGateway.test.ts +++ b/e2e/deployment/config/l1/l1GraphTokenGateway.test.ts @@ -1,7 +1,8 @@ import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { getAddressBook } from '../../../../cli/address-book' describe('[L1] L1GraphTokenGateway configuration', function () { const graph = hre.graph() @@ -13,9 +14,9 @@ describe('[L1] L1GraphTokenGateway configuration', function () { unauthorized = (await graph.getTestAccounts())[0] }) - it('bridge should be paused', async function () { + it('bridge should not be paused', async function () { const paused = await L1GraphTokenGateway.paused() - expect(paused).eq(true) + expect(paused).eq(false) }) it('should be controlled by Controller', async function () { @@ -23,6 +24,43 @@ describe('[L1] L1GraphTokenGateway configuration', function () { expect(controller).eq(Controller.address) }) + it('l2GRT should match the L2 GraphToken deployed address', async function () { + const l2GRT = await L1GraphTokenGateway.l2GRT() + expect(l2GRT).eq(graph.l2.contracts.GraphToken.address) + }) + + it('l2Counterpart should match the deployed L2 GraphTokenGateway address', async function () { + const l2Counterpart = await L1GraphTokenGateway.l2Counterpart() + expect(l2Counterpart).eq(graph.l2.contracts.L2GraphTokenGateway.address) + }) + + it('escrow should match the deployed L1 BridgeEscrow address', async function () { + const escrow = await L1GraphTokenGateway.escrow() + expect(escrow).eq(graph.l1.contracts.BridgeEscrow.address) + }) + + it("inbox should match Arbitrum's Inbox address", async function () { + const inbox = await L1GraphTokenGateway.inbox() + + // TODO: is there a cleaner way to get the router address? + const arbitrumAddressBook = process.env.ARBITRUM_ADDRESS_BOOK ?? 'arbitrum-addresses-local.json' + const arbAddressBook = getAddressBook(arbitrumAddressBook, graph.l1.chainId.toString()) + const arbIInbox = arbAddressBook.getEntry('IInbox') + + expect(inbox.toLowerCase()).eq(arbIInbox.address.toLowerCase()) + }) + + it("l1Router should match Arbitrum's router address", async function () { + const l1Router = await L1GraphTokenGateway.l1Router() + + // TODO: is there a cleaner way to get the router address? + const arbitrumAddressBook = process.env.ARBITRUM_ADDRESS_BOOK ?? 'arbitrum-addresses-local.json' + const arbAddressBook = getAddressBook(arbitrumAddressBook, graph.l1.chainId.toString()) + const arbL2Router = arbAddressBook.getEntry('L1GatewayRouter') + + expect(l1Router).eq(arbL2Router.address) + }) + describe('calls with unauthorized user', () => { it('initialize should revert', async function () { const tx = L1GraphTokenGateway.connect(unauthorized).initialize(unauthorized.address) @@ -77,7 +115,7 @@ describe('[L1] L1GraphTokenGateway configuration', function () { '0x00', ) - await expect(tx).revertedWith('Paused (contract)') + await expect(tx).revertedWith('NOT_FROM_BRIDGE') }) }) }) diff --git a/e2e/deployment/config/l1/rewardsManager.test.ts b/e2e/deployment/config/l1/rewardsManager.test.ts index 4cc990161..cf7cbb09c 100644 --- a/e2e/deployment/config/l1/rewardsManager.test.ts +++ b/e2e/deployment/config/l1/rewardsManager.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L1] RewardsManager configuration', () => { const graph = hre.graph() diff --git a/e2e/deployment/config/l2/l2GraphToken.test.ts b/e2e/deployment/config/l2/l2GraphToken.test.ts index 7b87253e0..a4ccdaec8 100644 --- a/e2e/deployment/config/l2/l2GraphToken.test.ts +++ b/e2e/deployment/config/l2/l2GraphToken.test.ts @@ -1,7 +1,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L2] L2GraphToken', () => { const graph = hre.graph() @@ -14,6 +14,16 @@ describe('[L2] L2GraphToken', () => { unauthorized = (await graph.getTestAccounts())[0] }) + it('l1Address should match the L1 GraphToken deployed address', async function () { + const l1Address = await L2GraphToken.l1Address() + expect(l1Address).eq(graph.l1.contracts.GraphToken.address) + }) + + it('gateway should match the L2 GraphTokenGateway deployed address', async function () { + const gateway = await L2GraphToken.gateway() + expect(gateway).eq(graph.l2.contracts.L2GraphTokenGateway.address) + }) + describe('calls with unauthorized user', () => { it('mint should revert', async function () { const tx = L2GraphToken.connect(unauthorized).mint( diff --git a/e2e/deployment/config/l2/l2GraphTokenGateway.test.ts b/e2e/deployment/config/l2/l2GraphTokenGateway.test.ts index c8944927c..46df97686 100644 --- a/e2e/deployment/config/l2/l2GraphTokenGateway.test.ts +++ b/e2e/deployment/config/l2/l2GraphTokenGateway.test.ts @@ -1,7 +1,8 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import { getAddressBook } from '../../../../cli/address-book' +import GraphChain from '../../../../gre/helpers/chain' describe('[L2] L2GraphTokenGateway configuration', function () { const graph = hre.graph() @@ -13,9 +14,9 @@ describe('[L2] L2GraphTokenGateway configuration', function () { unauthorized = (await graph.getTestAccounts())[0] }) - it('bridge should be paused', async function () { + it('bridge should not be paused', async function () { const paused = await L2GraphTokenGateway.paused() - expect(paused).eq(true) + expect(paused).eq(false) }) it('should be controlled by Controller', async function () { @@ -23,6 +24,27 @@ describe('[L2] L2GraphTokenGateway configuration', function () { expect(controller).eq(Controller.address) }) + it('l1GRT should match the L1 GraphToken deployed address', async function () { + const l1GRT = await L2GraphTokenGateway.l1GRT() + expect(l1GRT).eq(graph.l1.contracts.GraphToken.address) + }) + + it('l1Counterpart should match the deployed L1 GraphTokenGateway address', async function () { + const l1Counterpart = await L2GraphTokenGateway.l1Counterpart() + expect(l1Counterpart).eq(graph.l1.contracts.L1GraphTokenGateway.address) + }) + + it("l2Router should match Arbitrum's router address", async function () { + const l2Router = await L2GraphTokenGateway.l2Router() + + // TODO: is there a cleaner way to get the router address? + const arbitrumAddressBook = process.env.ARBITRUM_ADDRESS_BOOK ?? 'arbitrum-addresses-local.json' + const arbAddressBook = getAddressBook(arbitrumAddressBook, graph.l2.chainId.toString()) + const arbL2Router = arbAddressBook.getEntry('L2GatewayRouter') + + expect(l2Router).eq(arbL2Router.address) + }) + describe('calls with unauthorized user', () => { it('initialize should revert', async function () { const tx = L2GraphTokenGateway.connect(unauthorized).initialize(unauthorized.address) @@ -55,7 +77,7 @@ describe('[L2] L2GraphTokenGateway configuration', function () { '0x00', ) - await expect(tx).revertedWith('Paused (contract)') + await expect(tx).revertedWith('ONLY_COUNTERPART_GATEWAY') }) }) }) diff --git a/e2e/deployment/config/l2/rewardsManager.test.ts b/e2e/deployment/config/l2/rewardsManager.test.ts index 29ad83c5f..a5e2e7cbf 100644 --- a/e2e/deployment/config/l2/rewardsManager.test.ts +++ b/e2e/deployment/config/l2/rewardsManager.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L2] RewardsManager configuration', () => { const graph = hre.graph() diff --git a/e2e/deployment/init/l1/bridgeEscrow.test.ts b/e2e/deployment/init/l1/bridgeEscrow.test.ts new file mode 100644 index 000000000..2f0333eb2 --- /dev/null +++ b/e2e/deployment/init/l1/bridgeEscrow.test.ts @@ -0,0 +1,17 @@ +import { expect } from 'chai' +import hre from 'hardhat' +import GraphChain from '../../../../gre/helpers/chain' + +describe('BridgeEscrow initialization', () => { + const graph = hre.graph() + const { BridgeEscrow, GraphToken, L1GraphTokenGateway } = graph.contracts + + before(async function () { + if (GraphChain.isL2(graph.chainId)) this.skip() + }) + + it("should allow L1GraphTokenGateway contract to spend MAX_UINT256 tokens on BridgeEscrow's behalf", async function () { + const allowance = await GraphToken.allowance(BridgeEscrow.address, L1GraphTokenGateway.address) + expect(allowance).eq(hre.ethers.constants.MaxUint256) + }) +}) diff --git a/e2e/deployment/init/l1/graphToken.test.ts b/e2e/deployment/init/l1/graphToken.test.ts index aa0cc04e3..39766b479 100644 --- a/e2e/deployment/init/l1/graphToken.test.ts +++ b/e2e/deployment/init/l1/graphToken.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import hre from 'hardhat' import { getItemValue } from '../../../../cli/config' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L1] GraphToken initialization', () => { const graph = hre.graph() diff --git a/e2e/deployment/init/l2/graphToken.test.ts b/e2e/deployment/init/l2/graphToken.test.ts index 90b232531..048531283 100644 --- a/e2e/deployment/init/l2/graphToken.test.ts +++ b/e2e/deployment/init/l2/graphToken.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import GraphChain from '../../../../gre/helpers/network' +import GraphChain from '../../../../gre/helpers/chain' describe('[L2] GraphToken initialization', () => { const graph = hre.graph() diff --git a/e2e/scenarios/fixtures/bridge.ts b/e2e/scenarios/fixtures/bridge.ts new file mode 100644 index 000000000..a1441ec89 --- /dev/null +++ b/e2e/scenarios/fixtures/bridge.ts @@ -0,0 +1,29 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { BigNumber } from 'ethers' +import { toGRT } from '../../../cli/network' + +export interface BridgeFixture { + deploymentFile: string + funder: SignerWithAddress + accountsToFund: { + signer: SignerWithAddress + amount: BigNumber + }[] +} + +// Signers +// 0: l1Deployer +// 1: l2Deployer + +export const getBridgeFixture = (signers: SignerWithAddress[]): BridgeFixture => { + return { + deploymentFile: 'localNetwork.json', + funder: signers[0], + accountsToFund: [ + { + signer: signers[1], + amount: toGRT(10_000_000), + }, + ], + } +} diff --git a/e2e/scenarios/lib/curation.ts b/e2e/scenarios/lib/curation.ts index 1dc35cbb2..b0c2d927b 100644 --- a/e2e/scenarios/lib/curation.ts +++ b/e2e/scenarios/lib/curation.ts @@ -16,6 +16,6 @@ export const signal = async ( // Add signal console.log(`\nAdd ${amount} in signal to subgraphId ${subgraphId}..`) await sendTransaction(curator, contracts.GNS, 'mintSignal', [subgraphId, amount, 0], { - gasLimit: 2000000, + gasLimit: 4_000_000, }) } diff --git a/e2e/scenarios/lib/helpers.ts b/e2e/scenarios/lib/helpers.ts index db9da32cb..e9a1eca0e 100644 --- a/e2e/scenarios/lib/helpers.ts +++ b/e2e/scenarios/lib/helpers.ts @@ -1,14 +1,20 @@ export function getGraphOptsFromArgv(): { - graphConfig: string | undefined addressBook: string | undefined + graphConfig: string | undefined + l1GraphConfig: string | undefined + l2GraphConfig: string | undefined + disableSecureAccounts?: boolean | undefined } { const argv = process.argv.slice(2) - const getArgv = (index: number) => + const getArgv: any = (index: number) => argv[index] && argv[index] !== 'undefined' ? argv[index] : undefined return { - graphConfig: getArgv(0), - addressBook: getArgv(1), + addressBook: getArgv(0), + graphConfig: getArgv(1), + l1GraphConfig: getArgv(2), + l2GraphConfig: getArgv(3), + disableSecureAccounts: getArgv(4), } } diff --git a/e2e/scenarios/lib/staking.ts b/e2e/scenarios/lib/staking.ts index d221afccd..53465cfb8 100644 --- a/e2e/scenarios/lib/staking.ts +++ b/e2e/scenarios/lib/staking.ts @@ -42,7 +42,7 @@ export const allocateFrom = async ( 'allocateFrom', [indexer.address, subgraphDeploymentID, amount, allocationId, metadata, proof], { - gasLimit: 2000000, + gasLimit: 4_000_000, }, ) } @@ -56,6 +56,6 @@ export const closeAllocation = async ( console.log(`\nClosing ${allocationId}...`) await sendTransaction(indexer, contracts.Staking, 'closeAllocation', [allocationId, poi], { - gasLimit: 2000000, + gasLimit: 4_000_000, }) } diff --git a/e2e/scenarios/lib/subgraph.ts b/e2e/scenarios/lib/subgraph.ts index c17c4e543..f97f1d76b 100644 --- a/e2e/scenarios/lib/subgraph.ts +++ b/e2e/scenarios/lib/subgraph.ts @@ -26,10 +26,14 @@ export const publishNewSubgraph = async ( publisher.address, await contracts.GNS.nextAccountSeqID(publisher.address), ) - await sendTransaction(publisher, contracts.GNS, 'publishNewSubgraph', [ - deploymentId, - randomHexBytes(), - randomHexBytes(), - ]) + await sendTransaction( + publisher, + contracts.GNS, + 'publishNewSubgraph', + [deploymentId, randomHexBytes(), randomHexBytes()], + { + gasLimit: 4_000_000, + }, + ) return subgraphId } diff --git a/e2e/scenarios/send-grt-to-l2.test.ts b/e2e/scenarios/send-grt-to-l2.test.ts new file mode 100644 index 000000000..9b81371b6 --- /dev/null +++ b/e2e/scenarios/send-grt-to-l2.test.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai' +import hre from 'hardhat' +import { getBridgeFixture, BridgeFixture } from './fixtures/bridge' + +describe('Bridge GRT to L2', () => { + const graph = hre.graph() + let bridgeFixture: BridgeFixture + + before(async () => { + const l1Deployer = await graph.l1.getDeployer() + const l2Deployer = await graph.l2.getDeployer() + bridgeFixture = getBridgeFixture([l1Deployer, l2Deployer]) + }) + + describe('GRT balances', () => { + it(`L2 balances should match bridged amount`, async function () { + for (const account of bridgeFixture.accountsToFund) { + const l2GrtBalance = await graph.l2.contracts.GraphToken.balanceOf(account.signer.address) + expect(l2GrtBalance).eq(account.amount) + } + }) + }) +}) diff --git a/e2e/scenarios/send-grt-to-l2.ts b/e2e/scenarios/send-grt-to-l2.ts new file mode 100644 index 000000000..9884c9816 --- /dev/null +++ b/e2e/scenarios/send-grt-to-l2.ts @@ -0,0 +1,40 @@ +// ### Scenario description ### +// Bridge action > Bridge GRT tokens from L1 to L2 +// This scenario will bridge GRT tokens from L1 to L2. See fixtures for details. +// Run with: +// npx hardhat e2e:scenario send-grt-to-l2 --network --graph-config config/graph..yml + +import hre from 'hardhat' +import { TASK_BRIDGE_TO_L2 } from '../../tasks/bridge/to-l2' +import { getGraphOptsFromArgv } from './lib/helpers' +import { getBridgeFixture } from './fixtures/bridge' + +async function main() { + const graphOpts = getGraphOptsFromArgv() + const graph = hre.graph(graphOpts) + + const l1Deployer = await graph.l1.getDeployer() + const l2Deployer = await graph.l2.getDeployer() + + const bridgeFixture = getBridgeFixture([l1Deployer, l2Deployer]) + + // == Send GRT to L2 accounts + for (const account of bridgeFixture.accountsToFund) { + await hre.run(TASK_BRIDGE_TO_L2, { + ...graphOpts, + amount: account.amount.toString(), + sender: bridgeFixture.funder.address, + recipient: account.signer.address, + deploymentFile: bridgeFixture.deploymentFile, + }) + } +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exitCode = 1 + }) diff --git a/gre/README.md b/gre/README.md index 2ebb2365a..596e0122c 100644 --- a/gre/README.md +++ b/gre/README.md @@ -8,8 +8,9 @@ GRE is a hardhat plugin that extends hardhat's runtime environment to inject add - Exposes protocol configuration via graph config file and address book - Provides account management methods for convenience - Multichain! Supports both L1 and L2 layers of the protocol simultaneously +- Integrates seamlessly with [hardhat-secure-accounts](https://www.npmjs.com/package/hardhat-secure-accounts) -### Usage +## Usage #### Example Import GRE using `import './gre/gre'` on your hardhat config file and then: @@ -80,7 +81,7 @@ networks: { ... }, graph: { - addressBook: 'addresses.json' + addressBook: 'addresses.json' l1GraphConfig: 'config/graph.mainnet.yml' l2GraphConfig: 'config/graph.arbitrum-one.yml' } @@ -122,7 +123,7 @@ The priority for the address book is: 1) `hre.graph({ ... })` init parameter `addressBook` 2) `graph.addressBook` graph config parameter `addressBook` in hardhat config file -### API +## API GRE exposes functionality via a simple API: @@ -142,6 +143,7 @@ The interface for both `l1` and `l2` objects looks like this: export interface GraphNetworkEnvironment { chainId: number contracts: NetworkContracts + provider: EthersProviderWrapper graphConfig: any addressBook: AddressBook getNamedAccounts: () => Promise @@ -168,7 +170,7 @@ Returns an object with all the contracts available in the network. Connects usin **Graph Config** -Returns an object that grants raw access to the graph config file for the protocol. The graph config file is a YAML file that contains all the parameters with which the protocol was deployed. +Returns an object that grants raw access to the YAML parse of the graph config file for the protocol. The graph config file is a YAML file that contains all the parameters with which the protocol was deployed. > TODO: add better APIs to interact with the graph config file. @@ -226,4 +228,26 @@ It's important to note that the deployer is not a named account as it's derived Returns an object with wallets derived from the mnemonic or private key provided via hardhat network configuration. These wallets are not connected to a provider. **Account management: getWallet** -Returns a wallet derived from the mnemonic or private key provided via hardhat network configuration that matches a given address. This wallet is not connected to a provider. \ No newline at end of file +Returns a wallet derived from the mnemonic or private key provided via hardhat network configuration that matches a given address. This wallet is not connected to a provider. + +#### Integration with hardhat-secure-accounts + +[hardhat-secure-accounts](https://www.npmjs.com/package/hardhat-secure-accounts) is a hardhat plugin that allows you to use encrypted keystore files to store your private keys. GRE has built-in support to use this plugin. By default is enabled but can be disabled by setting the `disableSecureAccounts` option to `true` when instantiating the GRE object. When enabled, each time you call any of the account management methods you will be prompted for an account name and password to unlock: + +```js +// Without secure accounts +> const graph = hre.graph({ disableSecureAccounts: true }) +> const deployer = await g.l1.getDeployer() +> deployer.address +'0xBc7f4d3a85B820fDB1058FD93073Eb6bc9AAF59b' + +// With secure accounts +> const graph = hre.graph() +> const deployer = await g.l1.getDeployer() +== Using secure accounts, please unlock an account for L1(goerli) +Available accounts: goerli-deployer, arbitrum-goerli-deployer, rinkeby-deployer, test-mnemonic +Choose an account to unlock (use tab to autocomplete): test-mnemonic +Enter the password for this account: ************ +> deployer.address +'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1' +``` diff --git a/gre/accounts.ts b/gre/accounts.ts index 6baf3da6a..b316fba7d 100644 --- a/gre/accounts.ts +++ b/gre/accounts.ts @@ -4,7 +4,7 @@ import { derivePrivateKeys } from 'hardhat/internal/core/providers/util' import { Wallet } from 'ethers' import { getItemValue, readConfig } from '../cli/config' import { AccountNames, NamedAccounts } from './type-extensions' -import { getNetworkName } from './config' +import { getNetworkName } from './helpers/network' import { HttpNetworkHDAccountsConfig, NetworksConfig } from 'hardhat/types' const namedAccountList: AccountNames[] = [ diff --git a/gre/config.ts b/gre/config.ts index 9899f6d13..c642f068d 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -1,19 +1,16 @@ import fs from 'fs' -import path from 'path' -import { NetworkConfig, NetworksConfig } from 'hardhat/types/config' import { HardhatRuntimeEnvironment } from 'hardhat/types/runtime' -import { HttpNetworkConfig } from 'hardhat/types/config' import { GraphRuntimeEnvironmentOptions } from './type-extensions' import { GREPluginError } from './helpers/error' -import GraphNetwork, { counterpartName } from './helpers/network' - -import { createProvider } from 'hardhat/internal/core/providers/construction' +import GraphNetwork from './helpers/chain' import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' -import { logDebug, logWarn } from './logger' -import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins' +import { logDebug } from './helpers/logger' +import { normalizePath } from './helpers/utils' +import { getNetworkConfig } from './helpers/network' +import { getDefaultProvider } from './providers' interface GREChains { l1ChainId: number @@ -89,7 +86,7 @@ export function getChains(mainChainId: number | undefined): GREChains { } } -export function getProviders( +export function getDefaultProviders( hre: HardhatRuntimeEnvironment, l1ChainId: number, l2ChainId: number, @@ -97,41 +94,20 @@ export function getProviders( ): GREProviders { logDebug('== Getting providers') - const getProvider = ( - networks: NetworksConfig, - chainId: number, - mainNetworkName: string, - isMainProvider: boolean, - chainLabel: string, - ): EthersProviderWrapper | undefined => { - const network = getNetworkConfig(networks, chainId, mainNetworkName) as HttpNetworkConfig - const networkName = getNetworkName(networks, chainId, mainNetworkName) - - logDebug(`Provider url for ${chainLabel}(${networkName}): ${network?.url}`) - - // Ensure at least main provider is configured - // For Hardhat network we don't need url to create a provider - if ( - isMainProvider && - (network === undefined || network.url === undefined) && - networkName !== HARDHAT_NETWORK_NAME - ) { - throw new GREPluginError(`Must set a provider url for chain: ${chainId}!`) - } - - if (network === undefined || networkName === undefined) { - return undefined - } - - // Build provider as EthersProviderWrapper instead of JsonRpcProvider - // This allows us to use hardhat's account management methods for free - const ethereumProvider = createProvider(networkName, network) - const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider) - return ethersProviderWrapper - } - - const l1Provider = getProvider(hre.config.networks, l1ChainId, hre.network.name, isHHL1, 'L1') - const l2Provider = getProvider(hre.config.networks, l2ChainId, hre.network.name, !isHHL1, 'L2') + const l1Provider = getDefaultProvider( + hre.config.networks, + l1ChainId, + hre.network.name, + isHHL1, + 'L1', + ) + const l2Provider = getDefaultProvider( + hre.config.networks, + l2ChainId, + hre.network.name, + !isHHL1, + 'L2', + ) return { l1Provider, @@ -211,61 +187,3 @@ export function getGraphConfigPaths( l2GraphConfigPath: l2GraphConfigPath, } } - -function getNetworkConfig( - networks: NetworksConfig, - chainId: number, - mainNetworkName: string, -): (NetworkConfig & { name: string }) | undefined { - const candidateNetworks = Object.keys(networks) - .map((n) => ({ ...networks[n], name: n })) - .filter((n) => n.chainId === chainId) - - if (candidateNetworks.length > 1) { - logWarn( - `Found multiple networks with chainId ${chainId}, trying to use main network name to desambiguate`, - ) - - const filteredByMainNetworkName = candidateNetworks.filter((n) => n.name === mainNetworkName) - - if (filteredByMainNetworkName.length === 1) { - logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`) - return filteredByMainNetworkName[0] - } else { - logWarn(`Could not desambiguate with main network name, trying secondary network name`) - const secondaryNetworkName = counterpartName(mainNetworkName) - const filteredBySecondaryNetworkName = candidateNetworks.filter( - (n) => n.name === secondaryNetworkName, - ) - - if (filteredBySecondaryNetworkName.length === 1) { - logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`) - return filteredBySecondaryNetworkName[0] - } else { - throw new GREPluginError( - `Could not desambiguate network with chainID ${chainId}. Use case not supported!`, - ) - } - } - } else if (candidateNetworks.length === 1) { - return candidateNetworks[0] - } else { - return undefined - } -} - -export function getNetworkName( - networks: NetworksConfig, - chainId: number, - mainNetworkName: string, -): string | undefined { - const network = getNetworkConfig(networks, chainId, mainNetworkName) - return network?.name -} - -function normalizePath(_path: string, graphPath: string) { - if (!path.isAbsolute(_path)) { - _path = path.join(graphPath, _path) - } - return _path -} diff --git a/gre/gre.ts b/gre/gre.ts index f30c41504..d7e18e594 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -10,13 +10,16 @@ import { GraphRuntimeEnvironment, GraphRuntimeEnvironmentOptions, } from './type-extensions' -import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config' +import { getChains, getDefaultProviders, getAddressBookPath, getGraphConfigPaths } from './config' import { getDeployer, getNamedAccounts, getTestAccounts, getWallet, getWallets } from './accounts' -import { logDebug, logWarn } from './logger' +import { logDebug, logWarn } from './helpers/logger' import path from 'path' import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' import { Wallet } from 'ethers' +import 'hardhat-secure-accounts' +import { getSecureAccountsProvider } from './providers' + // Graph Runtime Environment (GRE) extensions for the HRE extendConfig((config: HardhatConfig, userConfig: Readonly) => { @@ -43,8 +46,46 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { logDebug('*** Initializing Graph Runtime Environment (GRE) ***') logDebug(`Main network: ${hre.network.name}`) + const enableTxLogging = opts.enableTxLogging ?? false + logDebug(`Tx logging: ${enableTxLogging ? 'enabled' : 'disabled'}`) + + const secureAccounts = !( + opts.disableSecureAccounts ?? + hre.config.graph.disableSecureAccounts ?? + false + ) + logDebug(`Secure accounts: ${secureAccounts ? 'enabled' : 'disabled'}`) + const { l1ChainId, l2ChainId, isHHL1 } = getChains(hre.network.config.chainId) - const { l1Provider, l2Provider } = getProviders(hre, l1ChainId, l2ChainId, isHHL1) + + // Default providers for L1 and L2 + const { l1Provider, l2Provider } = getDefaultProviders(hre, l1ChainId, l2ChainId, isHHL1) + + // Getters to unlock secure account providers for L1 and L2 + const l1UnlockProvider = () => + getSecureAccountsProvider( + hre.accounts, + hre.config.networks, + l1ChainId, + hre.network.name, + isHHL1, + 'L1', + opts.l1AccountName, + opts.l1AccountPassword, + ) + + const l2UnlockProvider = () => + getSecureAccountsProvider( + hre.accounts, + hre.config.networks, + l2ChainId, + hre.network.name, + !isHHL1, + 'L2', + opts.l2AccountName, + opts.l2AccountPassword, + ) + const addressBookPath = getAddressBookPath(hre, opts) const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( hre, @@ -69,8 +110,11 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { l1GraphConfigPath, addressBookPath, isHHL1, + enableTxLogging, + secureAccounts, l1GetWallets, l1GetWallet, + l1UnlockProvider, ) const l2Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment( @@ -79,8 +123,11 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { l2GraphConfigPath, addressBookPath, isHHL1, + enableTxLogging, + secureAccounts, l2GetWallets, l2GetWallet, + l2UnlockProvider, ) const gre: GraphRuntimeEnvironment = { @@ -102,8 +149,11 @@ function buildGraphNetworkEnvironment( graphConfigPath: string | undefined, addressBookPath: string, isHHL1: boolean, + enableTxLogging: boolean, + secureAccounts: boolean, getWallets: () => Promise, getWallet: (address: string) => Promise, + unlockProvider: () => Promise, ): GraphNetworkEnvironment | null { if (graphConfigPath === undefined) { logWarn( @@ -121,6 +171,9 @@ function buildGraphNetworkEnvironment( return null } + // Upgrade provider to secure accounts if feature is enabled + const getUpdatedProvider = async () => (secureAccounts ? await unlockProvider() : provider) + return { chainId: chainId, provider: provider, @@ -129,10 +182,14 @@ function buildGraphNetworkEnvironment( contracts: lazyObject(() => loadContracts(getAddressBook(addressBookPath, chainId.toString()), chainId, provider), ), - getDeployer: lazyFunction(() => () => getDeployer(provider)), - getNamedAccounts: lazyFunction(() => () => getNamedAccounts(provider, graphConfigPath)), - getTestAccounts: lazyFunction(() => () => getTestAccounts(provider, graphConfigPath)), getWallets: lazyFunction(() => () => getWallets()), getWallet: lazyFunction(() => (address: string) => getWallet(address)), + getDeployer: lazyFunction(() => async () => getDeployer(await getUpdatedProvider())), + getNamedAccounts: lazyFunction( + () => async () => getNamedAccounts(await getUpdatedProvider(), graphConfigPath), + ), + getTestAccounts: lazyFunction( + () => async () => getTestAccounts(await getUpdatedProvider(), graphConfigPath), + ), } } diff --git a/gre/helpers/chain.ts b/gre/helpers/chain.ts new file mode 100644 index 000000000..4530904b6 --- /dev/null +++ b/gre/helpers/chain.ts @@ -0,0 +1,69 @@ +class MapWithGetKey extends Map { + getKey(value: K): K | undefined { + for (const [k, v] of this.entries()) { + if (v === value) { + return k + } + } + return + } +} + +const chainMap = new MapWithGetKey([ + [1, 42161], // Ethereum Mainnet - Arbitrum One + [4, 421611], // Ethereum Rinkeby - Arbitrum Rinkeby + [5, 421613], // Ethereum Goerli - Arbitrum Goerli + [1337, 412346], // Localhost - Arbitrum Localhost +]) + +// Hardhat network names as per our convention +const nameMap = new MapWithGetKey([ + ['mainnet', 'arbitrum-one'], // Ethereum Mainnet - Arbitrum One + ['rinkeby', 'arbitrum-rinkeby'], // Ethereum Rinkeby - Arbitrum Rinkeby + ['goerli', 'arbitrum-goerli'], // Ethereum Goerli - Arbitrum Goerli + ['localnitrol1', 'localnitrol2'], // Arbitrum testnode L1 - Arbitrum testnode L2 +]) + +export const l1Chains = Array.from(chainMap.keys()) +export const l2Chains = Array.from(chainMap.values()) +export const chains = [...l1Chains, ...l2Chains] + +export const l1ChainNames = Array.from(nameMap.keys()) +export const l2ChainNames = Array.from(nameMap.values()) +export const chainNames = [...l1ChainNames, ...l2ChainNames] + +export const isL1 = (chainId: number): boolean => l1Chains.includes(chainId) +export const isL2 = (chainId: number): boolean => l2Chains.includes(chainId) +export const isSupported = (chainId: number | undefined): boolean => + chainId !== undefined && chains.includes(chainId) + +export const isL1Name = (name: string): boolean => l1ChainNames.includes(name) +export const isL2Name = (name: string): boolean => l2ChainNames.includes(name) +export const isSupportedName = (name: string | undefined): boolean => + name !== undefined && chainNames.includes(name) + +export const l1ToL2 = (chainId: number): number | undefined => chainMap.get(chainId) +export const l2ToL1 = (chainId: number): number | undefined => chainMap.getKey(chainId) +export const counterpart = (chainId: number): number | undefined => { + if (!isSupported(chainId)) return + return isL1(chainId) ? l1ToL2(chainId) : l2ToL1(chainId) +} + +export const l1ToL2Name = (name: string): string | undefined => nameMap.get(name) +export const l2ToL1Name = (name: string): string | undefined => nameMap.getKey(name) +export const counterpartName = (name: string): string | undefined => { + if (!isSupportedName(name)) return + return isL1Name(name) ? l1ToL2Name(name) : l2ToL1Name(name) +} + +export default { + l1Chains, + l2Chains, + chains, + isL1, + isL2, + isSupported, + l1ToL2, + l2ToL1, + counterpart, +} diff --git a/gre/helpers/error.ts b/gre/helpers/error.ts index f9aafa748..46a2d7122 100644 --- a/gre/helpers/error.ts +++ b/gre/helpers/error.ts @@ -1,5 +1,5 @@ import { HardhatPluginError } from 'hardhat/plugins' -import { logError } from '../logger' +import { logError } from './logger' export class GREPluginError extends HardhatPluginError { constructor(message: string) { diff --git a/gre/logger.ts b/gre/helpers/logger.ts similarity index 100% rename from gre/logger.ts rename to gre/helpers/logger.ts diff --git a/gre/helpers/network.ts b/gre/helpers/network.ts index 4530904b6..eb2980556 100644 --- a/gre/helpers/network.ts +++ b/gre/helpers/network.ts @@ -1,69 +1,55 @@ -class MapWithGetKey extends Map { - getKey(value: K): K | undefined { - for (const [k, v] of this.entries()) { - if (v === value) { - return k +import { NetworkConfig, NetworksConfig } from 'hardhat/types/config' +import { logDebug, logWarn } from './logger' +import { GREPluginError } from './error' +import { counterpartName } from './chain' + +export function getNetworkConfig( + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, +): (NetworkConfig & { name: string }) | undefined { + const candidateNetworks = Object.keys(networks) + .map((n) => ({ ...networks[n], name: n })) + .filter((n) => n.chainId === chainId) + + if (candidateNetworks.length > 1) { + logWarn( + `Found multiple networks with chainId ${chainId}, trying to use main network name to desambiguate`, + ) + + const filteredByMainNetworkName = candidateNetworks.filter((n) => n.name === mainNetworkName) + + if (filteredByMainNetworkName.length === 1) { + logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`) + return filteredByMainNetworkName[0] + } else { + logWarn(`Could not desambiguate with main network name, trying secondary network name`) + const secondaryNetworkName = counterpartName(mainNetworkName) + const filteredBySecondaryNetworkName = candidateNetworks.filter( + (n) => n.name === secondaryNetworkName, + ) + + if (filteredBySecondaryNetworkName.length === 1) { + logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`) + return filteredBySecondaryNetworkName[0] + } else { + throw new GREPluginError( + `Could not desambiguate network with chainID ${chainId}. Use case not supported!`, + ) } } - return + } else if (candidateNetworks.length === 1) { + return candidateNetworks[0] + } else { + return undefined } } -const chainMap = new MapWithGetKey([ - [1, 42161], // Ethereum Mainnet - Arbitrum One - [4, 421611], // Ethereum Rinkeby - Arbitrum Rinkeby - [5, 421613], // Ethereum Goerli - Arbitrum Goerli - [1337, 412346], // Localhost - Arbitrum Localhost -]) - -// Hardhat network names as per our convention -const nameMap = new MapWithGetKey([ - ['mainnet', 'arbitrum-one'], // Ethereum Mainnet - Arbitrum One - ['rinkeby', 'arbitrum-rinkeby'], // Ethereum Rinkeby - Arbitrum Rinkeby - ['goerli', 'arbitrum-goerli'], // Ethereum Goerli - Arbitrum Goerli - ['localnitrol1', 'localnitrol2'], // Arbitrum testnode L1 - Arbitrum testnode L2 -]) - -export const l1Chains = Array.from(chainMap.keys()) -export const l2Chains = Array.from(chainMap.values()) -export const chains = [...l1Chains, ...l2Chains] - -export const l1ChainNames = Array.from(nameMap.keys()) -export const l2ChainNames = Array.from(nameMap.values()) -export const chainNames = [...l1ChainNames, ...l2ChainNames] - -export const isL1 = (chainId: number): boolean => l1Chains.includes(chainId) -export const isL2 = (chainId: number): boolean => l2Chains.includes(chainId) -export const isSupported = (chainId: number | undefined): boolean => - chainId !== undefined && chains.includes(chainId) - -export const isL1Name = (name: string): boolean => l1ChainNames.includes(name) -export const isL2Name = (name: string): boolean => l2ChainNames.includes(name) -export const isSupportedName = (name: string | undefined): boolean => - name !== undefined && chainNames.includes(name) - -export const l1ToL2 = (chainId: number): number | undefined => chainMap.get(chainId) -export const l2ToL1 = (chainId: number): number | undefined => chainMap.getKey(chainId) -export const counterpart = (chainId: number): number | undefined => { - if (!isSupported(chainId)) return - return isL1(chainId) ? l1ToL2(chainId) : l2ToL1(chainId) -} - -export const l1ToL2Name = (name: string): string | undefined => nameMap.get(name) -export const l2ToL1Name = (name: string): string | undefined => nameMap.getKey(name) -export const counterpartName = (name: string): string | undefined => { - if (!isSupportedName(name)) return - return isL1Name(name) ? l1ToL2Name(name) : l2ToL1Name(name) -} - -export default { - l1Chains, - l2Chains, - chains, - isL1, - isL2, - isSupported, - l1ToL2, - l2ToL1, - counterpart, +export function getNetworkName( + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, +): string | undefined { + const network = getNetworkConfig(networks, chainId, mainNetworkName) + return network?.name } diff --git a/gre/helpers/utils.ts b/gre/helpers/utils.ts new file mode 100644 index 000000000..e8930621e --- /dev/null +++ b/gre/helpers/utils.ts @@ -0,0 +1,8 @@ +import path from 'path' + +export function normalizePath(_path: string, graphPath: string): string { + if (!path.isAbsolute(_path)) { + _path = path.join(graphPath, _path) + } + return _path +} diff --git a/gre/providers.ts b/gre/providers.ts new file mode 100644 index 000000000..61187e6c1 --- /dev/null +++ b/gre/providers.ts @@ -0,0 +1,97 @@ +import { HardhatRuntimeEnvironment, Network } from 'hardhat/types/runtime' +import { NetworksConfig, HttpNetworkConfig } from 'hardhat/types/config' +import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' +import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins' +import { createProvider } from 'hardhat/internal/core/providers/construction' + +import { getNetworkConfig, getNetworkName } from './helpers/network' +import { logDebug } from './helpers/logger' + +import { GREPluginError } from './helpers/error' +import { AccountsRuntimeEnvironment } from 'hardhat-secure-accounts/dist/src/type-extensions' + +export const getDefaultProvider = ( + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, + isMainProvider: boolean, + chainLabel: string, +): EthersProviderWrapper | undefined => { + const { networkConfig, networkName } = getNetworkData( + networks, + chainId, + mainNetworkName, + isMainProvider, + chainLabel, + ) + + if (networkConfig === undefined || networkName === undefined) { + return undefined + } + + logDebug(`Creating provider for ${chainLabel}(${networkName})`) + const ethereumProvider = createProvider(networkName, networkConfig) + const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider) + return ethersProviderWrapper +} + +export const getSecureAccountsProvider = async ( + accounts: AccountsRuntimeEnvironment, + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, + isMainProvider: boolean, + chainLabel: string, + accountName?: string, + accountPassword?: string, +): Promise => { + const { networkConfig, networkName } = getNetworkData( + networks, + chainId, + mainNetworkName, + isMainProvider, + chainLabel, + ) + + if (networkConfig === undefined || networkName === undefined) { + return undefined + } + + logDebug(`Using secure accounts provider for ${chainLabel}(${networkName})`) + if (accountName === undefined || accountPassword === undefined) { + console.log( + `== Using secure accounts, please unlock an account for ${chainLabel}(${networkName})`, + ) + } + + return await accounts.getProvider( + { name: networkName, config: networkConfig } as Network, + accountName, + accountPassword, + ) +} + +const getNetworkData = ( + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, + isMainProvider: boolean, + chainLabel: string, +): { networkConfig: HttpNetworkConfig | undefined; networkName: string | undefined } => { + const networkConfig = getNetworkConfig(networks, chainId, mainNetworkName) as HttpNetworkConfig + const networkName = getNetworkName(networks, chainId, mainNetworkName) + + logDebug(`Provider url for ${chainLabel}(${networkName}): ${networkConfig?.url}`) + + // Ensure at least main provider is configured + // For Hardhat network we don't need url to create a provider + if ( + isMainProvider && + (networkConfig === undefined || networkConfig.url === undefined) && + networkName !== HARDHAT_NETWORK_NAME + ) { + throw new GREPluginError(`Must set a provider url for chain: ${chainId}!`) + } + + return { networkConfig, networkName } +} diff --git a/gre/test/accounts.test.ts b/gre/test/accounts.test.ts index f309aed95..74b4020cd 100644 --- a/gre/test/accounts.test.ts +++ b/gre/test/accounts.test.ts @@ -83,3 +83,100 @@ describe('GRE usage > account management', function () { }) }) }) + +describe('GRE usage > secure accounts', function () { + useEnvironment('graph-config', 'hardhat') + + let graph: GraphRuntimeEnvironment + let graphSecureAccounts: GraphRuntimeEnvironment + + beforeEach(function () { + graph = this.hre.graph({ + disableSecureAccounts: true, + }) + + graphSecureAccounts = this.hre.graph({ + disableSecureAccounts: false, + l1AccountName: 'test-account', + l1AccountPassword: 'batman-with-cheese', + l2AccountName: 'test-account-l2', + l2AccountPassword: 'batman-with-cheese', + }) + }) + + describe('getDeployer', function () { + it('should return different accounts', async function () { + const deployer = await graph.l1.getDeployer() + const deployerSecureAccount = await graphSecureAccounts.l1.getDeployer() + + expect(deployer.address).not.to.equal(deployerSecureAccount.address) + expect(deployer.address).to.equal('0x2770fb12b368a9aBf4A02DB34B0F6057fC03BD0d') + expect(deployerSecureAccount.address).to.equal('0xC108fda1b5b2903751594298769Efd4904b146bD') + }) + + it('should return accounts capable of signing messages', async function () { + const deployer = await graph.l1.getDeployer() + const deployerSecureAccount = await graphSecureAccounts.l1.getDeployer() + + expect(deployer.signMessage('test')).to.eventually.be.fulfilled + expect(deployerSecureAccount.signMessage('test')).to.eventually.be.fulfilled + }) + }) + + describe('getNamedAccounts', function () { + it('should return the same accounts', async function () { + const accounts = await graph.l1.getNamedAccounts() + const secureAccounts = await graphSecureAccounts.l1.getNamedAccounts() + + const accountNames = Object.keys(accounts) + const secureAccountNames = Object.keys(secureAccounts) + + expect(accountNames.length).to.equal(secureAccountNames.length) + + for (const name of accountNames) { + const account = accounts[name] + const secureAccount = secureAccounts[name] + + expect(account.address).to.equal(secureAccount.address) + } + }) + + it('should return accounts incapable of signing messages', async function () { + const accounts = await graph.l1.getNamedAccounts() + const secureAccounts = await graphSecureAccounts.l1.getNamedAccounts() + + const accountNames = Object.keys(accounts) + + for (const name of accountNames) { + const account = accounts[name] + const secureAccount = secureAccounts[name] + + expect(account.signMessage('test')).to.eventually.be.rejectedWith(/unknown account/) + expect(secureAccount.signMessage('test')).to.eventually.be.rejected + } + }) + }) + + describe('getTestAccounts', function () { + it('should return different accounts', async function () { + const accounts = await graph.l1.getTestAccounts() + const secureAccounts = await graphSecureAccounts.l1.getTestAccounts() + + expect(accounts.length).to.equal(secureAccounts.length) + + for (let i = 0; i < accounts.length; i++) { + expect(accounts[i].address).not.to.equal(secureAccounts[i].address) + } + }) + + it('should return accounts capable of signing messages', async function () { + const accounts = await graph.l1.getTestAccounts() + const secureAccounts = await graphSecureAccounts.l1.getTestAccounts() + + for (let i = 0; i < accounts.length; i++) { + expect(accounts[i].signMessage('test')).to.eventually.be.fulfilled + expect(secureAccounts[i].signMessage('test')).to.eventually.be.fulfilled + } + }) + }) +}) diff --git a/gre/test/config.test.ts b/gre/test/config.test.ts index de6287e2e..e150b41c3 100644 --- a/gre/test/config.test.ts +++ b/gre/test/config.test.ts @@ -1,15 +1,10 @@ import { expect } from 'chai' import { useEnvironment } from './helpers' - -import { - getAddressBookPath, - getChains, - getGraphConfigPaths, - getNetworkName, - getProviders, -} from '../config' import path from 'path' +import { getAddressBookPath, getChains, getDefaultProviders, getGraphConfigPaths } from '../config' +import { getNetworkName } from '../helpers/network' + describe('GRE init functions', function () { describe('getAddressBookPath with graph-config project', function () { useEnvironment('graph-config') @@ -66,45 +61,45 @@ describe('GRE init functions', function () { }) }) - describe('getProviders with graph-config project', function () { + describe('getDefaultProviders with graph-config project', function () { useEnvironment('graph-config') it('should return L1 and L2 providers for supported networks (HH L1)', function () { - const { l1Provider, l2Provider } = getProviders(this.hre, 5, 421613, true) + const { l1Provider, l2Provider } = getDefaultProviders(this.hre, 5, 421613, true) expect(l1Provider).to.be.an('object') expect(l2Provider).to.be.an('object') }) it('should return L1 and L2 providers for supported networks (HH L2)', function () { - const { l1Provider, l2Provider } = getProviders(this.hre, 5, 421613, false) + const { l1Provider, l2Provider } = getDefaultProviders(this.hre, 5, 421613, false) expect(l1Provider).to.be.an('object') expect(l2Provider).to.be.an('object') }) it('should return only L1 provider if L2 is not supported (HH L1)', function () { - const { l1Provider, l2Provider } = getProviders(this.hre, 5, 123456, true) + const { l1Provider, l2Provider } = getDefaultProviders(this.hre, 5, 123456, true) expect(l1Provider).to.be.an('object') expect(l2Provider).to.be.undefined }) it('should return only L2 provider if L1 is not supported (HH L2)', function () { - const { l1Provider, l2Provider } = getProviders(this.hre, 123456, 421613, false) + const { l1Provider, l2Provider } = getDefaultProviders(this.hre, 123456, 421613, false) expect(l1Provider).to.be.undefined expect(l2Provider).to.be.an('object') }) }) - describe('getProviders with graph-config-bad project', function () { + describe('getDefaultProviders with graph-config-bad project', function () { useEnvironment('graph-config-bad') it('should throw if main network is not defined in hardhat config (HH L1)', function () { - expect(() => getProviders(this.hre, 4, 421611, true)).to.throw( + expect(() => getDefaultProviders(this.hre, 4, 421611, true)).to.throw( /Must set a provider url for chain: /, ) }) it('should throw if main network is not defined in hardhat config (HH L2)', function () { - expect(() => getProviders(this.hre, 5, 421613, false)).to.throw( + expect(() => getDefaultProviders(this.hre, 5, 421613, false)).to.throw( /Must set a provider url for chain: /, ) }) @@ -114,7 +109,7 @@ describe('GRE init functions', function () { useEnvironment('graph-config-desambiguate', 'localnitrol1') it('should use main network name to desambiguate if multiple chains are defined with same chainId', async function () { - const { l1Provider, l2Provider } = getProviders(this.hre, 1337, 412346, true) + const { l1Provider, l2Provider } = getDefaultProviders(this.hre, 1337, 412346, true) expect(l1Provider).to.be.an('object') expect(l2Provider).to.be.an('object') @@ -129,7 +124,7 @@ describe('GRE init functions', function () { useEnvironment('graph-config-desambiguate', 'localnitrol2') it('should use secondary network name to desambiguate if multiple chains are defined with same chainId', async function () { - const { l1Provider, l2Provider } = getProviders(this.hre, 1337, 412346, true) + const { l1Provider, l2Provider } = getDefaultProviders(this.hre, 1337, 412346, true) expect(l1Provider).to.be.an('object') expect(l2Provider).to.be.an('object') diff --git a/gre/test/files/config/graph.goerli.yml b/gre/test/files/config/graph.goerli.yml index e69de29bb..d15d1d25a 100644 --- a/gre/test/files/config/graph.goerli.yml +++ b/gre/test/files/config/graph.goerli.yml @@ -0,0 +1,7 @@ +general: + arbitrator: &arbitrator "0xFD01aa87BeB04D0ac764FC298aCFd05FfC5439cD" # Arbitration Council + governor: &governor "0xf1135bFF22512FF2A585b8d4489426CE660f204c" # Graph Council + authority: &authority "0x52e498aE9B8A5eE2A5Cd26805F06A9f29A7F489F" # Authority that signs payment vouchers + availabilityOracle: &availabilityOracle "0x14053D40ea2E81D3AB0739728a54ab84F21200F9" # Subgraph Availability Oracle + pauseGuardian: &pauseGuardian "0x6855D551CaDe60754D145fb5eDCD90912D860262" # Protocol pause guardian + allocationExchangeOwner: &allocationExchangeOwner "0xf1135bFF22512FF2A585b8d4489426CE660f204c" # Allocation Exchange owner diff --git a/gre/test/fixture-projects/graph-config/.accounts/test-account-l2.json b/gre/test/fixture-projects/graph-config/.accounts/test-account-l2.json new file mode 100644 index 000000000..ab1ae36bb --- /dev/null +++ b/gre/test/fixture-projects/graph-config/.accounts/test-account-l2.json @@ -0,0 +1,28 @@ +{ + "address": "5baa8472c470a400f830e2458ddb97b13cc8eb32", + "id": "ead5a876-efae-4cdf-aeab-ab81907427c8", + "version": 3, + "Crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { "iv": "5e90eb61380cee63382bd8c935eea554" }, + "ciphertext": "67800c67ab32b8baf2df4a697aa1108ee7f91b5a182ff2e29fa562009e1bbd9f", + "kdf": "scrypt", + "kdfparams": { + "salt": "415db4971651654fb4b381f86525c273e4c7470a69307f7c83f71ec38aca7d12", + "n": 131072, + "dklen": 32, + "p": 1, + "r": 8 + }, + "mac": "f5611372940c7da01e774aaf35046a5b3c4eec050d482b9f0912707ba645e681" + }, + "x-ethers": { + "client": "ethers.js", + "gethFilename": "UTC--2022-08-25T14-48-23.0Z--5baa8472c470a400f830e2458ddb97b13cc8eb32", + "mnemonicCounter": "b84bf04ecd5d0ab111950ee4cf168d86", + "mnemonicCiphertext": "672a53846059b4e8bae97747d684529a", + "path": "m/44'/60'/0'/0/0", + "locale": "en", + "version": "0.1" + } +} diff --git a/gre/test/fixture-projects/graph-config/.accounts/test-account.json b/gre/test/fixture-projects/graph-config/.accounts/test-account.json new file mode 100644 index 000000000..de055e684 --- /dev/null +++ b/gre/test/fixture-projects/graph-config/.accounts/test-account.json @@ -0,0 +1,28 @@ +{ + "address": "c108fda1b5b2903751594298769efd4904b146bd", + "id": "37ec99f7-8244-4982-b2d4-173c244784f3", + "version": 3, + "Crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { "iv": "1eb9d55c0882a50e7988a09e674c2402" }, + "ciphertext": "822fd907f44e48d15d500433200ac244b70487813982936a88c0830fa9cd66b6", + "kdf": "scrypt", + "kdfparams": { + "salt": "f6d158afdf9a11d3353fbe736cbb769626c8428015603c6449ca1fa0b42e3c2e", + "n": 131072, + "dklen": 32, + "p": 1, + "r": 8 + }, + "mac": "1af4526f4e62b6722226ee1c3a18d7f5dfff0d5b7862ca123989e7a464153f28" + }, + "x-ethers": { + "client": "ethers.js", + "gethFilename": "UTC--2022-08-24T12-27-39.0Z--c108fda1b5b2903751594298769efd4904b146bd", + "mnemonicCounter": "3bd3b82c7148351fe0cdc005a631d445", + "mnemonicCiphertext": "f2bc1c5598c60fe265bf7908344fde6d", + "path": "m/44'/60'/0'/0/0", + "locale": "en", + "version": "0.1" + } +} diff --git a/gre/test/fixture-projects/graph-config/hardhat.config.ts b/gre/test/fixture-projects/graph-config/hardhat.config.ts index 1def7b415..bbd6c079c 100644 --- a/gre/test/fixture-projects/graph-config/hardhat.config.ts +++ b/gre/test/fixture-projects/graph-config/hardhat.config.ts @@ -3,6 +3,7 @@ import '../../../gre' module.exports = { paths: { graph: '../../files', + accounts: '.accounts', }, solidity: '0.8.9', defaultNetwork: 'hardhat', diff --git a/gre/type-extensions.d.ts b/gre/type-extensions.d.ts index c20277927..8d5007bad 100644 --- a/gre/type-extensions.d.ts +++ b/gre/type-extensions.d.ts @@ -10,6 +10,14 @@ export interface GraphRuntimeEnvironmentOptions { l1GraphConfig?: string l2GraphConfig?: string graphConfig?: string + enableTxLogging?: boolean + disableSecureAccounts?: boolean + + // These are mostly for testing purposes + l1AccountName?: string + l2AccountName?: string + l1AccountPassword?: string + l2AccountPassword?: string } export type AccountNames = diff --git a/hardhat.config.ts b/hardhat.config.ts index 1a1c2758b..3b989dcd0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -29,7 +29,7 @@ const SKIP_LOAD = process.env.SKIP_LOAD === 'true' function loadTasks() { require('./gre/gre') - ;['contracts', 'misc', 'deployment', 'actions', 'verify', 'e2e'].forEach((folder) => { + ;['contracts', 'bridge', 'deployment', 'actions', 'verify', 'e2e'].forEach((folder) => { const tasksPath = path.join(__dirname, 'tasks', folder) fs.readdirSync(tasksPath) .filter((pth) => pth.includes('.ts')) @@ -107,6 +107,9 @@ function setupNetworkProviders(hardhatConfig) { const DEFAULT_TEST_MNEMONIC = 'myth like bonus scare over problem client lizard pioneer submit female collect' +const DEFAULT_L2_TEST_MNEMONIC = + 'urge never interest human any economy gentle canvas anxiety pave unlock find' + const config: HardhatUserConfig = { paths: { sources: './contracts', @@ -169,7 +172,7 @@ const config: HardhatUserConfig = { localnitrol2: { chainId: 412346, url: 'http://localhost:8547', - accounts: { mnemonic: DEFAULT_TEST_MNEMONIC }, + accounts: { mnemonic: DEFAULT_L2_TEST_MNEMONIC }, graphConfig: 'config/graph.arbitrum-localhost.yml', }, }, diff --git a/package.json b/package.json index 63fab4bd3..ef123981e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@graphprotocol/contracts", - "version": "1.16.0", + "version": "2.0.0", "description": "Contracts for the Graph Protocol", "directories": { "test": "test" @@ -15,7 +15,7 @@ "ethers": "^5.6.0" }, "devDependencies": { - "@arbitrum/sdk": "^3.0.0-beta.6", + "@arbitrum/sdk": "^3.0.0", "@commitlint/cli": "^13.2.1", "@commitlint/config-conventional": "^13.2.0", "@defi-wonderland/smock": "^2.0.7", @@ -61,6 +61,7 @@ "hardhat-abi-exporter": "^2.2.0", "hardhat-contract-sizer": "^2.0.3", "hardhat-gas-reporter": "^1.0.4", + "hardhat-secure-accounts": "0.0.5", "hardhat-storage-layout": "0.1.6", "hardhat-tracer": "^1.0.0-alpha.6", "husky": "^7.0.4", @@ -89,7 +90,6 @@ "compile": "hardhat compile", "deploy": "yarn build && yarn predeploy && hardhat migrate", "deploy-localhost": "yarn deploy --force --network localhost --graph-config config/graph.localhost.yml", - "deploy-rinkeby": "yarn deploy --force --network rinkeby --graph-config config/graph.rinkeby.yml", "deploy-goerli": "yarn deploy --force --network goerli --graph-config config/graph.goerli.yml", "deploy-arbitrum-goerli": "yarn deploy --force --network arbitrum-goerli --graph-config config/graph.arbitrum-goerli.yml", "predeploy": "scripts/predeploy", diff --git a/scripts/e2e b/scripts/e2e index c5008f25e..70aae7e63 100755 --- a/scripts/e2e +++ b/scripts/e2e @@ -3,22 +3,168 @@ set -eo pipefail source $(pwd)/scripts/evm +### > SCRIPT CONFIG < # Allow overriding config -GRAPH_CONFIG=${GRAPH_CONFIG:-"config/graph.localhost.yml"} -ADDRESS_BOOK=${ADDRESS_BOOK:-"addresses.json"} -NETWORK=${NETWORK:-"localhost"} +ADDRESS_BOOK=${ADDRESS_BOOK:-"addresses-local.json"} +ARBITRUM_ADDRESS_BOOK=${ARBITRUM_ADDRESS_BOOK:-"arbitrum-addresses-local.json"} +ARBITRUM_DEPLOYMENT_FILE=${ARBITRUM_DEPLOYMENT_FILE:-"localNetwork.json"} + +L1_NETWORK=${L1_NETWORK} +L2_NETWORK=${L2_NETWORK} + +L1_GRAPH_CONFIG=${L1_GRAPH_CONFIG:-"config/graph.localhost.yml"} +L2_GRAPH_CONFIG=${L2_GRAPH_CONFIG:-"config/graph.arbitrum-localhost.yml"} echo "Running e2e tests" -echo "- Using config: $GRAPH_CONFIG" echo "- Using address book: $ADDRESS_BOOK" -echo "- Using network: $NETWORK" -### Setup +if [[ -n "$L1_NETWORK" ]]; then + echo "- Using L1 network: $L1_NETWORK" + echo "- Using L1 config: $L1_GRAPH_CONFIG" +else + echo "- No L1_NETWORK provided, skipping L1 tests" +fi + +if [[ -n "$L2_NETWORK" ]]; then + echo "- Using L2 network: $L2_NETWORK" + echo "- Using L2 config: $L2_GRAPH_CONFIG" + echo "- Using arbitrum address book: $ARBITRUM_ADDRESS_BOOK" + echo "- Using arbitrum deployment file: $ARBITRUM_DEPLOYMENT_FILE" +else + echo "- No L2_NETWORK provided, skipping L2 tests" +fi + +if [[ -z "$L1_NETWORK" ]] && [[ -z "$L2_NETWORK" ]]; then + echo "Must specify one of L1_NETWORK or L2_NETWORK!" + exit 0 +fi + +if [[ "$L1_NETWORK" == "$L2_NETWORK" ]]; then + echo "L1_NETWORK and L2_NETWORK must be different networks!" + exit 0 +fi + +### > SCRIPT AUX FUNCTIONS < +function pre_deploy() { + local NETWORK=$1 + local GRAPH_CONFIG=$2 + + # Create named accounts + npx hardhat migrate:accounts --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --disable-secure-accounts + + # Fund accounts if using nitro test nodes + if [[ "$NETWORK" == *"localnitro"* ]]; then + npx hardhat nitro:fund-accounts --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --disable-secure-accounts + fi +} + +function deploy() { + local NETWORK=$1 + local GRAPH_CONFIG=$2 + local ADDRESS_BOOK=$3 + + # Deploy protocol + npx hardhat migrate \ + --network "$NETWORK" \ + --skip-confirmation \ + --auto-mine \ + --force \ + --graph-config "$GRAPH_CONFIG" \ + --address-book "$ADDRESS_BOOK" +} + +function post_deploy () { + local NETWORK=$1 + local GRAPH_CONFIG=$2 + local ADDRESS_BOOK=$3 + + # Governor to accept contracts ownership + npx hardhat migrate:ownership --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts + + # Unpause the protocol + npx hardhat migrate:unpause:protocol --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts +} + +function configure_bridge () { + local L1_NETWORK=$1 + local L1_GRAPH_CONFIG=$2 + local L2_NETWORK=$3 + local L2_GRAPH_CONFIG=$4 + local ADDRESS_BOOK=$5 + local ARBITRUM_ADDRESS_BOOK=$6 + local ARBITRUM_DEPLOYMENT_FILE=$7 + + # These settings are only used for CLI bridge commands + # so we keep them here to avoid confusion with hardhat based tasks + local L1_CHAIN_ID=${L1_CHAIN_ID:-"1337"} + local L2_CHAIN_ID=${L2_CHAIN_ID:-"412346"} + + local L1_RPC=${L1_RPC:-"http://localhost:8545"} + local L2_RPC=${L2_RPC:-"http://localhost:8547"} + + local L1_MNEMONIC=${L1_MNEMONIC:-"myth like bonus scare over problem client lizard pioneer submit female collect"} + local L2_MNEMONIC=${L2_MNEMONIC:-"urge never interest human any economy gentle canvas anxiety pave unlock find"} + + # Copy required arbitrum contract addresses to the local arbitrum address book + if [[ "$L1_NETWORK" == *"localnitro"* ]]; then + npx hardhat nitro:address-book-setup --deployment-file "$ARBITRUM_DEPLOYMENT_FILE" --arbitrum-address-book "$ARBITRUM_ADDRESS_BOOK" + fi + + # Configure the bridge + ./cli/cli.ts -a "$ADDRESS_BOOK" -p "$L2_RPC" -m "$L2_MNEMONIC" -n 2 -r "$ARBITRUM_ADDRESS_BOOK" protocol configure-l2-bridge "$L1_CHAIN_ID" + ./cli/cli.ts -a "$ADDRESS_BOOK" -p "$L1_RPC" -m "$L1_MNEMONIC" -n 2 -r "$ARBITRUM_ADDRESS_BOOK" protocol configure-l1-bridge "$L2_CHAIN_ID" + + # Unpause the bridge + npx hardhat migrate:unpause:bridge --network "$L2_NETWORK" --graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts + npx hardhat migrate:unpause:bridge --network "$L1_NETWORK" --graph-config "$L1_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts +} + +function test_e2e () { + local NETWORK=$1 + local L1_GRAPH_CONFIG=$2 + local L2_GRAPH_CONFIG=$3 + local ADDRESS_BOOK=$4 + local SKIP_BRIDGE_TESTS=$5 + + if [[ -z "$SKIP_BRIDGE_TESTS" ]]; then + npx hardhat e2e --network "$NETWORK" --l1-graph-config "$L1_GRAPH_CONFIG" --l2-graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" + else + npx hardhat e2e --network "$NETWORK" --l1-graph-config "$L1_GRAPH_CONFIG" --l2-graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --skip-bridge + fi +} + +function test_e2e_scenarios () { + local NETWORK=$1 + local L1_GRAPH_CONFIG=$2 + local L2_GRAPH_CONFIG=$3 + local ADDRESS_BOOK=$4 + + npx hardhat e2e:scenario create-subgraphs --network "$NETWORK" --l1-graph-config "$L1_GRAPH_CONFIG" --l2-graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts + npx hardhat e2e:scenario open-allocations --network "$NETWORK" --l1-graph-config "$L1_GRAPH_CONFIG" --l2-graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts + + # skip close-allocations for arbitrum testnodes as we can't advance epoch + if [[ "$NETWORK" != *"localnitro"* ]]; then + npx hardhat e2e:scenario close-allocations --network "$NETWORK" --l1-graph-config "$L1_GRAPH_CONFIG" --l2-graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts + fi +} + +function test_e2e_scenarios_bridge () { + local NETWORK=$1 + local L1_GRAPH_CONFIG=$2 + local L2_GRAPH_CONFIG=$3 + local ADDRESS_BOOK=$4 + + npx hardhat e2e:scenario send-grt-to-l2 --network "$NETWORK" --l1-graph-config "$L1_GRAPH_CONFIG" --l2-graph-config "$L2_GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" --disable-secure-accounts +} + + +### > SCRIPT START < ### +## SETUP # Compile contracts yarn build # Start evm -if [[ "$NETWORK" == "localhost" ]]; then +if [[ "$L1_NETWORK" == "localhost" || "$L2_NETWORK" == "localhost" ]]; then evm_kill evm_start fi @@ -28,42 +174,58 @@ if [[ ! -f "$ADDRESS_BOOK" ]]; then echo '{}' > "$ADDRESS_BOOK" fi -# Pre-deploy actions -npx hardhat migrate:accounts --network "$NETWORK" --graph-config "$GRAPH_CONFIG" -if [[ "$NETWORK" == *"localnitro"* ]]; then - npx hardhat migrate:accounts:nitro --network "$NETWORK" --graph-config "$GRAPH_CONFIG" +# Reset arbitrum address book (just in case the deployment changed) +if [[ -f "$ARBITRUM_ADDRESS_BOOK" ]]; then + rm "$ARBITRUM_ADDRESS_BOOK" +fi +echo '{}' > "$ARBITRUM_ADDRESS_BOOK" + +# Reset arbitrum address book (just in case the deployment changed) +if [[ -f "$ARBITRUM_DEPLOYMENT_FILE" ]]; then + rm "$ARBITRUM_DEPLOYMENT_FILE" +fi + +## DEPLOY +# Deploy L1 +if [[ -n "$L1_NETWORK" ]]; then + echo "Deploying L1 protocol" + pre_deploy "$L1_NETWORK" "$L1_GRAPH_CONFIG" + deploy "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$ADDRESS_BOOK" + post_deploy "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$ADDRESS_BOOK" +fi + +# Deploy L2 +if [[ -n "$L2_NETWORK" ]]; then + echo "Deploying L2 protocol" + pre_deploy "$L2_NETWORK" "$L2_GRAPH_CONFIG" + deploy "$L2_NETWORK" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" + post_deploy "$L2_NETWORK" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" +fi + +# Configure bridge +if [[ -n "$L1_NETWORK" ]] && [[ -n "$L2_NETWORK" ]]; then + configure_bridge "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$L2_NETWORK" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" "$ARBITRUM_ADDRESS_BOOK" "$ARBITRUM_DEPLOYMENT_FILE" fi -# Deploy protocol -npx hardhat migrate \ - --network "$NETWORK" \ - --skip-confirmation \ - --auto-mine \ - --graph-config "$GRAPH_CONFIG" \ - --address-book "$ADDRESS_BOOK" - -# Post deploy actions -npx hardhat migrate:ownership --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" -npx hardhat migrate:unpause --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" - -### Test -# Run tests -npx hardhat e2e --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" - -# Skip GRT scenarios in L2 as we don't have bridged GRT yet -if [[ "$NETWORK" != "localnitrol2" ]]; then - npx hardhat e2e:scenario create-subgraphs --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" - npx hardhat e2e:scenario open-allocations --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" +## TEST +# Run e2e tests +if [[ -z "$L2_NETWORK" ]]; then + test_e2e "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" true +else + test_e2e "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" + test_e2e "$L2_NETWORK" "$L1_GRAPH_CONFIG" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" fi -# skip close-allocations for arbitrum testnodes as we can't advance epoch -if [[ "$NETWORK" != *"localnitro"* ]]; then - npx hardhat e2e:scenario close-allocations --network "$NETWORK" --graph-config "$GRAPH_CONFIG" --address-book "$ADDRESS_BOOK" +# Run scenario tests +test_e2e_scenarios "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" +if [[ -n "$L2_NETWORK" ]]; then + test_e2e_scenarios_bridge "$L1_NETWORK" "$L1_GRAPH_CONFIG" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" + test_e2e_scenarios "$L2_NETWORK" "$L1_GRAPH_CONFIG" "$L2_GRAPH_CONFIG" "$ADDRESS_BOOK" fi -### Cleanup +## Cleanup # Exit error mode so the evm instance always gets killed -if [[ "$NETWORK" == "localhost" ]]; then +if [[ "$L1_NETWORK" == "localhost" || "$L2_NETWORK" == "localhost" ]]; then set +e result=0 diff --git a/scripts/test b/scripts/test index 42dbf7366..c8cc60c4a 100755 --- a/scripts/test +++ b/scripts/test @@ -9,6 +9,14 @@ source $(pwd)/scripts/evm yarn build +### Cleanup +function cleanup() { + if [ "$RUN_EVM" = true ]; then + evm_kill + fi +} +trap cleanup EXIT + # Gas reporter needs to run in its own evm instance if [ "$RUN_EVM" = true ]; then evm_kill @@ -23,14 +31,7 @@ mkdir -p reports # Run using the standalone evm instance npx hardhat test --network hardhat $@ -### Cleanup - -# Exit error mode so the evm instance always gets killed -set +e -result=0 - -if [ "$RUN_EVM" = true ]; then - evm_kill +if [ "$REPORT_GAS" = true ]; then + cat reports/gas-report.log + echo "" # Gas report doesn't have a newline at the end fi - -exit $result diff --git a/tasks/bridge/to-l2.ts b/tasks/bridge/to-l2.ts new file mode 100644 index 000000000..ce12d1a57 --- /dev/null +++ b/tasks/bridge/to-l2.ts @@ -0,0 +1,61 @@ +import { task } from 'hardhat/config' +import { cliOpts } from '../../cli/defaults' +import { sendToL2 } from '../../cli/commands/bridge/to-l2' +import { loadEnv } from '../../cli/env' +import { TASK_NITRO_SETUP_SDK } from '../deployment/nitro' +import { BigNumber } from 'ethers' + +export const TASK_BRIDGE_TO_L2 = 'bridge:send-to-l2' + +task(TASK_BRIDGE_TO_L2, 'Bridge GRT tokens from L1 to L2') + .addParam('amount', 'Amount of tokens to bridge') + .addOptionalParam('sender', 'Address of the sender. L1 deployer if empty.') + .addOptionalParam('recipient', 'Receiving address in L2. Same to L1 address if empty.') + .addOptionalParam('addressBook', cliOpts.addressBook.description) + .addOptionalParam( + 'arbitrumAddressBook', + cliOpts.arbitrumAddressBook.description, + cliOpts.arbitrumAddressBook.default, + ) + .addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description) + .addOptionalParam( + 'deploymentFile', + 'Nitro testnode deployment file. Must specify if using nitro test nodes.', + ) + .setAction(async (taskArgs, hre) => { + console.log('> Sending GRT to L2') + const graph = hre.graph(taskArgs) + + // If local, add nitro test node networks to sdk + if (taskArgs.deploymentFile) { + console.log('> Adding nitro test node network to sdk') + await hre.run(TASK_NITRO_SETUP_SDK, { deploymentFile: taskArgs.deploymentFile }) + } + + // Get the sender, use L1 deployer if not provided + const l1Deployer = await graph.l1.getDeployer() + const sender: string = taskArgs.sender ?? l1Deployer.address + + let wallet = await graph.l1.getWallet(sender) + + if (!wallet) { + throw new Error(`No wallet found for address ${sender}`) + } else { + console.log(`> Using wallet ${wallet.address}`) + wallet = wallet.connect(graph.l1.provider) + } + + // Patch sendToL2 opts + taskArgs.l2Provider = graph.l2.provider + taskArgs.amount = hre.ethers.utils.formatEther(taskArgs.amount) // sendToL2 expects amount in GRT + + // L2 provider gas limit estimation has been hit or miss in CI, 400k should be more than enough + if (process.env.CI) { + taskArgs.maxGas = BigNumber.from('400000') + } + + await sendToL2(await loadEnv(taskArgs, wallet), taskArgs) + + console.log('Done!') + }) diff --git a/tasks/deployment/accounts.ts b/tasks/deployment/accounts.ts index c09ff2a2f..747ae22eb 100644 --- a/tasks/deployment/accounts.ts +++ b/tasks/deployment/accounts.ts @@ -2,9 +2,9 @@ import { task } from 'hardhat/config' import { cliOpts } from '../../cli/defaults' import { updateItemValue, writeConfig } from '../../cli/config' -import { BigNumber, ContractTransaction } from 'ethers' task('migrate:accounts', 'Creates protocol accounts and saves them in graph config') + .addFlag('disableSecureAccounts', 'Disable secure accounts on GRE') .addOptionalParam('graphConfig', cliOpts.graphConfig.description) .setAction(async (taskArgs, hre) => { const { graphConfig, getDeployer } = hre.graph(taskArgs) @@ -39,69 +39,3 @@ task('migrate:accounts', 'Creates protocol accounts and saves them in graph conf writeConfig(taskArgs.graphConfig, graphConfig.toString()) }) - -task('migrate:accounts:nitro', 'Funds protocol accounts on Arbitrum Nitro testnodes') - .addOptionalParam('graphConfig', cliOpts.graphConfig.description) - .addOptionalParam('privateKey', 'The private key for Arbitrum testnode genesis account') - .addOptionalParam('amount', 'The amount to fund each account with') - .setAction(async (taskArgs, hre) => { - // Arbitrum Nitro testnodes have a pre-funded genesis account whose private key is hardcoded here: - // - L1 > https://github.com/OffchainLabs/nitro/blob/01c558c06ad9cbaa083bebe3e51960e195c3fd6b/test-node.bash#L136 - // - L2 > https://github.com/OffchainLabs/nitro/blob/01c558c06ad9cbaa083bebe3e51960e195c3fd6b/testnode-scripts/config.ts#L22 - const genesisAccountPrivateKey = - taskArgs.privateKey ?? 'e887f7d17d07cc7b8004053fb8826f6657084e88904bb61590e498ca04704cf2' - const genesisAccount = new hre.ethers.Wallet(genesisAccountPrivateKey) - - // Get protocol accounts - const { getDeployer, getNamedAccounts, getTestAccounts, provider } = hre.graph(taskArgs) - const deployer = await getDeployer() - const testAccounts = await getTestAccounts() - const namedAccounts = await getNamedAccounts() - const accounts = [ - deployer, - ...testAccounts, - ...Object.keys(namedAccounts).map((k) => namedAccounts[k]), - ] - - // Amount to fund - // - If amount is specified, use that - // - Otherwise, use 95% of genesis account balance with a maximum of 100 Eth - let amount: BigNumber - const maxAmount = hre.ethers.utils.parseEther('100') - const genesisAccountBalance = await provider.getBalance(genesisAccount.address) - - if (taskArgs.amount) { - amount = hre.ethers.BigNumber.from(taskArgs.amount) - } else { - const splitGenesisBalance = genesisAccountBalance.mul(95).div(100).div(accounts.length) - if (splitGenesisBalance.gt(maxAmount)) { - amount = maxAmount - } else { - amount = splitGenesisBalance - } - } - - // Check genesis account balance - const requiredFunds = amount.mul(accounts.length) - if (genesisAccountBalance.lt(requiredFunds)) { - throw new Error('Insufficient funds in genesis account') - } - - // Fund accounts - console.log('> Funding protocol addresses') - console.log(`Genesis account: ${genesisAccount.address}`) - console.log(`Total accounts: ${accounts.length}`) - console.log(`Amount per account: ${hre.ethers.utils.formatEther(amount)}`) - console.log(`Required funds: ${hre.ethers.utils.formatEther(requiredFunds)}`) - - const txs: ContractTransaction[] = [] - for (const account of accounts) { - const tx = await genesisAccount.connect(provider).sendTransaction({ - value: amount, - to: account.address, - }) - txs.push(tx) - } - await Promise.all(txs.map((tx) => tx.wait())) - console.log('Done!') - }) diff --git a/tasks/deployment/nitro.ts b/tasks/deployment/nitro.ts new file mode 100644 index 000000000..8cd3feb0c --- /dev/null +++ b/tasks/deployment/nitro.ts @@ -0,0 +1,141 @@ +import { BigNumber, ContractTransaction } from 'ethers' +import { subtask, task } from 'hardhat/config' +import { cliOpts } from '../../cli/defaults' +import { addCustomNetwork } from '@arbitrum/sdk/dist/lib/dataEntities/networks' +import fs from 'fs' +import { execSync } from 'child_process' + +export const TASK_NITRO_FUND_ACCOUNTS = 'nitro:fund-accounts' +export const TASK_NITRO_SETUP_SDK = 'nitro:sdk-setup' +export const TASK_NITRO_SETUP_ADDRESS_BOOK = 'nitro:address-book-setup' +export const TASK_NITRO_FETCH_DEPLOYMENT_FILE = 'nitro:fetch-deployment-file' + +task(TASK_NITRO_FUND_ACCOUNTS, 'Funds protocol accounts on Arbitrum Nitro testnodes') + .addFlag('disableSecureAccounts', 'Disable secure accounts on GRE') + .addOptionalParam('graphConfig', cliOpts.graphConfig.description) + .addOptionalParam('privateKey', 'The private key for Arbitrum testnode genesis account') + .addOptionalParam('amount', 'The amount to fund each account with') + .setAction(async (taskArgs, hre) => { + // Arbitrum Nitro testnodes have a pre-funded genesis account whose private key is hardcoded here: + // - L1 > https://github.com/OffchainLabs/nitro/blob/01c558c06ad9cbaa083bebe3e51960e195c3fd6b/test-node.bash#L136 + // - L2 > https://github.com/OffchainLabs/nitro/blob/01c558c06ad9cbaa083bebe3e51960e195c3fd6b/testnode-scripts/config.ts#L22 + const genesisAccountPrivateKey = + taskArgs.privateKey ?? 'e887f7d17d07cc7b8004053fb8826f6657084e88904bb61590e498ca04704cf2' + const genesisAccount = new hre.ethers.Wallet(genesisAccountPrivateKey) + + // Get protocol accounts + const { getDeployer, getNamedAccounts, getTestAccounts, provider } = hre.graph(taskArgs) + const deployer = await getDeployer() + const testAccounts = await getTestAccounts() + const namedAccounts = await getNamedAccounts() + const accounts = [ + deployer, + ...testAccounts, + ...Object.keys(namedAccounts).map((k) => namedAccounts[k]), + ] + + // Amount to fund + // - If amount is specified, use that + // - Otherwise, use 95% of genesis account balance with a maximum of 100 Eth + let amount: BigNumber + const maxAmount = hre.ethers.utils.parseEther('100') + const genesisAccountBalance = await provider.getBalance(genesisAccount.address) + + if (taskArgs.amount) { + amount = hre.ethers.BigNumber.from(taskArgs.amount) + } else { + const splitGenesisBalance = genesisAccountBalance.mul(95).div(100).div(accounts.length) + if (splitGenesisBalance.gt(maxAmount)) { + amount = maxAmount + } else { + amount = splitGenesisBalance + } + } + + // Check genesis account balance + const requiredFunds = amount.mul(accounts.length) + if (genesisAccountBalance.lt(requiredFunds)) { + throw new Error('Insufficient funds in genesis account') + } + + // Fund accounts + console.log('> Funding protocol addresses') + console.log(`Genesis account: ${genesisAccount.address}`) + console.log(`Total accounts: ${accounts.length}`) + console.log(`Amount per account: ${hre.ethers.utils.formatEther(amount)}`) + console.log(`Required funds: ${hre.ethers.utils.formatEther(requiredFunds)}`) + + const txs: ContractTransaction[] = [] + for (const account of accounts) { + const tx = await genesisAccount.connect(provider).sendTransaction({ + value: amount, + to: account.address, + }) + txs.push(tx) + } + await Promise.all(txs.map((tx) => tx.wait())) + console.log('Done!') + }) + +// Arbitrum SDK does not support Nitro testnodes out of the box +// This adds the testnodes to the SDK configuration +subtask(TASK_NITRO_SETUP_SDK, 'Adds nitro testnodes to SDK config') + .addParam('deploymentFile', 'The testnode deployment file to use', 'localNetwork.json') + .setAction(async (taskArgs) => { + if (!fs.existsSync(taskArgs.deploymentFile)) { + throw new Error(`Deployment file not found: ${taskArgs.deploymentFile}`) + } + const deployment = JSON.parse(fs.readFileSync(taskArgs.deploymentFile, 'utf-8')) + addCustomNetwork({ + customL1Network: deployment.l1Network, + customL2Network: deployment.l2Network, + }) + }) + +subtask(TASK_NITRO_FETCH_DEPLOYMENT_FILE, 'Fetches nitro deployment file from a local testnode') + .addParam( + 'deploymentFile', + 'Path to the file where to deployment file will be saved', + 'localNetwork.json', + ) + .setAction(async (taskArgs) => { + console.log(`Attempting to fetch deployment file from testnode...`) + + const command = `docker exec $(docker ps -qf "name=sequencer") cat /workspace/localNetwork.json > ${taskArgs.deploymentFile}` + const stdOut = execSync(command) + console.log(stdOut.toString()) + + if (!fs.existsSync(taskArgs.deploymentFile)) { + throw new Error(`Unable to fetch deployment file: ${taskArgs.deploymentFile}`) + } + console.log(`Deployment file saved to ${taskArgs.deploymentFile}`) + }) + +// Read arbitrum contract addresses from deployment file and write them to the address book +task(TASK_NITRO_SETUP_ADDRESS_BOOK, 'Write arbitrum addresses to address book') + .addParam('deploymentFile', 'The testnode deployment file to use') + .addParam('arbitrumAddressBook', 'Arbitrum address book file') + .setAction(async (taskArgs, hre) => { + if (!fs.existsSync(taskArgs.deploymentFile)) { + await hre.run(TASK_NITRO_FETCH_DEPLOYMENT_FILE, taskArgs) + } + const deployment = JSON.parse(fs.readFileSync(taskArgs.deploymentFile, 'utf-8')) + + const addressBook = { + '1337': { + L1GatewayRouter: { + address: deployment.l2Network.tokenBridge.l1GatewayRouter, + }, + IInbox: { + address: deployment.l2Network.ethBridge.inbox, + }, + }, + '412346': { + L2GatewayRouter: { + address: deployment.l2Network.tokenBridge.l2GatewayRouter, + }, + }, + } + + fs.writeFileSync(taskArgs.arbitrumAddressBook, JSON.stringify(addressBook)) + }) diff --git a/tasks/deployment/ownership.ts b/tasks/deployment/ownership.ts index 86591c974..9f8e318fc 100644 --- a/tasks/deployment/ownership.ts +++ b/tasks/deployment/ownership.ts @@ -1,8 +1,10 @@ -import { ContractTransaction } from 'ethers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { Contract, ContractTransaction, ethers } from 'ethers' import { task } from 'hardhat/config' import { cliOpts } from '../../cli/defaults' task('migrate:ownership', 'Accepts ownership of protocol contracts on behalf of governor') + .addFlag('disableSecureAccounts', 'Disable secure accounts on GRE') .addOptionalParam('addressBook', cliOpts.addressBook.description) .addOptionalParam('graphConfig', cliOpts.graphConfig.description) .setAction(async (taskArgs, hre) => { @@ -13,12 +15,36 @@ task('migrate:ownership', 'Accepts ownership of protocol contracts on behalf of console.log('> Accepting ownership of contracts') console.log(`- Governor: ${governor.address}`) + const governedContracts = [GraphToken, Controller, GraphProxyAdmin, SubgraphNFT] const txs: ContractTransaction[] = [] - txs.push(await GraphToken.connect(governor).acceptOwnership()) - txs.push(await Controller.connect(governor).acceptOwnership()) - txs.push(await GraphProxyAdmin.connect(governor).acceptOwnership()) - txs.push(await SubgraphNFT.connect(governor).acceptOwnership()) + for (const contract of governedContracts) { + const tx = await acceptOwnershipIfPending(contract, governor) + if (tx) { + txs.push() + } + } await Promise.all(txs.map((tx) => tx.wait())) console.log('Done!') }) + +async function acceptOwnershipIfPending( + contract: Contract, + signer: SignerWithAddress, +): Promise { + const pendingGovernor = await contract.connect(signer).pendingGovernor() + + if (pendingGovernor === ethers.constants.AddressZero) { + console.log(`No pending governor for ${contract.address}`) + return + } + + if (pendingGovernor === signer.address) { + console.log(`Accepting ownership of ${contract.address}`) + return contract.connect(signer).acceptOwnership() + } else { + console.log( + `Signer ${signer.address} is not the pending governor of ${contract.address}, it is ${pendingGovernor}`, + ) + } +} diff --git a/tasks/deployment/unpause.ts b/tasks/deployment/unpause.ts index afb85e0ea..b7bc29b5d 100644 --- a/tasks/deployment/unpause.ts +++ b/tasks/deployment/unpause.ts @@ -1,8 +1,9 @@ import { task } from 'hardhat/config' import { cliOpts } from '../../cli/defaults' -import GraphChain from '../../gre/helpers/network' +import GraphChain from '../../gre/helpers/chain' -task('migrate:unpause', 'Unpause protocol (except bridge)') +task('migrate:unpause:protocol', 'Unpause protocol (except bridge)') + .addFlag('disableSecureAccounts', 'Disable secure accounts on GRE') .addOptionalParam('addressBook', cliOpts.addressBook.description) .addOptionalParam('graphConfig', cliOpts.graphConfig.description) .setAction(async (taskArgs, hre) => { @@ -17,7 +18,8 @@ task('migrate:unpause', 'Unpause protocol (except bridge)') console.log('Done!') }) -task('migrate:unpause-bridge', 'Unpause bridge') +task('migrate:unpause:bridge', 'Unpause bridge') + .addFlag('disableSecureAccounts', 'Disable secure accounts on GRE') .addOptionalParam('addressBook', cliOpts.addressBook.description) .addOptionalParam('graphConfig', cliOpts.graphConfig.description) .setAction(async (taskArgs, hre) => { @@ -29,8 +31,8 @@ task('migrate:unpause-bridge', 'Unpause bridge') const GraphTokenGateway = GraphChain.isL2(graph.chainId) ? L2GraphTokenGateway : L1GraphTokenGateway - const tx2 = await GraphTokenGateway.connect(governor).setPaused(false) - await tx2.wait() + const tx = await GraphTokenGateway.connect(governor).setPaused(false) + await tx.wait() console.log('Done!') }) diff --git a/tasks/e2e/e2e.ts b/tasks/e2e/e2e.ts index d2ab8f96e..aa712380c 100644 --- a/tasks/e2e/e2e.ts +++ b/tasks/e2e/e2e.ts @@ -4,7 +4,7 @@ import { TASK_TEST } from 'hardhat/builtin-tasks/task-names' import glob from 'glob' import { cliOpts } from '../../cli/defaults' import fs from 'fs' -import { isL1 } from '../../gre/helpers/network' +import { isL1 } from '../../gre/helpers/chain' import { runScriptWithHardhat } from 'hardhat/internal/util/scripts-runner' const CONFIG_TESTS = 'e2e/deployment/config/**/*.test.ts' @@ -13,7 +13,13 @@ const INIT_TESTS = 'e2e/deployment/init/**/*.test.ts' // Built-in test & run tasks don't support GRE arguments // so we pass them by overriding GRE config object const setGraphConfig = async (args: TaskArguments, hre: HardhatRuntimeEnvironment) => { - const greArgs = ['graphConfig', 'l1GraphConfig', 'l2GraphConfig', 'addressBook'] + const greArgs = [ + 'graphConfig', + 'l1GraphConfig', + 'l2GraphConfig', + 'addressBook', + 'disableSecureAccounts', + ] for (const arg of greArgs) { if (args[arg]) { @@ -29,13 +35,23 @@ const setGraphConfig = async (args: TaskArguments, hre: HardhatRuntimeEnvironmen task('e2e', 'Run all e2e tests') .addOptionalParam('graphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description) .addOptionalParam('addressBook', cliOpts.addressBook.description) + .addFlag('skipBridge', 'Skip bridge tests') .setAction(async (args, hre: HardhatRuntimeEnvironment) => { - const testFiles = [ + let testFiles = [ ...new glob.GlobSync(CONFIG_TESTS).found, ...new glob.GlobSync(INIT_TESTS).found, ] + if (args.skipBridge) { + testFiles = testFiles.filter((file) => !['l1', 'l2'].includes(file.split('/')[3])) + } + + // Disable secure accounts, we don't need them for this task + hre.config.graph.disableSecureAccounts = true + setGraphConfig(args, hre) await hre.run(TASK_TEST, { testFiles: testFiles, @@ -44,9 +60,15 @@ task('e2e', 'Run all e2e tests') task('e2e:config', 'Run deployment configuration e2e tests') .addOptionalParam('graphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description) .addOptionalParam('addressBook', cliOpts.addressBook.description) .setAction(async (args, hre: HardhatRuntimeEnvironment) => { const files = new glob.GlobSync(CONFIG_TESTS).found + + // Disable secure accounts, we don't need them for this task + hre.config.graph.disableSecureAccounts = true + setGraphConfig(args, hre) await hre.run(TASK_TEST, { testFiles: files, @@ -55,9 +77,15 @@ task('e2e:config', 'Run deployment configuration e2e tests') task('e2e:init', 'Run deployment initialization e2e tests') .addOptionalParam('graphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description) .addOptionalParam('addressBook', cliOpts.addressBook.description) .setAction(async (args, hre: HardhatRuntimeEnvironment) => { const files = new glob.GlobSync(INIT_TESTS).found + + // Disable secure accounts, we don't need them for this task + hre.config.graph.disableSecureAccounts = true + setGraphConfig(args, hre) await hre.run(TASK_TEST, { testFiles: files, @@ -66,8 +94,11 @@ task('e2e:init', 'Run deployment initialization e2e tests') task('e2e:scenario', 'Run scenario scripts and e2e tests') .addPositionalParam('scenario', 'Name of the scenario to run') - .addOptionalParam('graphConfig', cliOpts.graphConfig.description) + .addFlag('disableSecureAccounts', 'Disable secure accounts on GRE') .addOptionalParam('addressBook', cliOpts.addressBook.description) + .addOptionalParam('graphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l1GraphConfig', cliOpts.graphConfig.description) + .addOptionalParam('l2GraphConfig', cliOpts.graphConfig.description) .addFlag('skipScript', "Don't run scenario script") .setAction(async (args, hre: HardhatRuntimeEnvironment) => { setGraphConfig(args, hre) @@ -82,8 +113,11 @@ task('e2e:scenario', 'Run scenario scripts and e2e tests') if (!args.skipScript) { if (fs.existsSync(script)) { await runScriptWithHardhat(hre.hardhatArguments, script, [ - args.graphConfig, args.addressBook, + args.graphConfig, + args.l1GraphConfig, + args.l2GraphConfig, + args.disableSecureAccounts, ]) } else { console.log(`No script found for scenario ${args.scenario}`) diff --git a/tasks/misc/accounts.ts b/tasks/misc/accounts.ts deleted file mode 100644 index 5bb6512df..000000000 --- a/tasks/misc/accounts.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { task } from 'hardhat/config' -import { HardhatRuntimeEnvironment } from 'hardhat/types' -import '@nomiclabs/hardhat-ethers' - -task('accounts', 'Prints the list of accounts', async (_, hre: HardhatRuntimeEnvironment) => { - const accounts = await hre.ethers.getSigners() - for (const account of accounts) { - console.log(await account.getAddress()) - } -}) diff --git a/yarn.lock b/yarn.lock index 7b859117b..42084d9cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,21 +2,15 @@ # yarn lockfile v1 -"@arbitrum/sdk@^3.0.0-beta.6": - version "3.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.0.0-beta.6.tgz#a36c3e39a7358396b5533f3288125107da6ae59e" - integrity sha512-kPCfgj72MeyVcIXQKoztLO29UTcpSbXFzc/S0oDgVNNcHcXp1hWUJqqkVRg0O43P2yKjZRT/I94K0Nj2nZNiiQ== +"@arbitrum/sdk@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.0.0.tgz#a5dc48a00cb8c6e230a2c696c0e880a7f80c637d" + integrity sha512-Mws5WAxxirp3vk8JH3vyQ5H6q1NNUIAAGEd9oEnQYDMyTBHLKU293GA3s9w4w6ZfIq/RZq8YCexhy4D1R+mQng== dependencies: "@ethersproject/address" "^5.0.8" "@ethersproject/bignumber" "^5.1.1" "@ethersproject/bytes" "^5.0.8" - "@typechain/ethers-v5" "9.0.0" - "@types/prompts" "^2.0.14" - "@types/yargs" "^17.0.9" - dotenv "^10.0.0" ethers "^5.1.0" - ts-node "^10.2.1" - typechain "7.0.0" "@babel/code-frame@7.12.11": version "7.12.11" @@ -191,18 +185,6 @@ dependencies: chalk "^4.0.0" -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1252,14 +1234,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== -"@typechain/ethers-v5@9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-9.0.0.tgz#6aa93bea7425c0463bd8a61eea3643540ef851bd" - integrity sha512-bAanuPl1L2itaUdMvor/QvwnIH+TM/CmG00q17Ilv3ZZMeJ2j8HcarhgJUZ9pBY1teBb85P8cC03dz3mSSx+tQ== - dependencies: - lodash "^4.17.15" - ts-essentials "^7.0.1" - "@typechain/ethers-v5@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz#cd3ca1590240d587ca301f4c029b67bfccd08810" @@ -1470,13 +1444,6 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA== -"@types/prompts@^2.0.14": - version "2.0.14" - resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.0.14.tgz#10cb8899844bb0771cabe57c1becaaaca9a3b521" - integrity sha512-HZBd99fKxRWpYCErtm2/yxUZv6/PBI9J7N4TNFffl5JbrYMHBwF25DjQGTW3b3jmXq+9P6/8fCIb2ee57BFfYA== - dependencies: - "@types/node" "*" - "@types/qs@^6.2.31": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -1560,13 +1527,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^17.0.9": - version "17.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" - integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== - dependencies: - "@types/yargs-parser" "*" - "@typescript-eslint/eslint-plugin@^4.0.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" @@ -1988,16 +1948,6 @@ array-back@^2.0.0: dependencies: typical "^2.6.1" -array-back@^3.0.1, array-back@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" - integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== - -array-back@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" - integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -3541,26 +3491,6 @@ command-line-args@^4.0.7: find-replace "^1.0.3" typical "^2.6.1" -command-line-args@^5.1.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" - integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== - dependencies: - array-back "^3.1.0" - find-replace "^3.0.0" - lodash.camelcase "^4.3.0" - typical "^4.0.0" - -command-line-usage@^6.1.0: - version "6.1.2" - resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.2.tgz#2b7ccd48a93fb19bd71ca8fe9900feab00e557b0" - integrity sha512-I+0XN613reAhpBQ6icsPOTwu9cvhc9NtLtUcY2fGYuwm9JZiWBzFDA8w0PHqQjru7Xth7fM/y9TJ13+VKdjh7Q== - dependencies: - array-back "^4.0.1" - chalk "^2.4.2" - table-layout "^1.0.1" - typical "^5.2.0" - commander@2.18.0: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" @@ -3884,7 +3814,7 @@ debug@3.2.6: dependencies: ms "^2.1.1" -debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.3: +debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3959,11 +3889,6 @@ deep-equal@~1.1.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-extend@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4166,11 +4091,6 @@ dotenv@*: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== - dotenv@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" @@ -5465,13 +5385,6 @@ find-replace@^1.0.3: array-back "^1.0.4" test-value "^2.1.0" -find-replace@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" - integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== - dependencies: - array-back "^3.0.1" - find-up@3.0.0, find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -6153,6 +6066,16 @@ hardhat-gas-reporter@^1.0.4: eth-gas-reporter "^0.2.24" sha1 "^1.1.1" +hardhat-secure-accounts@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/hardhat-secure-accounts/-/hardhat-secure-accounts-0.0.5.tgz#753889ad43ae1bfa2df6839ffc556ed3a25d9668" + integrity sha512-ma/UOYV8fROMucLifflUEvYdtchcK4JB2tCV6etAg8PB66OlBo7MwmofnWnN4ABMR8Qt7zGgedFBkGdmBrmxRA== + dependencies: + debug "^4.3.4" + enquirer "^2.3.6" + lodash.clonedeep "^4.5.0" + prompt-sync "^4.2.0" + hardhat-storage-layout@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/hardhat-storage-layout/-/hardhat-storage-layout-0.1.6.tgz#b6ae33d4c00f385dbc1ff67c86d67b0198cfbd91" @@ -7892,10 +7815,10 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== lodash.get@^4: version "4.4.2" @@ -9684,6 +9607,13 @@ promise@^8.0.0: dependencies: asap "~2.0.6" +prompt-sync@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/prompt-sync/-/prompt-sync-4.2.0.tgz#0198f73c5b70e3b03e4b9033a50540a7c9a1d7f4" + integrity sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw== + dependencies: + strip-ansi "^5.0.0" + proper-lockfile@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" @@ -10055,11 +9985,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -reduce-flatten@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" - integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== - regenerate@^1.2.1: version "1.4.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" @@ -11122,11 +11047,6 @@ string-argv@0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-format@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" - integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -11359,16 +11279,6 @@ sync-rpc@^1.2.1: dependencies: get-port "^3.1.0" -table-layout@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" - integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== - dependencies: - array-back "^4.0.1" - deep-extend "~0.6.0" - typical "^5.2.0" - wordwrapjs "^4.0.0" - table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -11622,16 +11532,6 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== -ts-command-line-args@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.2.1.tgz#fd6913e542099012c0ffb2496126a8f38305c7d6" - integrity sha512-mnK68QA86FYzQYTSA/rxIjT/8EpKsvQw9QkawPic8I8t0gjAOw3Oa509NIRoaY1FmH7hdrncMp7t7o+vYoceNQ== - dependencies: - chalk "^4.1.0" - command-line-args "^5.1.1" - command-line-usage "^6.1.0" - string-format "^2.0.0" - ts-essentials@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" @@ -11662,25 +11562,6 @@ ts-generator@^0.1.1: resolve "^1.8.1" ts-essentials "^1.0.0" -ts-node@^10.2.1: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" - yn "3.1.1" - ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -11823,22 +11704,6 @@ type@^2.5.0: resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f" integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== -typechain@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-7.0.0.tgz#258ca136de1d451368bde01c318976a83062f110" - integrity sha512-ILfvBBFJ7j9aIk0crX03+N2GmzoDN1gtk32G1+XrasjuvXS0XAw2XxwQeQMMgKwlnxViJjIkG87sTMYXPkXA9g== - dependencies: - "@types/prettier" "^2.1.1" - debug "^4.1.1" - fs-extra "^7.0.0" - glob "^7.1.6" - js-sha3 "^0.8.0" - lodash "^4.17.15" - mkdirp "^1.0.4" - prettier "^2.1.2" - ts-command-line-args "^2.2.0" - ts-essentials "^7.0.1" - typechain@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/typechain/-/typechain-3.0.0.tgz#d5a47700831f238e43f7429b987b4bb54849b92e" @@ -11912,16 +11777,6 @@ typical@^2.6.0, typical@^2.6.1: resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg== -typical@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" - integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== - -typical@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" - integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== - uglify-js@^3.1.4: version "3.15.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.3.tgz#9aa82ca22419ba4c0137642ba0df800cb06e0471" @@ -12136,11 +11991,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -12864,14 +12714,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -wordwrapjs@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" - integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== - dependencies: - reduce-flatten "^2.0.0" - typical "^5.2.0" - workerpool@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"