├── .circleci └── config.yml ├── .eslintrc.json ├── .github └── pull_request_template.md ├── .gitignore ├── .mocharc.json ├── .prettierrc ├── LICENSE ├── README.md ├── RELEASE.md ├── package-lock.json ├── package.json ├── scripts ├── npm-maybe-publish.sh └── test-keep-alive.sh ├── src ├── cheats.ts ├── constants.ts ├── devnet-provider.ts ├── devnet.ts ├── index.ts ├── postman.ts ├── rpc-provider.ts ├── types.ts ├── util.ts └── version-handler.ts ├── test ├── account-impersonation.test.ts ├── constants.ts ├── data │ ├── L1L2Example.json │ ├── L1L2Example.sol │ ├── MockStarknetMessaging.json │ ├── MockStarknetMessaging.sol │ ├── l1_l2.cairo │ ├── l1_l2.sierra │ ├── simple.cairo │ └── simple.sierra ├── devnet-persistence.test.ts ├── devnet-provider.test.ts ├── devnet-spawn.test.ts ├── gas.test.ts ├── l1-l2-postman.test.ts ├── util.test.ts └── util.ts ├── tsconfig.build.json └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/configuration-reference 3 | version: 2.1 4 | 5 | # Define a job to be invoked later in a workflow. 6 | # See: https://circleci.com/docs/configuration-reference/#jobs 7 | jobs: 8 | build-and-test: 9 | machine: 10 | image: ubuntu-2204:2024.01.2 11 | resource_class: xlarge 12 | environment: 13 | DEVNET_VERSION: "0.4.2" 14 | DEVNET_DIR: "/tmp/devnet-ci-storage" 15 | # to avoid definition complications, when updating DEVNET_DIR, always update DEVNET_PATH (should be prefix) 16 | DEVNET_PATH: "/tmp/devnet-ci-storage/starknet-devnet" 17 | FORKED_DEVNET_PORT: 5051 18 | steps: 19 | - checkout 20 | - run: 21 | name: Versions 22 | command: | 23 | echo "node: $(node --version)" 24 | echo "npm: $(npm --version)" 25 | - run: 26 | name: Install 27 | command: npm ci 28 | - run: 29 | name: Build 30 | command: npm run build 31 | - run: 32 | name: Format 33 | command: npm run format-check 34 | - run: 35 | name: Lint 36 | command: npm run lint-check 37 | - run: 38 | name: Check all tests executed 39 | command: npm run check-all-tests-executed 40 | - run: 41 | name: Spawn Devnet 42 | command: | 43 | docker run -d --network host --name devnet \ 44 | shardlabs/starknet-devnet-rs:${DEVNET_VERSION} --state-archive-capacity full --dump-on request 45 | - run: 46 | name: Spawn Devnet forked from Mainnet 47 | command: | 48 | docker run -d --network host --name forked-devnet \ 49 | shardlabs/starknet-devnet-rs:${DEVNET_VERSION} --port ${FORKED_DEVNET_PORT} --fork-network http://rpc.pathfinder.equilibrium.co/mainnet/rpc/v0_8 50 | - run: 51 | name: Download precompiled Devnet 52 | command: | 53 | URL=https://github.com/0xSpaceShard/starknet-devnet/releases/download/v${DEVNET_VERSION}/starknet-devnet-x86_64-unknown-linux-gnu.tar.gz 54 | mkdir -p ${DEVNET_DIR} 55 | curl -sSfL ${URL} | tar -xvz -C ${DEVNET_DIR} 56 | - run: 57 | name: Spawn Anvil 58 | command: | 59 | docker run -d --network host --name anvil \ 60 | ghcr.io/foundry-rs/foundry:nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a anvil 61 | - run: 62 | name: Test 63 | command: npm run test 64 | - run: 65 | name: Test keepAlive 66 | command: ./scripts/test-keep-alive.sh 67 | maybe-publish: 68 | docker: 69 | - image: cimg/node:20.10.0 70 | steps: 71 | - checkout 72 | - run: 73 | name: Publish a new version if conditions met 74 | command: ./scripts/npm-maybe-publish.sh 75 | 76 | # Orchestrate jobs using workflows 77 | # See: https://circleci.com/docs/configuration-reference/#workflows 78 | workflows: 79 | build-test-maybe-publish: 80 | jobs: 81 | - build-and-test 82 | - maybe-publish: 83 | context: spaceshard 84 | filters: 85 | branches: 86 | only: 87 | - master 88 | requires: 89 | - build-and-test 90 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "rules": { 14 | "array-bracket-spacing": ["error", "never"], 15 | "block-spacing": ["error", "always"], 16 | "brace-style": ["error", "1tbs"], 17 | "comma-dangle": ["error", "always-multiline"], 18 | "comma-spacing": [ 19 | "error", 20 | { 21 | "before": false, 22 | "after": true 23 | } 24 | ], 25 | "eol-last": ["error", "always"], 26 | "keyword-spacing": [ 27 | "error", 28 | { 29 | "overrides": { 30 | "this": { 31 | "before": false, 32 | "after": false 33 | } 34 | }, 35 | "before": true, 36 | "after": true 37 | } 38 | ], 39 | "key-spacing": [ 40 | "error", 41 | { 42 | "beforeColon": false, 43 | "afterColon": true 44 | } 45 | ], 46 | "linebreak-style": ["error", "unix"], 47 | "no-trailing-spaces": ["error"], 48 | "no-var": ["error"], 49 | "no-warning-comments": ["error"], 50 | "object-curly-spacing": ["error", "always"], 51 | "quotes": ["error", "double"], 52 | "semi": ["error", "always"], 53 | "semi-spacing": [ 54 | "error", 55 | { 56 | "before": false, 57 | "after": true 58 | } 59 | ], 60 | "space-before-blocks": ["error", "always"], 61 | "space-in-parens": ["error", "never"], 62 | "switch-colon-spacing": [ 63 | "error", 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "template-curly-spacing": ["error", "never"], 70 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 71 | "@typescript-eslint/no-empty-interface": [ 72 | "error", 73 | { 74 | "allowSingleExtends": true 75 | } 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Usage related changes 2 | 3 | 4 | 5 | ## Development related changes 6 | 7 | 8 | 9 | ## Checklist: 10 | 11 | - [ ] Applied formatting - `npm run format` 12 | - [ ] No linter errors - `npm run lint` 13 | - [ ] Performed code self-review 14 | - [ ] Rebased to the latest commit of the target branch (or merged it into my branch) 15 | - Once you make the PR reviewable, please avoid force-pushing 16 | - [ ] Updated the docs if needed 17 | - [ ] Linked the [issues](https://github.com/0xSpaceShard/starknet-devnet-js/issues) resolvable by this PR - [linking info](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) 18 | - [ ] Updated the tests if needed; all passing - `npm test` 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .eslintcache 4 | 5 | # testing artifacts 6 | *.dump.json 7 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "ts-node/register", 3 | "spec": "test/**/*.test.ts" 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 4, 4 | "trailingComma": "all" 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SpaceShard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm package](https://img.shields.io/npm/v/starknet-devnet?color=blue)](https://www.npmjs.com/package/starknet-devnet) 2 | 3 | # Introduction 4 | 5 | Using this JavaScript/TypeScript library, you can spawn [Starknet Devnet](https://github.com/0xSpaceShard/starknet-devnet/) without installing and running it in a separate terminal. You can interact with it via its specific [Devnet API](https://0xspaceshard.github.io/starknet-devnet/docs/api#devnet-api). To interact with any Starknet node or network (including Starknet Devnet) via the [Starknet JSON-RPC API](https://0xspaceshard.github.io/starknet-devnet/docs/api#starknet-api), see [starknet.js](https://www.starknetjs.com/). 6 | 7 | # Installation 8 | 9 | ``` 10 | npm i starknet-devnet 11 | ``` 12 | 13 | # Compatibility 14 | 15 | ## Devnet compatibility 16 | 17 | This library version is compatible with Devnet `v0.4.2`. 18 | 19 | [Devnet's balance checking functionality](https://0xspaceshard.github.io/starknet-devnet/docs/balance#check-balance) is not provided in this library because it is simply replaceable using starknet.js, as witnessed by the [getAccountBalance](./test/util.ts#L61) function. 20 | 21 | ## Environment compatibility 22 | 23 | This library is intended for use with Node.js, not in a browser environment. In browsers, you can only use [`DevnetProvider`](#connect-to-a-running-instance) for connecting to an already running Devnet instance, but you cannot [spawn a new Devnet](#spawn-a-new-devnet), because that relies on modules not present in the browser engine. 24 | 25 | To enable the use of `DevnetProvider` in browser, you need to configure sources for modules otherwise reported as not found. See [this issue](https://github.com/0xSpaceShard/starknet-devnet-js/issues/26) and [this SO answer](https://stackoverflow.com/a/51669301) for more info, but generally, if using webpack, it should be enough to populate a config file with the desired polyfill implementations or with `false` values. 26 | 27 | # Usage 28 | 29 | ## Spawn a new Devnet 30 | 31 | This library allows you to spawn a Devnet instance inside your program, without a separate terminal. It finds a random free port, and releases all used resources on exit. You can specify a port of your choice via `args: ["--port", ...]`. 32 | 33 | ### Spawn a version without manual installation 34 | 35 | Assuming your machine has a supported OS (macOS or Linux) and supported architecture (arm64/aarch64 or x64/x86_64), using `Devnet.spawnVersion` will quickly install and spawn a new Devnet. 36 | 37 | ```typescript 38 | import { Devnet } from "starknet-devnet"; 39 | 40 | async function main() { 41 | // Specify anything from https://github.com/0xSpaceShard/starknet-devnet/releases 42 | // Be sure to include the 'v' if it's in the version name. 43 | const devnet = await Devnet.spawnVersion("v0.4.2"); 44 | console.log(await devnet.provider.isAlive()); // true 45 | } 46 | ``` 47 | 48 | To use the latest compatible version: 49 | 50 | ```typescript 51 | const devnet = await Devnet.spawnVersion("latest"); 52 | ``` 53 | 54 | ### Spawn an already installed Devnet 55 | 56 | Assuming you have already installed Devnet and it is present in your environment's `PATH`, simply run: 57 | 58 | ```typescript 59 | const devnet = await Devnet.spawnInstalled(); 60 | ``` 61 | 62 | ### Specify Devnet arguments 63 | 64 | You can use the same CLI arguments you would pass to a Devnet running in a terminal: 65 | 66 | ```typescript 67 | const devnet = await Devnet.spawnInstalled({ args: ["--predeployed-accounts", "3"] }); 68 | ``` 69 | 70 | ### Devnet output 71 | 72 | By default, the spawned Devnet inherits the output streams of the main program in which it is invoked. If you invoke your program in a terminal without any stream redirections, it will print Devnet logs in that same terminal together with your program output. This can be overriden: 73 | 74 | ```typescript 75 | const outputStream = fs.createWriteStream("devnet-out.txt"); 76 | await events.once(outputStream, "open"); // necessary if specifying a --port, otherwise omissible 77 | const devnet = await Devnet.spawnInstalled({ 78 | stdout: outputStream, 79 | stderr: /* what you will, could be the same as stdout */, 80 | }); 81 | // do stuff with devnet and then close the stream 82 | outputStream.end(); 83 | ``` 84 | 85 | To track the output in a separate terminal, open a new terminal and run: 86 | 87 | ``` 88 | tail -f devnet-out.txt 89 | ``` 90 | 91 | To ignore the output completely, specify `{ stdout: "ignore", stderr: "ignore" }`. 92 | 93 | ### Spawn a custom build 94 | 95 | If you have a custom build of Devnet or have multiple custom versions present locally: 96 | 97 | ```typescript 98 | // provide the command 99 | const devnet = await Devnet.spawnCommand("my-devnet-command", { ... }); 100 | // or specify the path 101 | const devnet = await Devnet.spawnCommand("/path/to/my-devnet-command", { ... }); 102 | ``` 103 | 104 | ### Killing 105 | 106 | By default, the Devnet subprocess automatically exits and releases the used resources on program end, but you can send it a signal if needed: 107 | 108 | ```typescript 109 | const devnet = await Devnet.spawnInstalled(); 110 | devnet.kill(...); // defaults to SIGTERM 111 | ``` 112 | 113 | ### Keeping alive 114 | 115 | To keep the spawned Devnet alive after your program exits, set the `keepAlive` flag: 116 | 117 | ```typescript 118 | const devnet = await Devnet.spawnInstalled({ keepAlive: true }); 119 | ``` 120 | 121 | In that case, you must take care of the spawned process after the program exits. To kill it, you need to: 122 | 123 | 1. Know the port it is using. It is logged on Devnet startup and is also a part of `devnet.provider.url`. 124 | 2. Kill the process using the port, which you can do: 125 | 126 | a. In JS, by relying on the [cross-port-killer library](https://www.npmjs.com/package/cross-port-killer). 127 | 128 | b. From shell, by executing `lsof -i : | awk 'NR==2{print $2}' | xargs kill` (substitute `` with yours). 129 | 130 | ## Connect to a running instance 131 | 132 | If there already is a running Devnet instance (e.g. in another terminal or in another JS/TS program), you can simply connect to it by importing `DevnetProvider`. [Read more](https://0xspaceshard.github.io/starknet-devnet/docs/category/running) about different ways of running Devnet. 133 | 134 | ```typescript 135 | import { DevnetProvider } from "starknet-devnet"; 136 | const devnet = new DevnetProvider(); // accepts an optional configuration object 137 | console.log(await devnet.isAlive()); // true 138 | ``` 139 | 140 | ## Enabling Starknet API support 141 | 142 | Since this library only supports the [Devnet-specific API](https://0xspaceshard.github.io/starknet-devnet/docs/api#devnet-api), to interact via [Starknet JSON-RPC API](https://0xspaceshard.github.io/starknet-devnet/docs/api#starknet-api), use [starknet.js](https://www.starknetjs.com/). 143 | 144 | E.g. to get the latest block after spawning Devnet, you would need to do: 145 | 146 | ```typescript 147 | import { Devnet } from "starknet-devnet"; 148 | import * as starknet from "starknet"; 149 | 150 | const devnet = await Devnet.spawnInstalled(); 151 | const starknetProvider = new starknet.RpcProvider({ nodeUrl: devnet.provider.url }); 152 | 153 | const block = await starknetProvider.getBlock("latest"); 154 | ``` 155 | 156 | ## L1-L2 communication 157 | 158 | Assuming there is an L1 provider running (e.g. [anvil](https://github.com/foundry-rs/foundry/tree/master/crates/anvil)), use the `postman` property of `DevnetProvider` to achieve [L1-L2 communication](https://0xspaceshard.github.io/starknet-devnet/docs/postman). See [this example](https://github.com/0xSpaceShard/starknet-devnet-js/blob/master/test/l1-l2-postman.test.ts) for more info. 159 | 160 | ## Configuration modification and retrieval 161 | 162 | Devnet's configuration can be modified, other than [on startup (as already described)](#specify-devnet-arguments), via `setGasPrice`. It can be retrieved via `getConfig`. 163 | 164 | ```typescript 165 | const devnet = await Devnet.spawnInstalled({ args: ["--l2-gas-price-fri", ...] }); 166 | const modification = await devnet.provider.setGasPrice({ l2GasPrice: ... }); 167 | console.log(await devnet.provider.getConfig().l2_gas_price_fri); 168 | ``` 169 | 170 | ## Examples 171 | 172 | See the [`test` directory](https://github.com/0xSpaceShard/starknet-devnet-js/tree/master/test) for more usage examples. 173 | 174 | ## Contribute 175 | 176 | If you spot a problem or room for improvement, check if an issue for it [already exists](https://github.com/0xSpaceShard/starknet-devnet-js/issues). If not, [create a new one](https://github.com/0xSpaceShard/starknet-devnet-js/issues/new). You are welcome to open a PR yourself to close the issue. Once you open a PR, you will see a template with a list of steps - please follow them. 177 | 178 | ### Test 179 | 180 | Before running the tests with `npm test`, follow the steps defined in the [CI/CD config file](.circleci/config.yml). If your new test relies on environment variables, load them with `getEnvVar`. Conversely, to find all environment variables that need to be set before running existing tests, search the repo for `getEnvVar`. 181 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | The release of a new version is done automatically if the version in `package.json` on `master` is different from the one in npm. If the semver you use is not of the form `/v?[0-9.]+$/` (notice the optional `v`), a pre-release will be made using dist-tag `beta`. Otherwise a dist-tag `latest` is used. 2 | 3 | When adapting to a new starknet-devnet version, be sure to have replaced all occurrences of the previous version with the new one. 4 | 5 | Simply follow these steps: 6 | 7 | 1. `$ git checkout master` 8 | 9 | - Using another branch is only acceptable if making a pre-release, but then the publishing script needs to be run manually (or the CI config file needs to be modified to include your branch). 10 | 11 | 2. `$ npm version ` 12 | 13 | - This creates a commit and a tag for `` 14 | - See what `` can be [on this page](https://docs.npmjs.com/cli/v8/commands/npm-version). 15 | 16 | 3. `$ git push` 17 | 18 | - Once the change is pushed, the CI/CD pipeline will release the new version when all tests pass. 19 | 20 | 4. `$ git push origin v` 21 | 22 | - Notice how the tag name has the `v` prefix. 23 | 24 | Avoid the automatic release process by adding `[skip ci]` to your commit message. 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starknet-devnet", 3 | "version": "0.4.2", 4 | "description": "Starknet Devnet provider", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "build": "tsc -p tsconfig.build.json", 12 | "build-check": "tsc -p tsconfig.build.json --noEmit", 13 | "test": "mocha -r ts-node/register test/**/*.ts", 14 | "pretest": "npm run build", 15 | "format": "prettier --log-level log --write \"**/*.{ts,js,md,yml,json}\"", 16 | "format-check": "prettier --log-level log --check \"**/*.{ts,js,md,yml,json}\"", 17 | "lint": "eslint $(git ls-files '*.ts') --cache --fix", 18 | "lint-check": "eslint $(git ls-files '*.ts') --cache", 19 | "check-all-tests-executed": "! grep '\\(describe\\|it\\)\\.only(' $(git ls-files '*.ts')" 20 | }, 21 | "author": "SpaceShard", 22 | "repository": "github:0xSpaceShard/starknet-devnet-js", 23 | "keywords": [ 24 | "starknet", 25 | "devnet", 26 | "provider", 27 | "starkware", 28 | "l2", 29 | "l1-l2", 30 | "zk", 31 | "json-rpc" 32 | ], 33 | "license": "MIT", 34 | "dependencies": { 35 | "axios": "^1.7.4", 36 | "decompress": "^4.2.1", 37 | "decompress-targz": "^4.1.1" 38 | }, 39 | "devDependencies": { 40 | "@types/chai": "^4.2.22", 41 | "@types/decompress": "^4.2.7", 42 | "@types/mocha": "^9.1.1", 43 | "@types/node": "^20.3.1", 44 | "@types/tmp": "^0.2.6", 45 | "@typescript-eslint/eslint-plugin": "^7.13.1", 46 | "chai": "^4.3.7", 47 | "eslint": "^8.57.0", 48 | "ethers": "^6.13.1", 49 | "mocha": "^10.2.0", 50 | "prettier-eslint": "^16.3.0", 51 | "starknet": "~7.1.0", 52 | "tmp": "^0.2.3", 53 | "ts-node": "^10.9.1", 54 | "typescript": "^5.0.4" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripts/npm-maybe-publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | PACKAGE_NAME=$(jq -r ".name" package.json) 6 | LOCAL_VERSION=$(jq -r ".version" package.json) 7 | 8 | # If ordinary semver, publish as the "latest" release; otherwise make it a "beta" pre-release. 9 | # Without this check, it would always default to releasing as "latest". 10 | DIST_TAG=$(echo "$LOCAL_VERSION" | awk '/^v?[0-9.]+$/{ print "latest"; next; } { print "beta" }') 11 | echo "The local version is \"$DIST_TAG\"" 12 | 13 | NPM_VERSION=$(npm view "$PACKAGE_NAME" dist-tags."$DIST_TAG") 14 | 15 | if [ "$LOCAL_VERSION" = "$NPM_VERSION" ]; then 16 | echo "The \"$DIST_TAG\" npm version is equal to the local version: $LOCAL_VERSION. Increment to publish to npm." 17 | else 18 | npm ci 19 | npm run build 20 | 21 | # NPM access token: https://docs.npmjs.com/creating-and-viewing-access-tokens 22 | # If running locally, you can use `npm login` and comment out this step. 23 | npm config set //registry.npmjs.org/:_authToken=${NPM_TOKEN} 24 | 25 | npm publish --verbose --access=public --tag="$DIST_TAG" 26 | fi 27 | -------------------------------------------------------------------------------- /scripts/test-keep-alive.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | PORT=9876 6 | 7 | # This is a test of Devnet-wrapper's keepAlive flag, provided on spawning. 8 | 9 | # To ensure the artifacts expected by `require`. 10 | npm run build 11 | 12 | node -e 'require("./dist/devnet").Devnet.spawnVersion("latest", { args: ["--port", "'${PORT}'"] })' 13 | curl -sSfL localhost:${PORT}/is_alive || echo "Ok... expected to be unresponsive" 14 | 15 | node -e 'require("./dist/devnet").Devnet.spawnVersion("latest", { args: ["--port", "'${PORT}'"], keepAlive: true })' 16 | curl -sSfL localhost:${PORT}/is_alive && echo "Ok... expected to be responsive" 17 | 18 | lsof -i :${PORT} | awk 'NR==2{print $2}' | xargs kill 19 | curl -sSfL localhost:${PORT}/is_alive || echo "Ok... expected to be unresponsive" 20 | -------------------------------------------------------------------------------- /src/cheats.ts: -------------------------------------------------------------------------------- 1 | import { RpcProvider } from "./rpc-provider"; 2 | 3 | export class Cheats { 4 | constructor(private rpcProvider: RpcProvider) {} 5 | 6 | /** 7 | * Deactivate using `stopImpersonateAccount`. 8 | * 9 | * https://0xspaceshard.github.io/starknet-devnet/docs/account-impersonation 10 | * @param address the address of a locally non-present account that you want to impersonate 11 | */ 12 | public async impersonateAccount(address: string): Promise { 13 | await this.rpcProvider.sendRequest("devnet_impersonateAccount", { 14 | account_address: address, 15 | }); 16 | } 17 | 18 | /** 19 | * https://0xspaceshard.github.io/starknet-devnet/docs/account-impersonation 20 | * @param address the address of a locally non-present account that you want to stop impersonating 21 | */ 22 | public async stopImpersonateAccount(address: string): Promise { 23 | await this.rpcProvider.sendRequest("devnet_stopImpersonateAccount", { 24 | account_address: address, 25 | }); 26 | } 27 | 28 | /** 29 | * Enables automatic account impersonation. Every account that does not exist in the local state will be impersonated. Deactivate using `stopAutoImpersonate`. 30 | * 31 | * https://0xspaceshard.github.io/starknet-devnet/docs/account-impersonation 32 | */ 33 | public async autoImpersonate(): Promise { 34 | await this.rpcProvider.sendRequest("devnet_autoImpersonate"); 35 | } 36 | 37 | /** 38 | * https://0xspaceshard.github.io/starknet-devnet/docs/account-impersonation 39 | */ 40 | public async stopAutoImpersonate(): Promise { 41 | await this.rpcProvider.sendRequest("devnet_stopAutoImpersonate"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** milliseconds */ 2 | export const DEFAULT_HTTP_TIMEOUT = 30_000; 3 | 4 | export const DEFAULT_DEVNET_HOST = "127.0.0.1"; 5 | export const DEFAULT_DEVNET_PORT = 5050; 6 | export const DEFAULT_DEVNET_URL = `http://${DEFAULT_DEVNET_HOST}:${DEFAULT_DEVNET_PORT}`; 7 | 8 | export const LATEST_COMPATIBLE_DEVNET_VERSION = "v0.4.2"; 9 | -------------------------------------------------------------------------------- /src/devnet-provider.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import { Postman } from "./postman"; 3 | import { Cheats } from "./cheats"; 4 | import { RpcProvider } from "./rpc-provider"; 5 | import { BalanceUnit, BlockId, PredeployedAccount, toRpcBlockId } from "./types"; 6 | import { DEFAULT_DEVNET_URL, DEFAULT_HTTP_TIMEOUT } from "./constants"; 7 | 8 | export type DevnetProviderConfig = { 9 | url?: string; 10 | /** milliseconds */ 11 | timeout?: number; 12 | }; 13 | 14 | export type MintResponse = { 15 | new_balance: bigint; 16 | unit: BalanceUnit; 17 | tx_hash: string; 18 | }; 19 | 20 | export interface NewBlockResponse { 21 | block_hash: string; 22 | } 23 | 24 | export interface AbortedBlocksResponse { 25 | aborted: Array; 26 | } 27 | 28 | export interface SetTimeResponse { 29 | time: number; 30 | block_hash?: string; 31 | } 32 | 33 | export interface IncreaseTimeResponse { 34 | time: number; 35 | block_hash: string; 36 | } 37 | 38 | export interface GasModificationResponse { 39 | l1_gas_price?: bigint; 40 | l1_data_gas_price?: bigint; 41 | l2_gas_price?: bigint; 42 | } 43 | 44 | export class DevnetProvider { 45 | public readonly url: string; 46 | private httpProvider: AxiosInstance; 47 | private rpcProvider: RpcProvider; 48 | 49 | /** Contains methods for L1-L2 communication. */ 50 | public readonly postman: Postman; 51 | 52 | /** Contains methods for cheating, e.g. account impersonation. */ 53 | public readonly cheats: Cheats; 54 | 55 | public constructor(config?: DevnetProviderConfig) { 56 | this.url = config?.url || DEFAULT_DEVNET_URL; 57 | this.httpProvider = axios.create({ 58 | baseURL: this.url, 59 | timeout: config?.timeout ?? DEFAULT_HTTP_TIMEOUT, 60 | }); 61 | this.rpcProvider = new RpcProvider(this.httpProvider, this.url); 62 | this.postman = new Postman(this.rpcProvider); 63 | this.cheats = new Cheats(this.rpcProvider); 64 | } 65 | 66 | /** 67 | * @returns `true` if the underlying Devnet instance is responsive; `false` otherwise 68 | */ 69 | public async isAlive(): Promise { 70 | return this.httpProvider 71 | .get("/is_alive") 72 | .then((resp) => resp.status === axios.HttpStatusCode.Ok) 73 | .catch(() => false); 74 | } 75 | 76 | /** 77 | * Restart the state of the underlying Devnet instance. You may opt to restart L1-L2 messaging. 78 | * https://0xspaceshard.github.io/starknet-devnet/docs/dump-load-restart#restarting 79 | */ 80 | public async restart(params: { restartL1ToL2Messaging?: boolean } = {}): Promise { 81 | await this.rpcProvider.sendRequest("devnet_restart", { 82 | restart_l1_to_l2_messaging: params.restartL1ToL2Messaging, 83 | }); 84 | } 85 | 86 | /** 87 | * Generate funds at the provided address. For return spec and more info, see 88 | * https://0xspaceshard.github.io/starknet-devnet/docs/balance#mint-token---local-faucet 89 | * @param address the account address to receive funds 90 | * @param amount how much to mint 91 | * @param unit specifier of the currency unit; defaults to FRI 92 | */ 93 | public async mint( 94 | address: string, 95 | amount: bigint, 96 | unit: BalanceUnit = "FRI", 97 | ): Promise { 98 | const paramsSerialized = `{ 99 | "address": "${address}", 100 | "amount": ${amount}, 101 | "unit": "${unit}" 102 | }`; 103 | const respData = await this.rpcProvider.sendRequest("devnet_mint", paramsSerialized); 104 | 105 | return { 106 | new_balance: BigInt(respData.new_balance), 107 | unit: respData.unit, 108 | tx_hash: respData.tx_hash, 109 | }; 110 | } 111 | 112 | /** 113 | * https://0xspaceshard.github.io/starknet-devnet/docs/predeployed#how-to-get-predeployment-info 114 | * @returns a list of containing information on predeployed accounts. Load an account using e.g. starknet.js. 115 | */ 116 | public async getPredeployedAccounts( 117 | additionalArgs = { withBalance: false }, 118 | ): Promise> { 119 | return await this.rpcProvider.sendRequest("devnet_getPredeployedAccounts", { 120 | with_balance: additionalArgs.withBalance, 121 | }); 122 | } 123 | 124 | /** 125 | * https://0xspaceshard.github.io/starknet-devnet/docs/blocks 126 | * @returns the block hash of the newly created block 127 | */ 128 | public async createBlock(): Promise { 129 | return await this.rpcProvider.sendRequest("devnet_createBlock"); 130 | } 131 | 132 | /** 133 | * https://0xspaceshard.github.io/starknet-devnet/docs/blocks 134 | * @param staringBlockId the block ID of the block after which (inclusive) all blocks 135 | * should be aborted. See docs {@link BlockId} for more info. 136 | * @returns hash values of aborted blocks 137 | */ 138 | public async abortBlocks(startingBlockId: BlockId): Promise { 139 | return await this.rpcProvider.sendRequest("devnet_abortBlocks", { 140 | starting_block_id: toRpcBlockId(startingBlockId), 141 | }); 142 | } 143 | 144 | /** 145 | * https://0xspaceshard.github.io/starknet-devnet/docs/next/starknet-time#set-time 146 | * @returns the new time in unix seconds and, if block creation requested, the hash of the created block 147 | */ 148 | public async setTime( 149 | time: number, 150 | additionalArgs = { generateBlock: false }, 151 | ): Promise { 152 | return await this.rpcProvider.sendRequest("devnet_setTime", { 153 | time, 154 | generate_block: additionalArgs.generateBlock, 155 | }); 156 | } 157 | 158 | /** 159 | * Increase the time by the provided `increment` seconds. 160 | * https://0xspaceshard.github.io/starknet-devnet/docs/next/starknet-time#increase-time 161 | * @returns the new time in unix seconds 162 | */ 163 | public async increaseTime(increment: number): Promise { 164 | return await this.rpcProvider.sendRequest("devnet_increaseTime", { 165 | time: increment, 166 | }); 167 | } 168 | 169 | /** 170 | * https://0xspaceshard.github.io/starknet-devnet/docs/dump-load-restart#dumping 171 | * @param path the path where your Devnet instance will be serialized; if not provided, defaults to the dump-path provided via CLI on Devnet startup. 172 | */ 173 | public async dump(path?: string): Promise { 174 | return await this.rpcProvider.sendRequest("devnet_dump", { path }); 175 | } 176 | 177 | /** 178 | * After loading, this DevnetProvider instance will be connected to the loaded Devnet instance. 179 | * https://0xspaceshard.github.io/starknet-devnet/docs/dump-load-restart#dumping 180 | * @param path the path from which a Devnet instance will be deserialized 181 | */ 182 | public async load(path: string): Promise { 183 | return await this.rpcProvider.sendRequest("devnet_load", { path }); 184 | } 185 | 186 | /** 187 | * Modify gas prices, according to https://0xspaceshard.github.io/starknet-devnet/docs/gas 188 | * @param price new gas prices; any gas price can be ommitted 189 | * @param generateBlock if `true`, a new block is generated immediately, having new gas prices; 190 | * otherwise (by default) the price change takes effect with the usual next block generation 191 | * @returns gas prices after modification, including the unchanged ones 192 | */ 193 | public async setGasPrice( 194 | price: { 195 | l1GasPrice?: bigint; 196 | l1DataGasPrice?: bigint; 197 | l2GasPrice?: bigint; 198 | }, 199 | generateBlock?: boolean, 200 | ): Promise { 201 | const newGasPrices = await this.rpcProvider.sendRequest( 202 | "devnet_setGasPrice", 203 | `{ 204 | "gas_price_fri": ${price.l1GasPrice ?? null}, 205 | "data_gas_price_fri": ${price.l1DataGasPrice ?? null}, 206 | "l2_gas_price_fri": ${price.l2GasPrice ?? null}, 207 | "generate_block": ${generateBlock ?? null} 208 | }`, 209 | ); 210 | 211 | return { 212 | l1_gas_price: BigInt(newGasPrices.gas_price_fri), 213 | l1_data_gas_price: BigInt(newGasPrices.data_gas_price_fri), 214 | l2_gas_price: BigInt(newGasPrices.l2_gas_price_fri), 215 | }; 216 | } 217 | 218 | /** 219 | * More info at: https://0xspaceshard.github.io/starknet-devnet/docs/api#config-api 220 | * @returns the configuration of the underlying Devnet instance. The returned object is marked 221 | * as `any` because it may change too often. 222 | */ 223 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 224 | public async getConfig(): Promise { 225 | return await this.rpcProvider.sendRequest("devnet_getConfig"); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/devnet.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcess, spawn as spawnChildProcess } from "child_process"; 2 | import { DevnetProvider } from "./devnet-provider"; 3 | import { DevnetError } from "./types"; 4 | import { isFreePort, sleep } from "./util"; 5 | import { 6 | DEFAULT_DEVNET_HOST, 7 | DEFAULT_DEVNET_PORT, 8 | LATEST_COMPATIBLE_DEVNET_VERSION, 9 | } from "./constants"; 10 | import { VersionHandler } from "./version-handler"; 11 | import { Stream } from "stream"; 12 | 13 | export type DevnetOutput = "inherit" | "ignore" | Stream | number; 14 | 15 | export interface DevnetConfig { 16 | /** The CLI args you would pass to a Devnet run in terminal. */ 17 | args?: string[]; 18 | stdout?: DevnetOutput; 19 | stderr?: DevnetOutput; 20 | /** The maximum amount of time waited for Devnet to start. Defaults to 5000 ms. */ 21 | maxStartupMillis?: number; 22 | /** 23 | * If `false` (default), automatically closes the spawned Devnet on program exit. 24 | * Otherwise keeps it alive. 25 | */ 26 | keepAlive?: boolean; 27 | } 28 | 29 | /** 30 | * Attempt to extract the URL from the provided Devnet CLI args. If host or present not present, 31 | * populates the received array with default values. The host defaults to 127.0.0.1 and the port 32 | * is randomly assigned. 33 | * @param args CLI args to Devnet 34 | * @returns the URL enabling communication with the Devnet instance 35 | */ 36 | async function ensureUrl(args: string[]): Promise { 37 | let host: string; 38 | const hostParamIndex = args.indexOf("--host"); 39 | if (hostParamIndex === -1) { 40 | host = DEFAULT_DEVNET_HOST; 41 | args.push("--host", host); 42 | } else { 43 | host = args[hostParamIndex + 1]; 44 | } 45 | 46 | let port: string; 47 | const portParamIndex = args.indexOf("--port"); 48 | if (portParamIndex === -1) { 49 | port = await getFreePort(); 50 | args.push("--port", port); 51 | } else { 52 | port = args[portParamIndex + 1]; 53 | } 54 | 55 | return `http://${host}:${port}`; 56 | } 57 | 58 | async function getFreePort(): Promise { 59 | const step = 1000; 60 | const maxPort = 65535; 61 | for (let port = DEFAULT_DEVNET_PORT + step; port <= maxPort; port += step) { 62 | if (await isFreePort(port)) { 63 | return port.toString(); 64 | } 65 | } 66 | 67 | throw new DevnetError("Could not find a free port! Try rerunning your command."); 68 | } 69 | 70 | export class Devnet { 71 | static instances: Devnet[] = []; 72 | 73 | private constructor( 74 | private process: ChildProcess, 75 | public provider: DevnetProvider, 76 | ) {} 77 | 78 | /** 79 | * Assumes `starknet-devnet` is installed and present in the environment PATH and executes it, using the args provided in `config`. 80 | * @param config an object for configuring Devnet 81 | * @returns a newly spawned Devnet instance 82 | */ 83 | static async spawnInstalled(config: DevnetConfig = {}): Promise { 84 | return this.spawnCommand("starknet-devnet", config); 85 | } 86 | 87 | /** 88 | * Spawns a new Devnet using the provided command and optional args in `config`. 89 | * The `command` can be an absolute or a relative path, or a command in your environment's PATH. 90 | * @param command the command used for starting Devnet; can be a path 91 | * @param config configuration object 92 | * @returns a newly spawned Devnet instance 93 | */ 94 | static async spawnCommand(command: string, config: DevnetConfig = {}): Promise { 95 | const args = config.args || []; 96 | const devnetUrl = await ensureUrl(args); 97 | 98 | const devnetProcess = spawnChildProcess(command, args, { 99 | detached: true, 100 | stdio: [undefined, config.stdout || "inherit", config.stderr || "inherit"], 101 | }); 102 | devnetProcess.unref(); 103 | 104 | const devnetInstance = new Devnet(devnetProcess, new DevnetProvider({ url: devnetUrl })); 105 | 106 | if (!config.keepAlive) { 107 | // store it now to ensure it's cleaned up automatically if the remaining steps fail 108 | Devnet.instances.push(devnetInstance); 109 | } 110 | 111 | return new Promise((resolve, reject) => { 112 | const maxStartupMillis = config?.maxStartupMillis ?? 5000; 113 | devnetInstance.ensureAlive(maxStartupMillis).then(() => resolve(devnetInstance)); 114 | 115 | devnetProcess.on("error", function (e) { 116 | reject(e); 117 | }); 118 | 119 | devnetProcess.on("exit", function () { 120 | if (devnetProcess.exitCode) { 121 | reject(`Devnet exited with code ${devnetProcess.exitCode}. \ 122 | Check Devnet's logged output for more info. \ 123 | The output location is configurable via the config object passed to the Devnet spawning method.`); 124 | } 125 | }); 126 | }); 127 | } 128 | 129 | /** 130 | * Spawns a Devnet of the provided `version` using the parameters provided in `config`. 131 | * If not present locally, a precompiled version is fetched, extracted and executed. 132 | * If you already have a local Devnet you would like to run, use {@link spawnCommand}. 133 | * @param version if set to `"latest"`, uses the latest Devnet version compatible with this library; 134 | * otherwise needs to be a semver string with a prepended "v" (e.g. "v1.2.3") and 135 | * should be available in https://github.com/0xSpaceShard/starknet-devnet/releases 136 | * @param config configuration object 137 | * @returns a newly spawned Devnet instance 138 | */ 139 | static async spawnVersion(version: string, config: DevnetConfig = {}): Promise { 140 | version = version === "latest" ? LATEST_COMPATIBLE_DEVNET_VERSION : version; 141 | 142 | const command = await VersionHandler.getExecutable(version); 143 | return this.spawnCommand(command, config); 144 | } 145 | 146 | private async ensureAlive(maxStartupMillis: number): Promise { 147 | const checkPeriod = 100; // ms 148 | const maxIterations = maxStartupMillis / checkPeriod; 149 | 150 | for (let i = 0; !this.process.exitCode && i < maxIterations; ++i) { 151 | if (await this.provider.isAlive()) { 152 | return; 153 | } 154 | await sleep(checkPeriod); 155 | } 156 | 157 | throw new DevnetError( 158 | "Could not spawn Devnet! Ensure that you can spawn using the chosen method. \ 159 | Alternatively, increase the startup time defined in the config object provided on spawning.", 160 | ); 161 | } 162 | 163 | /** 164 | * Sends the provided signal to the underlying Devnet process. Keep in mind 165 | * that the process is killed automatically on program exit. 166 | * @param signal the signal to be sent; deaults to `SIGTERM` 167 | * @returns `true` if successful; `false` otherwise 168 | */ 169 | public kill(signal: NodeJS.Signals = "SIGTERM"): boolean { 170 | return this.process.kill(signal); 171 | } 172 | 173 | static cleanup() { 174 | for (const instance of Devnet.instances) { 175 | if (!instance.process.killed) { 176 | instance.kill(); 177 | } 178 | } 179 | } 180 | } 181 | 182 | for (const event of ["exit", "SIGINT", "SIGTERM", "SIGQUIT", "uncaughtException"]) { 183 | process.on(event, Devnet.cleanup); 184 | } 185 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./devnet"; 2 | export * from "./devnet-provider"; 3 | export * from "./types"; 4 | -------------------------------------------------------------------------------- /src/postman.ts: -------------------------------------------------------------------------------- 1 | import { RpcProvider } from "./rpc-provider"; 2 | import { BigNumberish } from "./types"; 3 | 4 | function numericToHexString(numeric: BigNumberish): string { 5 | return "0x" + BigInt(numeric).toString(16); 6 | } 7 | 8 | export interface L1ToL2Message { 9 | l2_contract_address: string; 10 | entry_point_selector: string; 11 | l1_contract_address: string; 12 | payload: Array; 13 | paid_fee_on_l1: string; 14 | nonce: string; 15 | } 16 | 17 | export interface L2ToL1Message { 18 | from_address: string; 19 | payload: string[]; 20 | to_address: string; 21 | } 22 | 23 | export interface FlushResponse { 24 | messages_to_l1: Array; 25 | messages_to_l2: Array; 26 | generated_l2_transactions: Array; 27 | l1_provider: string; 28 | } 29 | 30 | export interface LoadL1MessagingContractResponse { 31 | messaging_contract_address: string; 32 | } 33 | 34 | export interface L1ToL2MockTxRequest { 35 | l2_contract_address: string; 36 | l1_contract_address: string; 37 | entry_point_selector: string; 38 | payload: Array; 39 | nonce: string; 40 | paidFeeOnL1: string; 41 | } 42 | 43 | export interface L1ToL2MockTxResponse { 44 | transaction_hash: string; 45 | } 46 | 47 | export interface L2ToL1MockTxRequest { 48 | l2_contract_address: string; 49 | l1_contract_address: string; 50 | payload: Array; 51 | } 52 | 53 | export interface L2ToL1MockTxResponse { 54 | message_hash: string; 55 | } 56 | 57 | /** 58 | * https://0xspaceshard.github.io/starknet-devnet/docs/postman 59 | */ 60 | export class Postman { 61 | public constructor(private rpcProvider: RpcProvider) {} 62 | 63 | /** 64 | * https://0xspaceshard.github.io/starknet-devnet/docs/postman#flush 65 | */ 66 | public async flush(additionalArgs = { dryRun: false }): Promise { 67 | return this.rpcProvider.sendRequest("devnet_postmanFlush", { 68 | dry_run: additionalArgs.dryRun, 69 | }); 70 | } 71 | 72 | /** 73 | * If `address` specified, tries to load an L1 messaging contract from that address. 74 | * If `address` omitted, deploys a new messaging contract by relying on the first predeployed 75 | * account of the L1 network specified with `networkUrl`, assuming default mnemonic seed. 76 | * If this predeployed account assumption does not hold, you should specify the private key 77 | * of the account to be used in `deployerAccountPrivateKey`. 78 | * More info in: https://0xspaceshard.github.io/starknet-devnet/docs/postman#load 79 | */ 80 | public async loadL1MessagingContract( 81 | networkUrl: string, 82 | messagingContractAddress?: string, 83 | deployerAccountPrivateKey?: string, 84 | ): Promise { 85 | if (!!messagingContractAddress && !!deployerAccountPrivateKey) { 86 | throw new Error( 87 | "Both parameters cannot be specified simulatenously: `address`, `deployer_account_private_key`", 88 | ); 89 | } 90 | return await this.rpcProvider.sendRequest("devnet_postmanLoad", { 91 | messaging_contract_address: messagingContractAddress, 92 | network_url: networkUrl, 93 | deployer_account_private_key: deployerAccountPrivateKey, 94 | }); 95 | } 96 | 97 | /** 98 | * https://0xspaceshard.github.io/starknet-devnet/docs/postman#mock-transactions 99 | */ 100 | public async sendMessageToL2( 101 | l2ContractAddress: string, 102 | entryPointSelector: string, 103 | l1ContractAddress: string, 104 | payload: BigNumberish[], 105 | nonce: BigNumberish, 106 | paidFeeOnL1: BigNumberish, 107 | ): Promise { 108 | return await this.rpcProvider.sendRequest("devnet_postmanSendMessageToL2", { 109 | l2_contract_address: l2ContractAddress, 110 | entry_point_selector: entryPointSelector, 111 | l1_contract_address: l1ContractAddress, 112 | payload: payload.map(numericToHexString), 113 | nonce: numericToHexString(nonce), 114 | paid_fee_on_l1: numericToHexString(paidFeeOnL1), 115 | }); 116 | } 117 | 118 | /** 119 | * https://0xspaceshard.github.io/starknet-devnet/docs/postman#l2-l1 120 | */ 121 | public async consumeMessageFromL2( 122 | fromAddress: string, 123 | toAddress: string, 124 | payload: BigNumberish[], 125 | ): Promise { 126 | return await this.rpcProvider.sendRequest("devnet_postmanConsumeMessageFromL2", { 127 | from_address: fromAddress, 128 | to_address: toAddress, 129 | payload: payload.map(numericToHexString), 130 | }); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/rpc-provider.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError, AxiosInstance } from "axios"; 2 | import { DevnetProviderError } from "./types"; 3 | 4 | export class RpcProvider { 5 | public constructor( 6 | private httpProvider: AxiosInstance, 7 | private url: string, 8 | ) {} 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | public async sendRequest(method: string, params: unknown = {}): Promise { 12 | // providing a string may be of use in case a bigint needs to be preserved 13 | const paramsSerialized = typeof params === "string" ? params : JSON.stringify(params); 14 | 15 | const jsonRpcBodySerialized = `{ 16 | "jsonrpc": "2.0", 17 | "id": "1", 18 | "method": "${method}", 19 | "params": ${paramsSerialized} 20 | }`; 21 | 22 | return this.httpProvider 23 | .post(this.url, jsonRpcBodySerialized, { 24 | headers: { 25 | "Content-Type": "application/json", 26 | }, 27 | }) 28 | .then((resp) => { 29 | if ("result" in resp.data) { 30 | return resp.data["result"]; 31 | } else if ("error" in resp.data) { 32 | // not wrapping in new Error to avoid printing as [Object object] 33 | throw resp.data["error"]; 34 | } else { 35 | throw resp.data; 36 | } 37 | }) 38 | .catch((err) => { 39 | if (err.code === AxiosError.ECONNABORTED) { 40 | throw DevnetProviderError.fromAxiosError(err); 41 | } 42 | throw err; 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError } from "axios"; 2 | 3 | export type BigNumberish = string | number | bigint; 4 | 5 | export type BalanceUnit = "WEI" | "FRI"; 6 | 7 | export interface PredeployedAccount { 8 | balance: { 9 | [key: string]: { amount: string; unit: BalanceUnit }; 10 | }; 11 | initial_balance: string; 12 | private_key: string; 13 | public_key: string; 14 | address: string; 15 | } 16 | 17 | export class DevnetProviderError extends Error { 18 | constructor(msg: string) { 19 | super(msg); 20 | 21 | // Set the prototype explicitly. 22 | Object.setPrototypeOf(this, DevnetProviderError.prototype); 23 | } 24 | 25 | public static fromAxiosError(err: AxiosError) { 26 | if (err.code === AxiosError.ECONNABORTED) { 27 | return new this( 28 | `${err.message}. Try specifying a greater timeout in DevnetProvider({...})`, 29 | ); 30 | } 31 | 32 | throw new Error(`Cannot create a DevnetProviderError from ${err}`); 33 | } 34 | } 35 | 36 | export class DevnetError extends Error { 37 | constructor(msg: string) { 38 | super(msg); 39 | 40 | // Set the prototype explicitly. 41 | Object.setPrototypeOf(this, DevnetError.prototype); 42 | } 43 | } 44 | 45 | export class GithubError extends Error { 46 | constructor(msg: string) { 47 | super(`Unexpected response from GitHub: ${msg}`); 48 | 49 | // Set the prototype explicitly. 50 | Object.setPrototypeOf(this, DevnetError.prototype); 51 | } 52 | } 53 | 54 | export type BlockTag = "latest" | "pending"; 55 | 56 | /** 57 | * If string of value "latest" or "pending", interpreted as block tag. 58 | * If number, interpreted as block number. 59 | * If hex string, interpreted as block hash. 60 | */ 61 | export type BlockId = BlockTag | number | string; 62 | 63 | export function toRpcBlockId(blockId: BlockId) { 64 | if (blockId === "latest" || blockId === "pending") { 65 | return blockId; 66 | } else if (typeof blockId === "number" && blockId >= 0) { 67 | return { block_number: blockId }; 68 | } else if (typeof blockId === "string" && /0[xX][a-fA-F0-9]+/.test(blockId)) { 69 | return { block_hash: blockId }; 70 | } 71 | 72 | throw new DevnetProviderError(`Invalid block ID: ${blockId}`); 73 | } 74 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import net from "net"; 2 | 3 | export async function sleep(millis: number): Promise { 4 | await new Promise((resolve, _) => setTimeout(resolve, millis)); 5 | } 6 | 7 | export function isFreePort(port: number): Promise { 8 | return new Promise((accept, reject) => { 9 | const sock = net.createConnection(port); 10 | sock.once("connect", () => { 11 | sock.end(); 12 | accept(false); 13 | }); 14 | sock.once("error", (e: NodeJS.ErrnoException) => { 15 | sock.destroy(); 16 | if (e.code === "ECONNREFUSED") { 17 | accept(true); 18 | } else { 19 | reject(e); 20 | } 21 | }); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/version-handler.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import os from "os"; 5 | import { DEFAULT_HTTP_TIMEOUT } from "./constants"; 6 | import { DevnetError, GithubError } from "./types"; 7 | import decompress from "decompress"; 8 | // eslint-disable-next-line @typescript-eslint/no-var-requires 9 | const decompressTargz = require("decompress-targz"); 10 | 11 | export class VersionHandler { 12 | static httpProvider: AxiosInstance = axios.create({ 13 | timeout: DEFAULT_HTTP_TIMEOUT, 14 | }); 15 | 16 | static localStoragePath: string = path.join(os.tmpdir(), "devnet-versions"); 17 | 18 | private static getVersionDir(version: string): string { 19 | return path.join(this.localStoragePath, version); 20 | } 21 | 22 | private static getExecutablePath(versionDir: string): string { 23 | return path.join(versionDir, "starknet-devnet"); 24 | } 25 | 26 | /** 27 | * Ensure that the command corresponding to the provided `version` exists. 28 | * @param version semver string with a prepended "v"; 29 | * should be available in https://github.com/0xSpaceShard/starknet-devnet/releases 30 | * @returns the path to the executable corresponding to the version 31 | */ 32 | static async getExecutable(version: string): Promise { 33 | const versionDir = this.getVersionDir(version); 34 | const executable = this.getExecutablePath(versionDir); 35 | if (fs.existsSync(executable)) { 36 | return executable; 37 | } 38 | 39 | const executableUrl = await this.getArchivedExecutableUrl(version); 40 | const archivePath = await this.fetchArchivedExecutable(executableUrl, versionDir); 41 | await this.extract(archivePath, versionDir); 42 | return executable; 43 | } 44 | 45 | private static getCompatibleArch(): string { 46 | switch (process.arch) { 47 | case "arm64": 48 | return "aarch64"; 49 | case "x64": 50 | return "x86_64"; 51 | default: 52 | throw new DevnetError(`Incompatible architecture: ${process.platform}`); 53 | } 54 | } 55 | 56 | private static getCompatiblePlatform(): string { 57 | switch (process.platform) { 58 | case "linux": 59 | return "unknown-linux-gnu"; 60 | case "darwin": 61 | return "apple-darwin"; 62 | default: 63 | throw new DevnetError(`Incompatible platform: ${process.platform}`); 64 | } 65 | } 66 | 67 | private static async getArchivedExecutableUrl(version: string): Promise { 68 | const releasesUrl = "https://api.github.com/repos/0xSpaceShard/starknet-devnet/releases"; 69 | const releasesResp = await this.httpProvider.get(releasesUrl); 70 | 71 | if (releasesResp.status !== axios.HttpStatusCode.Ok) { 72 | throw new GithubError(releasesResp.statusText); 73 | } 74 | 75 | if (!Array.isArray(releasesResp.data)) { 76 | throw new GithubError(`Invalid response: ${JSON.stringify(releasesResp.data)}`); 77 | } 78 | 79 | let versionPresent = false; 80 | for (const release of releasesResp.data) { 81 | if (release.name === version) { 82 | versionPresent = true; 83 | break; 84 | } 85 | } 86 | 87 | if (!versionPresent) { 88 | throw new GithubError( 89 | `Version not found. If specifying an exact version, make sure you prepended the 'v' and that the version really exists in ${releasesUrl}.`, 90 | ); 91 | } 92 | 93 | const arch = this.getCompatibleArch(); 94 | const platform = this.getCompatiblePlatform(); 95 | return `https://github.com/0xSpaceShard/starknet-devnet/releases/download/${version}/starknet-devnet-${arch}-${platform}.tar.gz`; 96 | } 97 | 98 | /** 99 | * @param url the url of the archived executable 100 | * @param versionDir the path to the directory where the archive will be written and extracted 101 | * @returns the path where the archive was stored 102 | */ 103 | private static async fetchArchivedExecutable(url: string, versionDir: string): Promise { 104 | const resp = await this.httpProvider.get(url, { responseType: "stream" }); 105 | if (resp.status === axios.HttpStatusCode.NotFound) { 106 | throw new GithubError(`Not found: ${url}`); 107 | } else if (resp.status !== axios.HttpStatusCode.Ok) { 108 | throw new GithubError(resp.statusText); 109 | } 110 | 111 | if (!fs.existsSync(versionDir)) { 112 | fs.mkdirSync(versionDir, { recursive: true }); 113 | } 114 | 115 | const archivedExecutablePath = path.join(versionDir, "archive.tar.gz"); 116 | const writer = fs.createWriteStream(archivedExecutablePath); 117 | 118 | return new Promise((resolve, reject) => { 119 | resp.data.pipe(writer); 120 | 121 | let error: Error; 122 | writer.on("error", (e) => { 123 | error = e; 124 | writer.close(); 125 | reject(error); 126 | }); 127 | 128 | writer.on("close", () => { 129 | if (!error) { 130 | resolve(archivedExecutablePath); 131 | } 132 | // no need to call `reject` here, as it will have been called in the 133 | // "error" stream; 134 | }); 135 | }); 136 | } 137 | 138 | /** 139 | * Extract the content of the archive. 140 | * @param archivePath the local path of the archive 141 | */ 142 | private static async extract(archivePath: string, targetDir: string): Promise { 143 | await decompress(archivePath, targetDir, { 144 | plugins: [decompressTargz()], 145 | }); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/account-impersonation.test.ts: -------------------------------------------------------------------------------- 1 | import { RpcProvider, Account, Contract, LibraryError, Provider } from "starknet"; 2 | import { DevnetProvider } from ".."; 3 | import { getContractArtifact, getEnvVar, getPredeployedAccount } from "./util"; 4 | import { assert, expect } from "chai"; 5 | import { SIMPLE_CONTRACT_CASM_HASH, SIMPLE_CONTRACT_PATH } from "./constants"; 6 | 7 | describe("Account impersonation", function () { 8 | this.timeout(35_000); // ms 9 | 10 | /** 11 | * Assuming there is a Devnet instance forked from the network where the impersonated account is located. 12 | */ 13 | let devnetProvider: DevnetProvider; 14 | let starknetProvider: Provider; 15 | 16 | /** An actual mainnet account. */ 17 | let impersonatedAccount: Account; 18 | 19 | /** A contract used for testing account interaction with the state.*/ 20 | let contract: Contract; 21 | 22 | before("set up providers and account for impersonation", function () { 23 | const forkedDevnetUrl = `http://localhost:${getEnvVar("FORKED_DEVNET_PORT")}`; 24 | devnetProvider = new DevnetProvider({ url: forkedDevnetUrl }); 25 | starknetProvider = new RpcProvider({ nodeUrl: devnetProvider.url }); 26 | 27 | impersonatedAccount = new Account( 28 | starknetProvider, 29 | "0x0276feffed3bf366a4305f5e32e0ccb08c2da4915d83d81127b5b9d4210a80db", 30 | "0x1", // dummy private key, in impersonation it is not actually used for signing 31 | ); 32 | }); 33 | 34 | before("restart the state", async function () { 35 | await devnetProvider.restart(); 36 | 37 | // Test contract setup (declaration and deployment) is done here using a local account. 38 | // Later, the contract is interacted with using an impersonated account. 39 | const predeployedAccount = await getPredeployedAccount(devnetProvider, starknetProvider); 40 | 41 | const contractArtifact = getContractArtifact(SIMPLE_CONTRACT_PATH); 42 | const contractDeployment = await predeployedAccount.declareAndDeploy({ 43 | contract: contractArtifact, 44 | compiledClassHash: SIMPLE_CONTRACT_CASM_HASH, 45 | constructorCalldata: { initial_balance: 0 }, 46 | }); 47 | contract = new Contract( 48 | contractArtifact.abi, 49 | contractDeployment.deploy.contract_address, 50 | starknetProvider, 51 | ); 52 | contract.connect(impersonatedAccount); 53 | // We are dealing with an actual Mainnet account and its funds might have been used up. 54 | await devnetProvider.mint(impersonatedAccount.address, BigInt(1e18)); 55 | }); 56 | 57 | async function expectValidationFailure(invocation: Promise) { 58 | try { 59 | await invocation; 60 | assert.fail("Invocation should have failed"); 61 | } catch (err) { 62 | const typedErr = err as LibraryError; 63 | expect(typedErr.message).to.contain("Account validation failed"); 64 | } 65 | } 66 | 67 | it("should work for one account", async function () { 68 | const initialBalance = await contract.get_balance(); 69 | const incrementAmount = 100n; 70 | 71 | // Attempt invoking a contract method - should fail since we defined the account using a dummy key. 72 | // The contract was connected to the account during setup. 73 | await expectValidationFailure(contract.increase_balance(incrementAmount, 0)); 74 | 75 | // Configure impersonation and expect transaction success 76 | await devnetProvider.cheats.impersonateAccount(impersonatedAccount.address); 77 | const successReceipt = await starknetProvider.waitForTransaction( 78 | (await contract.increase_balance(incrementAmount, 0)).transaction_hash, 79 | ); 80 | 81 | expect(successReceipt.isSuccess()); 82 | expect(await contract.get_balance()).to.equal(initialBalance + incrementAmount); 83 | 84 | // Revoke impersonation, should fail again 85 | await devnetProvider.cheats.stopImpersonateAccount(impersonatedAccount.address); 86 | await expectValidationFailure(contract.increase_balance(incrementAmount, 0)); 87 | }); 88 | 89 | it("should work for any account", async function () { 90 | const initialBalance = await contract.get_balance(); 91 | const incrementAmount = 100n; 92 | 93 | // Attempt invoking a contract method - should fail since we defined the account using a dummy key. 94 | // The contract was connected to the account during setup. 95 | await expectValidationFailure(contract.increase_balance(incrementAmount, 0)); 96 | 97 | // Configure impersonation and expect transaction success 98 | await devnetProvider.cheats.autoImpersonate(); 99 | const successReceipt = await starknetProvider.waitForTransaction( 100 | (await contract.increase_balance(incrementAmount, 0)).transaction_hash, 101 | ); 102 | 103 | expect(successReceipt.isSuccess()); 104 | expect(await contract.get_balance()).to.equal(initialBalance + incrementAmount); 105 | 106 | // Revoke impersonation, should fail again 107 | await devnetProvider.cheats.stopAutoImpersonate(); 108 | await expectValidationFailure(contract.increase_balance(incrementAmount, 0)); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/constants.ts: -------------------------------------------------------------------------------- 1 | export const SIMPLE_CONTRACT_PATH = "test/data/simple.sierra"; 2 | export const SIMPLE_CONTRACT_SIERRA_HASH = 3 | "0x02dc84cbdfc63f980cc6f8b9054184d7e33f43fdc02b2baf40017ea88490212d"; 4 | export const SIMPLE_CONTRACT_CASM_HASH = 5 | "0x63b33a5f2f46b1445d04c06d7832c48c48ad087ce0803b71f2b8d96353716ca"; 6 | -------------------------------------------------------------------------------- /test/data/L1L2Example.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0. 2 | pragma solidity ^0.6.12; 3 | 4 | /** 5 | Demo contract for L1 <-> L2 interaction between an L2 StarkNet contract and this L1 solidity 6 | contract. 7 | */ 8 | 9 | import "./MockStarknetMessaging.sol"; 10 | 11 | contract L1L2Example { 12 | // The StarkNet core contract. 13 | MockStarknetMessaging starknetCore; 14 | 15 | mapping(uint256 => uint256) public userBalances; 16 | 17 | uint256 constant MESSAGE_WITHDRAW = 0; 18 | 19 | // The selector of the "deposit" l1_handler. 20 | uint256 constant DEPOSIT_SELECTOR = 21 | 352040181584456735608515580760888541466059565068553383579463728554843487745; 22 | 23 | /** 24 | Initializes the contract state. 25 | */ 26 | constructor(MockStarknetMessaging starknetCore_) public { 27 | starknetCore = starknetCore_; 28 | } 29 | 30 | function get_balance(uint256 user) external view returns (uint256) { 31 | return userBalances[user]; 32 | } 33 | 34 | function withdraw(uint256 l2ContractAddress, uint256 user, uint256 amount) external { 35 | // Construct the withdrawal message's payload. 36 | uint256[] memory payload = new uint256[](3); 37 | payload[0] = MESSAGE_WITHDRAW; 38 | payload[1] = user; 39 | payload[2] = amount; 40 | 41 | // Consume the message from the StarkNet core contract. 42 | // This will revert the (Ethereum) transaction if the message does not exist. 43 | starknetCore.consumeMessageFromL2(l2ContractAddress, payload); 44 | 45 | // Update the L1 balance. 46 | userBalances[user] += amount; 47 | } 48 | 49 | function deposit(uint256 l2ContractAddress, uint256 user, uint256 amount) external payable { 50 | require(amount < 2 ** 64, "Invalid amount."); 51 | require(amount <= userBalances[user], "The user's balance is not large enough."); 52 | 53 | // Update the L1 balance. 54 | userBalances[user] -= amount; 55 | 56 | // Construct the deposit message's payload. 57 | uint256[] memory payload = new uint256[](2); 58 | payload[0] = user; 59 | payload[1] = amount; 60 | 61 | // Send the message to the StarkNet core contract. 62 | starknetCore.sendMessageToL2{value: msg.value}( 63 | l2ContractAddress, 64 | DEPOSIT_SELECTOR, 65 | payload 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/data/MockStarknetMessaging.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0. 2 | pragma solidity ^0.6.12; 3 | 4 | import "./StarknetMessaging.sol"; 5 | 6 | contract MockStarknetMessaging is StarknetMessaging { 7 | constructor(uint256 MessageCancellationDelay) public { 8 | messageCancellationDelay(MessageCancellationDelay); 9 | } 10 | 11 | /** 12 | Mocks a message from L2 to L1. 13 | */ 14 | function mockSendMessageFromL2( 15 | uint256 fromAddress, 16 | uint256 toAddress, 17 | uint256[] calldata payload 18 | ) external { 19 | bytes32 msgHash = keccak256( 20 | abi.encodePacked(fromAddress, toAddress, payload.length, payload) 21 | ); 22 | l2ToL1Messages()[msgHash] += 1; 23 | 24 | // Devnet-specific modification to trigger the event 25 | emit LogMessageToL1(fromAddress, address(toAddress), payload); 26 | } 27 | 28 | /** 29 | Mocks consumption of a message from L2 to L1. 30 | */ 31 | function mockConsumeMessageFromL2( 32 | uint256 fromAddress, 33 | uint256 toAddress, 34 | uint256[] calldata payload 35 | ) external { 36 | bytes32 msgHash = keccak256( 37 | abi.encodePacked(fromAddress, toAddress, payload.length, payload) 38 | ); 39 | 40 | require(l2ToL1Messages()[msgHash] > 0, "INVALID_MESSAGE_TO_CONSUME"); 41 | emit ConsumedMessageToL1(fromAddress, msg.sender, payload); 42 | l2ToL1Messages()[msgHash] -= 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/data/l1_l2.cairo: -------------------------------------------------------------------------------- 1 | //! L1 L2 messaging demo contract. 2 | //! Rewrite in Cairo 1 of the contract from previous Devnet version: 3 | //! https://github.com/0xSpaceShard/starknet-devnet/blob/e477aa1bbe2348ba92af2a69c32d2eef2579d863/test/contracts/cairo/l1l2.cairo 4 | //! 5 | //! This contract does not use interface to keep the code as simple as possible. 6 | //! 7 | 8 | #[starknet::contract] 9 | mod l1_l2 { 10 | const MESSAGE_WITHDRAW: felt252 = 0; 11 | 12 | #[storage] 13 | struct Storage { 14 | // Balances (users) -> (amount). 15 | balances: LegacyMap, 16 | } 17 | 18 | #[event] 19 | #[derive(Drop, starknet::Event)] 20 | enum Event { 21 | DepositFromL1: DepositFromL1, 22 | } 23 | 24 | #[derive(Drop, starknet::Event)] 25 | struct DepositFromL1 { 26 | #[key] 27 | user: felt252, 28 | #[key] 29 | amount: felt252, 30 | } 31 | 32 | /// Gets the balance of the `user`. 33 | #[external(v0)] 34 | fn get_balance(self: @ContractState, user: felt252) -> felt252 { 35 | self.balances.read(user) 36 | } 37 | 38 | /// Increases the balance of the `user` for the `amount`. 39 | #[external(v0)] 40 | fn increase_balance(ref self: ContractState, user: felt252, amount: felt252) { 41 | let balance = self.balances.read(user); 42 | self.balances.write(user, balance + amount); 43 | } 44 | 45 | /// Withdraws the `amount` for the `user` and sends a message to `l1_address` to 46 | /// send the funds. 47 | #[external(v0)] 48 | fn withdraw(ref self: ContractState, user: felt252, amount: felt252, l1_address: felt252) { 49 | assert(amount.is_non_zero(), 'Amount must be positive'); 50 | 51 | let balance = self.balances.read(user); 52 | assert(balance.is_non_zero(), 'Balance is already 0'); 53 | 54 | // We need u256 to make comparisons. 55 | let balance_u: u256 = balance.into(); 56 | let amount_u: u256 = amount.into(); 57 | assert(balance_u >= amount_u, 'Balance will be negative'); 58 | 59 | let new_balance = balance - amount; 60 | 61 | self.balances.write(user, new_balance); 62 | 63 | let payload = array![MESSAGE_WITHDRAW, user, amount,]; 64 | 65 | starknet::send_message_to_l1_syscall(l1_address, payload.span()).unwrap(); 66 | } 67 | 68 | /// Withdraws the `amount` for the `user` and sends a message to `l1_address` to 69 | /// send the funds. 70 | #[external(v0)] 71 | fn withdraw_from_lib( 72 | ref self: ContractState, user: felt252, amount: felt252, l1_address: felt252, message_sender_class_hash: starknet::ClassHash, 73 | ) { 74 | assert(amount.is_non_zero(), 'Amount must be positive'); 75 | 76 | let balance = self.balances.read(user); 77 | assert(balance.is_non_zero(), 'Balance is already 0'); 78 | 79 | // We need u256 to make comparisons. 80 | let balance_u: u256 = balance.into(); 81 | let amount_u: u256 = amount.into(); 82 | assert(balance_u >= amount_u, 'Balance will be negative'); 83 | 84 | let new_balance = balance - amount; 85 | 86 | self.balances.write(user, new_balance); 87 | 88 | let calldata = array![user, amount, l1_address]; 89 | 90 | starknet::SyscallResultTrait::unwrap_syscall( 91 | starknet::library_call_syscall( 92 | message_sender_class_hash, 93 | selector!("send_withdraw_message"), 94 | calldata.span(), 95 | ) 96 | ); 97 | } 98 | 99 | /// Deposits the `amount` for the `user`. Can only be called by the sequencer itself, 100 | /// after having fetched some messages from the L1. 101 | #[l1_handler] 102 | fn deposit(ref self: ContractState, from_address: felt252, user: felt252, amount: felt252) { 103 | // In a real case scenario, here we would assert from_address value 104 | 105 | let balance = self.balances.read(user); 106 | self.balances.write(user, balance + amount); 107 | 108 | self.emit(DepositFromL1 { user, amount }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/data/l1_l2.sierra: -------------------------------------------------------------------------------- 1 | {"sierra_program":["0x1","0x3","0x0","0x2","0x3","0x1","0x194","0x6c","0x2a","0x52616e6765436865636b","0x800000000000000100000000000000000000000000000000","0x66656c74323532","0x800000000000000700000000000000000000000000000000","0x537472756374","0x800000000000000700000000000000000000000000000002","0x0","0x1166fe35572d4e7764dac0caf1fd7fc591901fd01156db2561a07b68ab8dca2","0x1","0x800000000000000700000000000000000000000000000003","0x1f79e461d4110c889d492439b1535f7993951b709cb09e5623a32b680d16623","0x456e756d","0x3f7115e49fa9072c09e2ffaef8654793cf157a4c84bee27bde30f7817de899a","0x3","0x75313238","0x4172726179","0x800000000000000300000000000000000000000000000001","0x800000000000000300000000000000000000000000000003","0x90d0203c41ad646d024845257a6eceb2f8b59b29ce7420dd518053d2edeedc","0x6","0x53746f7261676541646472657373","0x53746f726167654261736541646472657373","0x536e617073686f74","0x800000000000000700000000000000000000000000000001","0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62","0xa","0x161ee0e6962e56453b5d68e09d1cabe5633858c1ba3a7e73fee8c70867eced0","0xb","0x800000000000000f00000000000000000000000000000001","0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3","0x800000000000000f00000000000000000000000000000002","0xd","0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672","0xf","0xcc5e86243f861d2d64b08c35db21013e773ac5cf10097946fe0011304886d5","0xe","0x10","0x101dc0399934cc08fa0d6f6f2daead4e4a38cabeea1c743e1fc28d2d6e58e99","0x25e2ca4b84968c2d8b83ef476ca8549410346b00836ce79beaf538155990bb2","0x5","0x3288d594b9a45d15bb2fcb7903f06cdb06b27f0ba88186ec4cfaa98307cb972","0x4e6f6e5a65726f","0x3f51060ece3ba20644007f2b7041118e06660163080985e5154b817c466bb2e","0x800000000000000f00000000000000000000000000000003","0x16","0x370713a165e5a64af24e5aab2e87f979926db4e2833838543df20814ed75898","0x17","0x426f78","0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7","0x19","0x436c61737348617368","0x11771f2d3e7dc3ed5afe7eae405dfd127619490dec57ceaa021ac8bc2b9b315","0x1b","0x17cd30688a90ae8662ae16f974a089e12270e497ecc8314db219143d003da31","0x1d","0x3c927518e5e3fd4a9fe0cedd1cf3a38563ada2b5953a1c41e2049a0ac793552","0x1e","0x10203be321c62a7bd4c060d69539c1fbe065baa9e253c74d2cc48be163e259","0x21","0x4275696c74696e436f737473","0x53797374656d","0x506564657273656e","0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6","0x20","0x753332","0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511","0x4761734275696c74696e","0xbe","0x7265766f6b655f61705f747261636b696e67","0x77697468647261775f676173","0x6272616e63685f616c69676e","0x73746f72655f74656d70","0x66756e6374696f6e5f63616c6c","0x656e756d5f6d61746368","0x28","0x7374727563745f6465636f6e737472756374","0x61727261795f6c656e","0x736e617073686f745f74616b65","0x27","0x64726f70","0x7533325f636f6e7374","0x2","0x72656e616d65","0x7533325f6571","0x61727261795f6e6577","0x66656c743235325f636f6e7374","0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473","0x61727261795f617070656e64","0x7374727563745f636f6e737472756374","0x656e756d5f696e6974","0x26","0x25","0x29","0x24","0x6765745f6275696c74696e5f636f737473","0x23","0x77697468647261775f6761735f616c6c","0x22","0x7","0x4f7574206f6620676173","0x4661696c656420746f20646573657269616c697a6520706172616d202331","0x8","0x1f","0x4661696c656420746f20646573657269616c697a6520706172616d202332","0x9","0x4661696c656420746f20646573657269616c697a6520706172616d202333","0x1c","0x4661696c656420746f20646573657269616c697a6520706172616d202334","0xc","0x61727261795f736e617073686f745f706f705f66726f6e74","0x1a","0x6a756d70","0x756e626f78","0x647570","0x66656c743235325f616464","0x18","0x66656c743235325f737562","0x66656c743235325f69735f7a65726f","0x14","0x15","0x626f6f6c5f6e6f745f696d706c","0x416d6f756e74206d75737420626520706f736974697665","0x42616c616e636520697320616c72656164792030","0x13","0x42616c616e63652077696c6c206265206e65676174697665","0x73656e645f6d6573736167655f746f5f6c315f73797363616c6c","0x12","0x11","0x636c6173735f686173685f7472795f66726f6d5f66656c74323532","0x167ed61be3b800ca0247bfcb1dfc6b5bc76183796c4b527d5348937b30a696a","0x6c6962726172795f63616c6c5f73797363616c6c","0x73746f726167655f616464726573735f66726f6d5f62617365","0x73746f726167655f726561645f73797363616c6c","0x73746f726167655f77726974655f73797363616c6c","0x75313238735f66726f6d5f66656c74323532","0x753132385f636f6e7374","0x753132385f6f766572666c6f77696e675f737562","0x753132385f6571","0x526573756c743a3a756e77726170206661696c65642e","0x4","0x656d69745f6576656e745f73797363616c6c","0x25b1ef8ee6544359221f3cf316f768360e83448109193bdcef77f52a79d95c4","0x706564657273656e","0xad292db4ff05a993c318438c1b6c8a8303266af2da151aa28ccece6726f1f1","0x2fcde209a303a2fc48dcf9c7a3b76f69f9b032505d6aa5312a6835bc9f40c88","0x6fc","0xffffffffffffffff","0x6e","0x5e","0x4f","0x30","0x31","0x32","0x33","0x34","0x35","0x2b","0x2c","0x2d","0x2e","0x2f","0x36","0x47","0x37","0x38","0x39","0x3a","0x3b","0x3e","0x3f","0x3c","0x3d","0x40","0x41","0x42","0x43","0x44","0x45","0x46","0x48","0x49","0x4a","0x4b","0x4c","0x4d","0x4e","0x50","0x51","0x52","0x53","0x54","0x55","0x56","0x57","0x58","0x59","0x5a","0x5b","0x5c","0x5d","0x5f","0x60","0x61","0x62","0x63","0x64","0x65","0x66","0x67","0x68","0x69","0x6a","0x6b","0x6c","0x6d","0x6f","0x70","0xfb","0xeb","0xda","0xa4","0xca","0xc2","0x71","0x72","0x73","0x74","0x75","0x76","0x77","0x78","0x1a1","0x191","0x180","0x16e","0x136","0x15d","0x155","0x79","0x7a","0x7b","0x7c","0x7d","0x7e","0x7f","0x80","0x81","0x82","0x83","0x84","0x85","0x86","0x87","0x88","0x89","0x262","0x252","0x241","0x22f","0x21c","0x1e2","0x20a","0x202","0x8a","0x8b","0x8c","0x8d","0x8e","0x8f","0x90","0x91","0x92","0x93","0x94","0x95","0x96","0x97","0x98","0x99","0x9a","0x9b","0x9c","0x308","0x2f8","0x2e7","0x2d5","0x29d","0x2c4","0x2bc","0x31e","0x323","0x32d","0x347","0x381","0x379","0x39c","0x3a1","0x3b7","0x462","0x3d6","0x3db","0x3f2","0x417","0x457","0x43a","0x43f","0x44e","0x9d","0x9e","0x9f","0xa0","0xa1","0xa2","0xa3","0xa5","0xa6","0xa7","0xa8","0xa9","0xaa","0xab","0xac","0xad","0xae","0xaf","0xb0","0xb1","0x480","0x479","0x496","0x49b","0x4b2","0x55f","0x4d1","0x4d6","0x4ee","0x514","0x553","0x536","0x53b","0x54a","0xb2","0x5af","0x5a5","0x59d","0x5c9","0x5ce","0x5db","0x5f4","0x5f9","0x606","0x616","0x61a","0x648","0x634","0x645","0x63d","0x643","0x652","0x660","0x66a","0x68b","0x690","0x69b","0xb3","0xb4","0xb5","0xb6","0x6b6","0xb7","0x6c2","0x6cf","0xb8","0xb9","0xba","0xbb","0xbc","0xbd","0x10a","0x1b0","0x271","0x317","0x334","0x34f","0x355","0x38c","0x46e","0x486","0x56c","0x5ba","0x5e3","0x60f","0x61d","0x655","0x664","0x670","0x6a2","0x6b0","0x6bc","0x6c8","0x6d8","0x6db","0x6ea","0x3c6c","0x101e0e048340e060602412090482c0e0a028241208038180a04018080200","0x3034090c81c0c050a02430170205808150a02412090981c240c048242210","0x88121e038480a21038740a20048780e1f028780e1d02850121c0486c0e12","0x305009140244e07050142809100244c07090304a09120244607090302809","0xb80e1206080122d048780e2c028ac0e1d028241218150801220048a40e0a","0x1440091a82468070503008331002464091881c140c048243030128245e09","0x70121e038180a25048e41238038481820048dc121e038b00a2d048d80e1f","0x2480091f81c240c010f8043d020f04a091d82474070903012090f01c0c05","0x3812490e02412480391c0e46039148802218801209049080e0a060108225","0x24a24f04824a04f048249c09048249a1c04824984b048249407048249028","0x2412482b9481256048241255048241250039509e09049209e090494c0e52","0x174120924170120924024a45b04948b42504824b22204824b20904824b009","0x24124e1b824125916824125903984c009049200e5f2d82412482f0241248","0x249009048249c3b04824986204824941407024923704824903704824a037","0x168800904964380904964280904940280904938400904940c60e049242809","0x24a06704824946607024926529024ac1c04824a06429024ac072916c1252","0x1206a0904940d60904928340e04924d45204958d20e04924d052049587209","0x249007291bc12522d0241209371b41c09249b0a4092b0701c09248d41209","0x14c1209049c4de0904928125237824a45a100241259039c0de09049203409","0xb41209240dc120926024a44b04948b44b0482490072912c12522d0241209","0x24124804948c40929168c409049200e5231024a45a1d8241259100381249","0xe412092c8bc1209261d01209250901c092481ce60904824e42d048249c20","0x1d80e7503948125616824125004948ce0929168ce09049200e5233824a45a","0x1dc12092501cf207291dc12522d1e01209281dc120924024a47704948b407","0x1680e7e3e94812561283812493e02412481103812493d94812563d1481256","0x2001209252001c09249fc120929824a47f04948b47f048249007291fc1252","0x20d045204958125235824a45a35824124803948d609291680e81120241250","0x24947f07024926d04824a609291b412522d1b412092401ca46d04948b407","0x210ee0e04924720904930f80e049241c09049201c0904964800904940b609","0x1e01c092498c120929824a46304948b4630482490072918c12522d01d0a07","0x24a45a3a024124803948e809291685e09049644809049305a0e049240e86","0x24e47c04824987c04824a60704824a67c04824b20729025100743824a474","0x125165204958ee090494c0e8a14024125314024124e14024125003a245009","0x1b4120925024a48004948b4800482490072920012522d09012092c8bc1c09","0x1651c52049580e8d19038124946024124846024125046024124e3a0381249","0x23012522d1fc12092518c1209251a412092401d2007479481209261481209","0x240e0703a481c09049301c09049c8de0e049252252049591809049280e52","0x2518090701c0e930481ca4073318ca4940a0a0a4932914812520481c0e93","0x2526520d02428071402526091402450070d1a4a49304870128c038701293","0x1c44094982448093301c480949824d2093181c0e930481ca407100252a6d","0x24fe093481cfe09498240e1c0381d260912824340740094a493048881269","0xb41293049dc126d039e0129304a00126d0381d26093e02434073b9f0a493","0x24440703a4c12072901c0e6203a4ca42d3c14848073c02526093c0244007","0x1ce80949824e8093f81ce809498240e80038bc12930481c4a0703a4c126d","0x245a071a8252609191bca478039bc12930481cee071902526093a0bca47c","0x24c1214049d00e3904a4c1228048a00e3704a4c1207048bc0e6b04a4c1235","0x1480e3b2019c723714024760949824d6093781c8009498241c091901cce09","0x24ca4620a0a01c3703988129304988126b0398812930481c6a0703a4c1207","0x25260927824ce07278252609038e40e07498240e520396cb8524b178c052","0x1d00e9704a4c1260048a00e0749824ba091d81c005d2924c124b049000e4b","0x2400093101d3409498241c091901d3209498240e091781d300949824bc09","0x2813e9e4e8a126094e26d34994c25c286003a701293049b4127f03a6c1293","0x1700ea504a4c12071281c0e930481ca4075202546a204a4ca4a1049780ea1","0x2550093f81c0e9304a9c122203aa14e52498254c092d81d4c09498254409","0x24c12ac049740eac559492609552a4a44b03aa8129304a94124f03aa41293","0x27c0eaf04a4c12ae04a780e07498255a094e81d5cad2924c12ab048000e07","0x253a091401d6409498253e091781d62094982560095001d6009498255e09","0x2d4129304ac4126f03ad0129304a80123203a8c129304a78127403acc1293","0x24c129f048bc0eb604a4c12a4048b40e07498240e5203ad568a359ac85009","0x1d74094982540091901d7209498253c093a01d7009498253a091401d6e09","0x1d260936824440703a4c12072901d2cba5cae16e2804a58129304ad8126f","0x2f176523e01d78094982578093f81d7809498240ea103aec12930481c4a07","0x2526095f8245a075f82526095eaf8a47803af812930481cee075e8252609","0xc80ec304a4c125b049d00ec204a4c125c048a00ec104a4c1207048bc0ec0","0x1d2609039480ec56230d84c1140258a094982580093781d8809498241c09","0x25260903a600ec604a4c12071281c0e93049a412970381d260910024ba07","0x1e00e9504a4c12073b81d9009498258ec6291f00ec704a4c12c7049fc0ec7","0xa0122803b2c12930481c122f03b28129304b24122d03b24129304b212a52","0x25260965024de076702526090702464076682526090a024e807660252609","0x252609038940e074982518094b81c0e930481ca40767b399acc658a012cf","0x1dc0ed204a4c12d168148f80768825260968824fe0768825260903a840ed0","0x240e091781daa0949825a8091681da80949825a4d3291e00ed304a4c1207","0x360129304838123203a50129304998127403b5c12930498c122803b581293","0x148a409290240e07498240e0703b65b0946bb5850096c82526096a824de07","0x2438094601c38094982518090701c0e930481ca4073318ca4da0a0a0a493","0x240e520388012db3682526520d02428071402526091402450070d1a4a493","0x2001293290881214038884852498244a094601c4a0949824d2090701c0e93","0x1a40e7704a4c127c049980e7c04a4c12240498c0e07498240e52039fc12dc","0x24c122f049a40e2f04a4c12070e01c0e93049e0121a038b4f05249824ee09","0x1c6a094982464093681cde09498245a093681c0e93049d0121a038c8e852","0x20012220381d2609039480e076e81d26521a9bca424039bc1293049bc1220","0x24fe071b825260903a000e6b04a4c12071281c0e93049b412220381d2609","0x247267291e00e6704a4c12073b81c7209498246e6b291f00e3704a4c1237","0x1801293048a012280398812930481c122f038ec129304900122d039001293","0x18850092d82526091d824de072e02526090702464072f02526090a024e807","0xdc0e4f04a4c124f049ac0e4f04a4c12071a81c0e930481ca4072d970bc60","0x1d3c09498240e390381d2609039480e9d00149bc5d25949265227850500e","0x1c122f03a6c129304974127403a6812930492c122803a7c129304a781267","0x25260936824fe075202526094f824c4075102526090702464074e0252609","0x265309750a80509304a994aa451271369a31a640ea604a4c1280049fc0ea5","0x1c4a0703a4c12a704a6c0e07498240e5203aa012df5382526524c8253407","0x252609548253c0703a4c12ac04a740ea9561492609558240007558252609","0xa00eaf04a4c1297048bc0eae04a4c12ad04a800ead04a4c12aa04a7c0eaa","0x255c093781d64094982530091901d62094982542093a01d6009498254009","0x245e07518252609540245a0703a4c12072901d66b258ac15e2804acc1293","0x24c1298048c80eb604a4c12a1049d00eb504a4c12a0048a00eb404a4c1297","0x20012220381d2609039480eb85bad96ab41402570094982546093781d6e09","0x24fe075d025260903a840eb904a4c12071281c0e93049b412220381d2609","0x252cbb291e00ebb04a4c12073b81d2c094982574b9291f00eba04a4c12ba","0x2fc129304800122803af812930481c122f03af4129304af0122d03af01293","0x2f850096102526095e824de076082526090702464076002526094e824e807","0x24440703a4c122404a5c0e0749824fe092e81c0e930481ca4076130580bf","0x1d88094982588093f81d8809498240e9c03b0c12930481c4a0703a4c126d","0x245a0763825260962b18a47803b1812930481cee076282526096230ca47c","0x24c1214049d00ec904a4c1228048a00e9504a4c1207048bc0ec804a4c12c7","0x1480ecc65b2992951402598094982590093781d9609498241c091901d9409","0x2600ecd04a4c12071281c0e93049a412970381d260910024ba0703a4c1207","0x24c12073b81d9e09498259ccd291f00ece04a4c12ce049fc0ece04a4c1207","0x34c12930481c122f03b48129304b44122d03b44129304b3da0523c01da009","0x24de076b02526090702464076a82526090a024e8076a0252609140245007","0x940e074982518094b81c0e930481ca4076bb59aad4698a012d704a4c12d2","0x24c12d84a148f8076c02526096c024fe076c025260903a840e9404a4c1207","0x1dc40949825c2091681dc20949825b2e0291e00ee004a4c12073b81db209","0x38123203b94129304998127403b9012930498c122803b8c12930481c122f","0x240e07498240e0703b9dcce57238c500973825260971024de07730252609","0x1c38094982518090701c0e930481ca4073318ca4e80a0a0a493291481252","0x8012e93682526520d02428071402526091402450070d1a4a49304870128c","0x881214038884852498244a094601c4a0949824d2090701c0e930481ca407","0x24c127804a300e7804a4c1224048380e07498240e52039fc12ea400252652","0x2526093e024c60703a4c12072901c5e09758b41293291dc1214039dcf852","0x700e0749824de090d01c6a6f2924c1232049a40e3204a4c1274049980e74","0x24c1235049b40e07498246e090d01c72372924c126b049a40e6b04a4c1207","0x3b00e9329100ce521201cce0949824ce091001c80094982472093681cce09","0x24c126d048880e074982500091101c0e93048b412220381d2609039480e07","0xeca47c03988129304988127f0398812930481d00071d8252609038940e07","0x24c125c048b40e5c04a4c12602f148f0072f0252609039dc0e6004a4c1262","0x1cba094982428093a01c96094982450091401c9e09498240e091781cb609","0x24c12072901d3a002e92c9e2804a7412930496c126f038001293048381232","0x2813e52499493c14140386e074f02526094f024d6074f0252609038d40e07","0xa00e9904a4c12980499c0e9804a4c12071c81c0e930481ca4074ba84a4ed","0x241c091901d4e09498240e091781d4c094982540093a01d4a09498253e09","0x2a4129304a00127f03ab01293049b4127f03aac129304a64126203aa01293","0x291449c4da68509304aa952ac55aa14ea652999440755025260916824fe07","0x1c4a0703a4c12ad04a6c0e07498240e5203ab812ee568252652520253407","0x252609588253c0703a4c12b004a740eb1581492609578240007578252609","0xa00eb404a4c129c048bc0ea304a4c12b304a800eb304a4c12b204a7c0eb2","0x2546093781d6e094982544091901d6c094982536093a01d6a09498253409","0x245e075c8252609570245a0703a4c12072901d70b75b2d5682804ae01293","0x24c12a2048c80ebb04a4c129b049d00e9604a4c129a048a00eba04a4c129c","0xb412220381d2609039480ebd5e2ed2cba140257a094982572093781d7809","0x1d42075f0252609038940e0749824da091101c0e9304a0012220381d2609","0x252609039dc0ec004a4c12bf5f148f8075f82526095f824fe075f8252609","0x1d8809498240e091781d86094982584091681d84094982580c1291e00ec1","0x30c126f03b1c129304838123203b18129304a5c127403b14129304a841228","0x252e0703a4c122f049740e07498240e5203b218ec662b105009640252609","0x2900e9504a4c12071281c0e93049b412220381d260940024440703a4c127c","0x24c12073b81d9409498259295291f00ec904a4c12c9049fc0ec904a4c1207","0x33812930481c122f03b34129304b30122d03b30129304b2996523c01d9609","0x24de076882526090702464076802526090a024e807678252609140245007","0x880e0749824fe092e81c0e930481ca40769345a0cf670a012d204a4c12cd","0x1da809498240e9c03b4c12930481c4a0703a4c122404a5c0e0749824da09","0x358a47803b5812930481cee076a82526096a34ca47c03b50129304b50127f","0x24c1228048a00ed804a4c1207048bc0e9404a4c12d7048b40ed704a4c12d5","0x25c4094982528093781dc209498241c091901dc0094982428093a01db209","0x1c0e93049a412970381d260910024ba0703a4c12072901dc4e170365b028","0x25c8e3291f00ee404a4c12e4049fc0ee404a4c12074c01dc609498240e25","0x3bc129304b9c122d03b9c129304b95cc523c01dcc09498240e7703b941293","0x2464077902526090a024e807788252609140245007780252609038245e07","0x1c0e930481ca4077a3cde4f1780a012f404a4c12ef049bc0ef304a4c120e","0x2526097b024fe077b025260903a840ef504a4c12071281c0e9304a301297","0x1df20949825eef8291e00ef804a4c12073b81dee0949825ecf5291f00ef6","0x198127403bf012930498c122803bec12930481c122f03be8129304be4122d","0x3fdfcfd7e3ec50097f82526097d024de077f02526090702464077e8252609","0x1c0e930481ca4073318ca5000a0a0a4932914812520481c0e930481c0e07","0x2428071402526091402450070d1a4a49304870128c03870129304a30120e","0x244a094601c4a0949824d2090701c0e930481ca40710026026d04a4ca41a","0x24c1224048380e07498240e52039fc130240025265211024280711090a493","0x24c12072901c5e09818b41293291dc1214039dcf85249824f0094601cf009","0xc8e80e49824d635292940e6b04a4c127c048380e3504a4c1228048a00e07","0x1cce094982464093181c0e930481ca4071c826083704a4ca46f04a980e6f","0x240e1c0381d26091d8243407310eca4930490012690390012930499c1266","0x16c129304988126d0381d26092f02434072e178a493049801269039801293","0x1c0f0503a4ca44f2d94848072d82526092d82440072782526092e024da07","0x1c0e9304a0012220381d260916824440703a4c123704a9c0e07498240e52","0x2526092e824fe072e825260903a000e4b04a4c12071281c0e93049b41222","0x1d3c0949824009d291e00e9d04a4c12073b81c000949824ba4b291f00e5d","0x50127403a841293049d0122803a8012930481c122f03a7c129304a78122d","0x265309750a8050094c82526094f824de074c02526090702464074b8252609","0x2682874070dc0e9a04a4c129a049ac0e9a04a4c12071a81c0e930481ca407","0x254a093381d4a09498240e390381d2609039480ea45114a0c9c4d9492652","0x2b812930481c122f03ab4129304a70127403aa8129304a6c122803a981293","0x24fe0758825260936824fe0758025260953024c407578252609070246407","0x2b95aaa34aac0ea304a4c123704aa00eb304a4c122d049fc0eb204a4c1280","0x1480eb504c1d68094994952094d01d52ac55aa14e284982546b3592c560af","0x2e16e52498256c090001d6c09498240e250381d26095a025360703a4c1207","0x2e812a003ae8129304ae4129f03ae4129304ae0129e0381d26095b8253a07","0x25260954024e8075e02526095382450075d8252609558245e074b0252609","0x1ca4075faf97abc5d8a012bf04a4c1296049bc0ebe04a4c12ac048c80ebd","0x308129304a9c122803b04129304aac122f03b00129304ad4122d0381d2609","0x304500962825260960024de0762025260956024640761825260954024e807","0x24440703a4c122d048880e07498246e095381c0e930481ca40762b1186c2","0x1fc0ec704a4c12075081d8c09498240e250381d260936824440703a4c1280","0x3212a523c01d2a09498240e7703b20129304b1d8c523e01d8e09498258e09","0x252609510245007658252609038245e07650252609648245a07648252609","0xa012cf04a4c12ca049bc0ece04a4c120e048c80ecd04a4c12a4049d00ecc","0x880e074982464094b81c0e93048e4125d0381d2609039480ecf6733598cb","0x1da009498240e250381d260936824440703a4c1280048880e07498245a09","0x240e7703b48129304b45a0523e01da20949825a2093f81da209498240eac","0x252609038245e076a82526096a0245a076a02526096934ca47803b4c1293","0x1bc0ed804a4c120e048c80e9404a4c1214049d00ed704a4c1274048a00ed6","0x1c0e93048bc125d0381d2609039480ed96c251aed614025b20949825aa09","0x38012930481c4a0703a4c127c04a5c0e0749824da091101c0e9304a001222","0x1cee0771025260970b80a47c03b84129304b84127f03b8412930481d4807","0x24c1207048bc0ee504a4c12e4048b40ee404a4c12e271948f007718252609","0x1de009498241c091901dde094982428093a01dce094982450091401dcc09","0x1d26093f824ba0703a4c12072901de2f077b9dcc2804bc4129304b94126f","0x25260903a700ef204a4c12071281c0e93049b412220381d2609120252e07","0x1e00ef504a4c12073b81de80949825e6f2291f00ef304a4c12f3049fc0ef3","0xa0122803be012930481c122f03bdc129304bd8122d03bd8129304bd1ea52","0x2526097b824de077d82526090702464077d02526090a024e8077c8252609","0x24c126904a5c0e074982440092e81c0e930481ca4077e3edf4f97c0a012fc","0x3f4a47c03bf8129304bf8127f03bf812930481d30077e8252609038940e07","0x24c1309048b40f0904a4c12ff84148f007840252609039dc0eff04a4c12fe","0x1e1a094982428093a01e18094982450091401e1609498240e091781e1409","0x24c12072901e1f0e86c32162804c3c129304c28126f03c381293048381232","0x2622093f81e2209498240ea103c4012930481c4a0703a4c128c04a5c0e07","0x2526098944ca47803c4c12930481cee0789025260988c40a47c03c441293","0x1d00f1704a4c1263048a00f1604a4c1207048bc0f1504a4c1314048b40f14","0x4622f16140263409498262a093781e3209498241c091901e300949824cc09","0x24c12072901ccc632946c28282924ca45204948120703a4c12070381e3519","0x1c50094982450091401c34692924c121c04a300e1c04a4c128c048380e07","0x251807128252609348241c0703a4c12072901c40098e1b41293290681214","0x90120e0381d2609039480e7f04c7500094994844090a01c44242924c1225","0x1ca407178263c2d04a4ca477048500e773e14926093c02518073c0252609","0x1bca493048c81269038c81293049d01266039d01293049f012630381d2609","0x2434071c8dca493049ac1269039ac12930481c380703a4c126f048680e35","0x2526093382440072002526091c824da073382526091a824da0703a4c1237","0x24440703a4c122d048880e07498240e520381e3e07499488067290900e67","0x1fc0e6204a4c12074001c7609498240e250381d260936824440703a4c1280","0x180bc523c01cbc09498240e770398012930498876523e01cc40949824c409","0x252609140245007278252609038245e072d82526092e0245a072e0252609","0xa0129d04a4c125b049bc0e0004a4c120e048c80e5d04a4c1214049d00e4b","0x1d3c09498253c093581d3c09498240e350381d2609039480e9d00174964f","0x26012930481c720703a4c12072901d2ea129481409f2924ca49e0a0a01c37","0x245e0753025260950024e8075282526094f82450074c82526094c024ce07","0x24c126d049fc0eab04a4c1299049880ea804a4c120e048c80ea704a4c1207","0x2b156a853a994a665481d5409498245a093f81d52094982500093f81d5809","0x1d2609039480eae04c855a094994948094d01d48a24e26d34284982554a9","0x2c0129d03ac56052498255e090001d5e09498240e250381d2609568253607","0x28c129304acc12a003acc129304ac8129f03ac8129304ac4129e0381d2609","0x2464075b02526094d824e8075a82526094d02450075a02526094e0245e07","0x1c0e930481ca4075c2dd6cb55a0a012b804a4c12a3049bc0eb704a4c12a2","0x26c127403a58129304a68122803ae8129304a70122f03ae4129304ab8122d","0x2f578bb4b2e850095e82526095c824de075e02526095102464075d8252609","0x1d260936824440703a4c1280048880e07498245a091101c0e930481ca407","0x2fd7c523e01d7e09498257e093f81d7e09498240ea103af812930481c4a07","0x252609610245a0761025260960304a47803b0412930481cee07600252609","0xc80ec604a4c1297049d00ec504a4c12a1048a00ec404a4c1207048bc0ec3","0x1d2609039480ec863b198ac41402590094982586093781d8e09498241c09","0x24c126d048880e074982500091101c0e93049f012970381d260917824ba07","0x254a47c03b24129304b24127f03b2412930481d48074a8252609038940e07","0x24c12cc048b40ecc04a4c12ca65948f007658252609039dc0eca04a4c12c9","0x1da0094982428093a01d9e094982450091401d9c09498240e091781d9a09","0x24c12072901da4d16833d9c2804b48129304b34126f03b441293048381232","0x24c12071281c0e930489012970381d260936824440703a4c127f049740e07","0x1daa0949825a8d3291f00ed404a4c12d4049fc0ed404a4c12074e01da609","0x1c122f03a50129304b5c122d03b5c129304b55ac523c01dac09498240e77","0x2526090702464077002526090a024e8076c82526091402450076c0252609","0x2440092e81c0e930481ca40771385c0d96c0a012e204a4c1294049bc0ee1","0x390127f03b9012930481d3007718252609038940e0749824d2094b81c0e93","0x24c12e573148f007730252609039dc0ee504a4c12e471948f807720252609","0x1de2094982450091401de009498240e091781dde0949825ce091681dce09","0x3c5e02804bd0129304bbc126f03bcc129304838123203bc81293048501274","0x240ea103bd412930481c4a0703a4c128c04a5c0e07498240e5203bd1e6f2","0x3e012930481cee077b82526097b3d4a47c03bd8129304bd8127f03bd81293","0xa00efb04a4c1207048bc0efa04a4c12f9048b40ef904a4c12f77c148f007","0x25f4093781dfc09498241c091901dfa0949824cc093a01df80949824c609","0x38a4524994812095501c1209498240e093181dfefe7ebf1f62804bfc1293","0x2bc0e1404a4c125204ab80e2804a4c120e04ab40e07498240e5203a301322","0x2c80e6604a4c12075881c0e930481ca40703c8c12075801cc609498245009","0x2428094f01cc60949824d2095781c28094982518095701cd20949824cc09","0x24c12072901cda099207012932918c12b303868129304868120e038681293","0x380e2204a4c122404ad40e2404a4c122004ad00e2004a4c121c04a8c0e07","0x24ba0703a4c12072901d00252902500094982444095b01c4a09498243409","0x1dc129304868120e039f01293049fc12b7039fc12930481d620703a4c126d","0x2526090382450070a02526094602570073c1dca4093c02526093e0256c07","0x2e40e2204a4c120e048c80e2404a4c1252048bc0e2004a4c1209049d00e6d","0x18c509304a004a2212080da145d01d00094982450093f81c4a09498242809","0x24c127f049700e07498240e52039f013253f82526520e024bc070e068d266","0x1c5e0949824c6091401c5a0949824f0095d81cf00949824ee094b01cee09","0xb412bc039bc1293048681232038c81293049a4122f039d01293049981274","0xa00e6b04a4c127c04af40e07498240e52038d4de323a0bc50091a8252609","0x2434091901cce0949824d2091781c720949824cc093a01c6e0949824c609","0x1ca409498240e095a01c7640338e46e28048ec1293049ac12bc039001293","0x23012be038a0129304838124f03a3012930481d620707025260929024a47c","0xa00e69331492609318257e073182526094602570070a0a0a4090a0252609","0x241c091901d000949824a4091781c4a094982412093a01c4409498240e09","0x2526093b824fe073b8a0a493048a012c0039f01293049a412b9039fc1293","0x498f0094994848092f01c482036870342849824ee7c3fa004a220a2e80e77","0xa00e7404a4c12141794982071782526093c024b80703a4c12072901c5a09","0x2440091901c800949824da091781cce094982438093a01c7209498243409","0x1781293049d0127f039801293048a0127f0398812930499812b9038ec1293","0x49cb809499486e096181c6e6b1a9bc642849824bc60310ec80671c98d8407","0x1d620703a4c124b049740e4b2794926092e025880703a4c12072901cb609","0x24c129d04b180e9d04a4c125d001498a0700025260927824ce072e8252609","0x1d4209498246a091781d400949824de093a01d3e094982464091401d3c09","0x24c12072901d309750a813e2804a60129304a7812c703a5c1293049ac1232","0xbc0e9b04a4c126f049d00e9a04a4c1232048a00e9904a4c125b04b200e07","0x271369a1402548094982532096381d440949824d6091901d3809498246a09","0x2428091101c0e930499812950381d260914024440703a4c12072901d48a2","0x1d4e094982438093a01d4c094982434091401d4a09498245a096401c0e93","0x29d4c2804ab0129304a9412c703aac129304880123203aa01293049b4122f","0x24c1269048880e1a34949260933024b60733050a4930485012c003ab156a8","0x25680703a4c126d048880e203694926090e024b6070e025260903b240e07","0x244a093f81c4a09498244424293280e2204a4c122004ad00e2404a4c121a","0x1cfe09498240eb10381d2609039480e8004ca00e932909412cb038941293","0x1d2609039480e07948240eb0039dc1293049f012cd039f01293049fc12cc","0x245a096681c5a0949824f0096781cf009498240eb10381d2609400259c07","0x1d01293290bc12d1038bc1293048bc12cd038bc1293049dc12d0039dc1293","0xa012220381d26090a024440703a4c1274049740e07498240e52038c8132a","0x1da407378252609038940e074982518091d81c0e930498c12220381d2609","0x252609039dc0e6b04a4c123537948f8071a82526091a824fe071a8252609","0x1c8009498240e091401cce094982472096401c720949824d637291e00e37","0x19c12c703980129304838123203988129304948122f038ec1293048241274","0x25700703a4c1232049740e07498240e5203978c0621d90050092f0252609","0x2412093a01d3e09498240e091401c9e5b2924c125c04afc0e5c04a4c128c","0x26012930493c12b903a5c129304838123203a84129304948122f03a801293","0x2532984ba85409f0a2e80e9904a4c1299049fc0e99141492609140258007","0x24b80703a4c12072901d360995a68129329278125e03a793a002e92c5093","0x290122203a9548524982544092d81d449c2924c129c04b000e9c04a4c129a","0x1c0e9304a9c122203aa14e52498254c092d81d4c09498240ec90381d2609","0x24fe07548252609562aca4ca03ab0129304aa012b403aac129304a9412b4","0x25260903ac40e07498240e5203aa8132c03a4ca4a904b2c0ea904a4c12a9","0x240e520381e5a0903ac00eaf04a4c12ae04b340eae04a4c12ad04b300ead","0x259a07588252609580259e0758025260903ac40e074982554096701c0e93","0x24ca4b204b440eb204a4c12b204b340eb204a4c12af04b400eaf04a4c12b1","0x880e074982538091101c0e9304acc125d0381d2609039480ea304cb96609","0x1c0e930496c12950381d260931824440703a4c1228048880e07498242809","0x256ab4291f00eb504a4c12b5049fc0eb504a4c12076981d6809498240e25","0x2e4129304ae012c803ae0129304ad96e523c01d6e09498240e7703ad81293","0x2464075d8252609000245e074b02526092e824e8075d0252609258245007","0x1c0e930481ca4075eaf176965d0a012bd04a4c12b904b1c0ebc04a4c129d","0x304127f03b0538524982538096001d80094982496091401c0e9304a8c125d","0x2428096001d8809498257c091401d7ebe2924c12c160149a807608252609","0x2584091401d86c22924c12c562149a80762825260962824fe0762850a493","0x24c12c94ab201cd603b24129304b0c12d503a54129304afc12d503b201293","0x32c12932932812d103b28129304b2812cd03b28129304b1c12d003b1d8c52","0xa012220381d26090a024440703a4c12cb049740e07498240e5203b30132f","0x1c4a0703a4c129c048880e0749824b6094a81c0e930498c12220381d2609","0x25260967334a47c03b38129304b38127f03b3812930481dae07668252609","0xa00ed204a4c12d104b200ed104a4c12cf68148f007680252609039dc0ecf","0x253a091901daa094982400091781da80949824ba093a01da609498258c09","0x24ba0703a4c12072901daed66ab51a62804b5c129304b4812c703b581293","0x258c091401d280949825b09c293280ed80a14926090a025800703a4c12cc","0x39c129304a74123203b98129304800122f03b94129304974127403b901293","0x1fc0ef004a4c12f0049fc0ef01414926091402580077782526092d8257207","0x25860771b89c2e06c8a1260978bc1dee773395c8636101de209498252809","0x25260903b240ef404a4c12071281c0e930481ca4077982660f204a4ca4e3","0x3dc1293048a1ec523e01dec0949825eaf4291f00ef504a4c12f5049fc0ef5","0xe0749825f4092e81df4f92924c12f204b100ef804a4c12147b948f807","0x25fa090701dfa0949825f8094f01c0e9304bec129d03bf1f65249825f009","0x1c0e930481ca40785426100e98bfdfc5249949fa6371381189403bf41293","0x25fe091901e1a0949825fc093a01e18094982616096c01e1609498240eb1","0x42812e00381d2609039480e07990240eb003c3c129304c3012d903c381293","0x25260988025b20787025260984824640786825260984024e807880252609","0x26671304a4ca51104b8c0f1104a4c131204b880f1204a4c130f04b840f0f","0x24c12f90499c0f1504a4c12075881c0e9304c4c12e40381d2609039480f14","0x464129304b64122803c60129304c5c12c603c5c129304c562c526281e2c09","0x258e079a82526098702464079a0252609708245e078d025260986824e807","0x3200e0749825f2094a81c0e930481ca4079b4d6691a8c8a0133604a4c1318","0x25c2091781e7209498261a093a01e700949825b2091401e6e09498262809","0x1e76dd9d4e6702804cec129304cdc12c703b74129304c38123203ce81293","0x1c0e930485012220381d260931824440703a4c1228048880e07498240e52","0x384122f03cf8129304b80127403cf4129304b64122803cf0129304bcc12c8","0x506813f9f4f45009a082526099e0258e07a002526097102464079f8252609","0x1d260914024440703a4c1214048880e0749824b6094a81c0e930481ca407","0x174127403d0c12930492c122803d08129304a6c12c80381d2609318244407","0x252609a10258e07a302526094e8246407a28252609000245e07a20252609","0x500e0e291492609460251807460252609048241c07a3d1a8b44a18a01347","0x2692663194926521401ca4e50381d2609039480e1404d205009499481c09","0x148120e0387012930498c12280386812930499812e60381d2609039480e69","0x240eb10381d2609039480e20368701c091002526090d025ce07368252609","0x200129304948120e038941293049a412280388812930489012ef038901293","0x1f012930485012ef0381d2609039480e7f400941c093f825260911025ce07","0x1dc1c091682526093e025ce073c0252609290241c073b8252609038245007","0x1d26090d02444070e068a493049a4125b039a428524982428096001c5a78","0x7012b40381d260910024440712080a493049b4125b039b412930481d9207","0x24c1280049fc0e8004a4c1225111499407128252609120256807110252609","0x3300e7c04a4c12075881c0e930481ca4073f82694074994900096581d0009","0x1c0e930481ca40703d2c12075801cf00949824ee096681cee0949824f809","0x24c122f04b340e2f04a4c122d04b3c0e2d04a4c12075881c0e93049fc12ce","0x530640949948e8096881ce80949824e8096681ce80949824f0096801cf009","0x24cc095381c0e93048a012220381d260919024ba0703a4c12072901cde09","0x240e250381d260946024760703a4c1214048880e0749824c6091101c0e93","0xdc1293049ac6a523e01cd60949824d6093f81cd609498240ed2038d41293","0x2450072002526093382590073382526091b8e4a478038e412930481cee07","0x24c120e048c80e6004a4c1252048bc0e6204a4c1209049d00e3b04a4c1207","0x1bc125d0381d2609039480e5c2f180c43b14024b8094982480096381cbc09","0x2526090382450072593ca4930496c12bf0396c129304a3012b80381d2609","0x2e40e9804a4c120e048c80e9704a4c1252048bc0ea104a4c1209049d00ea0","0x28028ba03a68129304a68127f03a6850524982450096001d3209498249609","0x1ca4074e0269a9b04a4ca49f049780e9f4f274005d1424c129a4ca612ea1","0x149260952024b60752288a49304a8812c003a88129304a6c125c0381d2609","0x880eab54149260953824b60753825260903b240e07498254a091101d4ca5","0x2552ac293280ea904a4c12ab04ad00eac04a4c12a604ad00e07498255009","0x1d2609039480ead04d380e93292a812cb03aa8129304aa8127f03aa81293","0x240eb003ac0129304abc12cd03abc129304ab812cc03ab812930481d6207","0x2562096781d6209498240eb10381d2609568259c0703a4c12072901c0f4f","0x2cc129304acc12cd03acc129304ac012d003ac0129304ac812cd03ac81293","0x24440703a4c12a3049740e07498240e5203ad0135051825265259825a207","0x880e0749824c6091101c0e930499812a70381d260914024440703a4c12a2","0x1d6c09498240ed303ad412930481c4a0703a4c124f04a540e07498242809","0x2e0a47803ae012930481cee075b82526095b2d4a47c03ad8129304ad8127f","0x24c1200049d00e9604a4c125d048a00eba04a4c12b904b200eb904a4c12b7","0x257c094982574096381d7a09498253c091901d7809498253a091781d7609","0x30412930497412280381d26095a024ba0703a4c12072901d7cbd5e2ed2c28","0x3017e524982584c1293500ec204a4c12c2049fc0ec2511492609510258007","0x3500ec604a4c12c6049fc0ec60a14926090a02580076282526095f8245007","0x25aa0764825260960025aa074a82526096182450076230ca49304b198a52","0x259a0765825260964025a0076431ca49304b299295073580eca04a4c12c4","0x2598092e81c0e930481ca40766826a2cc04a4ca4cb04b440ecb04a4c12cb","0x5012220381d260931824440703a4c126604a9c0e074982450091101c0e93","0x1dae07670252609038940e074982544091101c0e930493c12950381d2609","0x252609039dc0ed004a4c12cf67148f80767825260967824fe07678252609","0x1da809498258e091401da60949825a4096401da40949825a0d1291e00ed1","0x34c12c703b5c129304a78123203b58129304a74122f03b541293048001274","0x25800703a4c12cd049740e07498240e5203a51aed66ab5050094a0252609","0x127403b94129304b1c122803b60129304b6544526501db2142924c1214","0x2526092782572077782526094f02464077382526094e8245e07730252609","0x1de40949825b0093f81de20949825e2093f81de2282924c122804b000ef0","0x26a4f304a4ca4e404b0c0ee471b89c2e01424c12f278bc1dee773394c6c2","0x3d8a47c03bd81293048a1ea523e01dea09498240e250381d2609039480ef4","0x3e8125d03be9f25249825e6096201df00949824c6f7291f00ef704a4c1214","0x1c0e9304bf0129d03bf5f85249825f0090001df609498240ef00381d2609","0x38450f103bf8129304bf8120e03bec129304bec127f03bf8129304bf4129e","0x24c130904bc80e07498240e5203c32170a0754e13087f83926527f3eccce3","0x1e2009498261a097981e1e094982610091901e1c0949825fe093a01e1a09","0x438129304c28127403c44129304c3012f40381d2609039480e07aa0240eb0","0x25ec0789825260988025ea0788025260988825e607878252609858246407","0x2628097c01c0e930481ca4078a826ab1404a4ca51204bdc0f1204a4c1313","0x1e3009498262d17293140f1704a4c12f90499c0f1604a4c12075881c0e93","0x388122f03cd0129304c38127403c68129304b80122803c64129304c6012c6","0x4de6d359a46850099b82526098c8258e079b02526098782464079a8252609","0x24c12e0048a00f3804a4c131504b200e0749825f2094a81c0e930481ca407","0x1e7609498261e091901dba0949825c4091781e7409498261c093a01e7209","0x1d260914024440703a4c12072901e793b6ecea722804cf0129304ce012c7","0x24c12f404b200e074982428091101c0e930498c12220381d2609330254e07","0x1e800949825c4091781e7e0949825c2093a01e7c0949825c0091401e7a09","0x24c12072901e8541a04fe7c2804d08129304cf412c703d04129304b8c1232","0x24c6091101c0e930499812a70381d260914024440703a4c124f04a540e07","0x1d00f4404a4c125d048a00f4304a4c129c04b200e074982428091101c0e93","0x2686096381e8e09498253c091901e8c09498253a091781e8a09498240009","0x2fc0e6604a4c128c04ae00e074982450091101ead47a3516882804d581293","0x148122f03a0012930482412740389412930481c122803868d25249824cc09","0x14926090a02580073b82526090d02572073e02526090702464073f8252609","0x884820368705093049e0ee7c3fa004a145d01cf00949824f0093f81cf014","0x3000e7404a4c122d049700e07498240e52038bc135716825265211024bc07","0x24e8072002526090e0245007190252609379d0a4c1039bcc65249824c609","0x24c126904ae40e6004a4c1224048c80e6204a4c1220048bc0e3b04a4c126d","0x16c1293048c8127f03970129304970127f0397028524982428096001cbc09","0x5609e0949948ce096181cce391b9ac6a2849824b65c2f180c43b2018d8407","0x149f20703a4c1200049740e002e949260927825880703a4c12072901c9609","0x2472091901d2e0949824d6093a01d3c0949824ba093381d3a0949824c614","0x2534994c25d18fb03a68129304a7412fa03a64129304a78126203a601293","0x2536097e01c0e930481ca4074e026b29b04a4ca4a104a680ea15027c1c93","0x298129304a9544526281d4a09498240eb10381d260952024ba0752288a493","0x245e075582526094f824e8075402526091a8245007538252609530258c07","0x2a558ab540a012aa04a4c12a704b1c0ea904a4c12a0048c80eac04a4c1237","0x27c127403ab81293048d4122803ab4129304a7012c80381d2609039480eaa","0x252609568258e075882526095002464075802526091b8245e07578252609","0x24c1263048880e074982428091101c0e930481ca407592c560af570a012b2","0xbc0eb404a4c126b049d00ea304a4c1235048a00eb304a4c124b04b200e07","0x2d568a3140256e094982566096381d6c094982472091901d6a09498246e09","0x24d2094a81c0e930498c12220381d26090a024440703a4c12072901d6eb6","0x1d740949824da093a01d72094982438091401d7009498245e096401c0e93","0x2e9722804af0129304ae012c703aec129304890123203a58129304880122f","0x70129304a3012b903868129304948122f039a412930481c122803af17696","0x24c126604bf80e66318501c93049b4381a34a31fa0736825260914024fe07","0x392652100901c09463fc0e2404a4c1224048800e2404a4c12070e01c4009","0x2444093a01cf0094982500098401c0e930481ca4073b9f0fe0ead2004a22","0x1480e07ad8240eb0039d01293049e01309038bc1293048941232038b41293","0x2526093e02464071682526093f824e8071902526093b826140703a4c1207","0x1780e6f04a4c123504c300e3504a4c127404c2c0e7404a4c123204c240e2f","0xe41296038e41293049ac125c0381d2609039480e3704d70d60949948de09","0x25260916824e8071d82526090a0245007200252609338257607338252609","0xa0125c04a4c124004af00e5e04a4c122f048c80e6004a4c1263048bc0e62","0x13c12930485012280396c1293048dc12bd0381d2609039480e5c2f180c43b","0x2578070002526091782464072e8252609318245e0725825260916824e807","0x24c1207048a00e66319492609460257e074e800ba4b278a0129d04a4c125b","0x1c44094982450093f81c480949824cc095c81c400949824a4091781cda09","0x20012930481c38071282526090e025fc070e068d20e498244424101b518fd","0x1c5a783b83aba7c3f94926520a095000e048a21a07400252609400244007","0x2526093f824e8073a025260917825b00717825260903ac40e07498240e52","0x240e520381ebc0903ac00e3504a4c127404b640e6f04a4c127c048c80e32","0x1cde0949824f0091901c640949824ee093a01cd609498245a097001c0e93","0xdc12e3038dc1293048e4130e038e41293048d412e1038d41293049ac12d9","0x247663294400e3b04a4c126704c3c0e07498240e5203900135f338252652","0x1701293048c81274039781293049a41228039801293049881311039881293","0x17850092582526093002624072782526093782464072d82526090d0245e07","0xa00e5d04a4c124004c4c0e0749824c6094a81c0e930481ca4072593cb65c","0x24de091901d3c094982434091781d3a094982464093a01c000949824d209","0x148a493290240e528a01d409f4f274002804a80129304974131203a7c1293","0x25260931838a5160398c12930481e2a0703a4c12072901c28284603ac00e","0x240e520381ec20903ac00e1a04a4c126604b540e6904a4c1252048a00e66","0x6812930487012d5039a4129304a301228038701293048a028528b01c0e93","0x1492609048263207101b4a4091002526090d0263007368252609348262e07","0x14926090a026340731a30a49304a30131a03850505249824a4098c81d180e","0x6813350381d2609039480e6d0e14ac41a3494926523318c0e0e9a01ccc14","0x88a4930485013360381d2609100266a0712080a49304a3013360381d2609","0x2450073f8252609128266e07400252609120266e0703a4c122204cd40e25","0x24c122804cd40e07498240e520381ec60749948fe80294e00e6904a4c1269","0x1a41228039dc1293049f012cf039f012930481d620703a4c120e04cd40e07","0x3a680703a4c12072901c0f640481d60071682526093b8259a073c0252609","0x2c40e0749824e8099a81c0e930481ca407378c8a5653a0bca493290a01c69","0x24c126b04b340e3704a4c122f048a00e6b04a4c123504b3c0e3504a4c1207","0x25260903ac40e0749824de099a81c0e930481ca40703d9812075801c7209","0x45c0e3904a4c124004b340e3704a4c1232048a00e4004a4c126704b300e67","0x245a099c81c760949824f0098b81c5a094982472099c81cf009498246e09","0x2518099a81c0e93049b413350381d2609039480e07b38240eb0039881293","0x240eb10381d26090a0266a0703a4c120e04cd40e074982450099a81c0e93","0x18812930497812cd038ec12930487012280397812930498012cc039801293","0x2412930481e74072d970a4092d82526093102672072e02526091d8262e07","0x38c0e5204a4c128c07149ba0746025260904824fe0707025260903825b207","0x18c133b0398c1293048a0130f0381d2609039480e1404da0500949948a409","0x240e520386812090d0252609348267a07348252609330267807330252609","0x24ca40704cfc0e6d04824da094982438099e81c38094982428099f01c0e93","0x23012930483812a003838129304824129f0381d2609039480e5204da41209","0x148f0070a0252609039dc0e07498240e52038a0120914025260946024de07","0x3e80e6904824d20949824cc093781ccc0949824c6091681cc60949824a414","0x252609038940e1404a4c12071281d1809498245009a001c5009498241c09","0x13c0e2004a4c126904d0c0e0749824cc09a101cd2662924c128c04d040e63","0x1cda1c0d039260911090400ea201c440949824c6092781c4809498242809","0x2500094f01c0e9304894129d03a004a524982434090001c0e93049b4125d","0x1e01293049dc129e0381d26093e0253a073b9f0a493048701200039fc1293","0xb4a493291e0fe0903a328a073c02526093c0241c073f82526093f8241c07","0x1ac1293048d412d8038d412930481d620703a4c12072901cde323a03ad42f","0x1d600733825260935825b2071c82526091782464071b825260916824e807","0xc80e3704a4c1274049d00e4004a4c126f04b800e07498240e520381ed609","0x24c4098701cc40949824ce097081cce094982480096c81c7209498246409","0x252609300261e0703a4c12072901cbc09b61801293290ec12e3038ec1293","0x1c9609498246e093a01c9e0949824b6096301cb60949824b852293140e5c","0x1c0e930481ca40700174960e0480012930493c12c7039741293048e41232","0x2472091901d3c09498246e093a01d3a0949824bc096401c0e9304948123b","0x240f460381d2609290252a075027d3c0e04a80129304a7412c703a7c1293","0x50129304850127f038501293048a01356038a0129304a30134703a301293","0x681293049a41356039a4129304998134703998c652498241c140483ada07","0x1c40094982438091401cda1c2924c121a0394adc070d02526090d024fe07","0x25265203826e00711090400e048881293049b4136f0389012930498c122f","0x1d1809498241c095d81c1c094982412094b01c0e930481ca40729026e209","0x50a4780385012930481cee0703a4c12072901c5009048a0129304a3012bc","0x26e40734824126904a4c126604af00e6604a4c126304af40e6304a4c1252","0x241c099e01c1c094982412099d81c0e930481ca40729026e60904a4ca407","0x5012930481cee0703a4c12072901c5009048a0129304a30133d03a301293","0x24126904a4c126604cf40e6604a4c126304cf80e6304a4c12520a148f007","0x4ec0e074982412091101c0e930481ca40707026e85204a4ca40704dc80e69","0x1480e140482428094982450099e81c50094982518099e01d180949824a409","0x198129304824c6523e01cc609498240e250381d2609070253a0703a4c1207","0x267a070e02526090d0267c070d0252609331a4a478039a412930481cee07","0x148120929025260904826860704825260903826ea0736824126d04a4c121c","0x24c128c049fc0e8c04a4c1207bb81c0e930481ca40707025260903826ec07","0x681293048a0124f039a412930483812fa038a0129304a3012523e01d1809","0x1d260933024ba073318c280e49824381a3483af0070e0252609290249e07","0x1b412be0389012930498c124f03880129304850124f039b412930481d6207","0xa11852498241c09bd01c1c072924c120704de40e22120801c09110252609","0x198a44b039a4129304824124f03998129304a30127f0381d2609140244407","0x2434091101c381a2924c120704de80e0749824c6092e81cc6142924c1269","0x1b4a4930488848522581c44094982428092781c48094982438093f81c0e93","0x148124f03a001293049b4124f0389412930481d620703a4c1220049740e20","0x1cb8280e178ba072e0a06a7c3fa001c093e0252609128257c073f8252609","0x3cd180e290240e5b2f1740e5c14070bc5d03970500746038a4090396cbc5d","0x1cb8280e178ba072e0a2f68c0714812072d978ba072e0a0385e2e81cb828","0x5f5180e290240e5b2f1740e5c14070bc5d03970517c46038a4090396cbc5d","0x5fc508c07148120731178b85d038a012372f170ba070a5f80e4b0e1483809","0x38a4090399cbc5c2e81c5009048dcbc5c2e81cc7800481c4014290501252","0x18c282846038a4090399cbc5c2e81c5009048246e5e2e1740e66c0850508c","0x19cbc5c2e81c50350482412372f170ba0734e0c1207358700e0e0e01ca582","0x240e672f170ba071402412091b978b85d0399b086631850508c071481207","0x618508c07148120731178b85d038a0122d2f170ba070a614c614142301c52","0x1ca4090394b0e14142301c520481ce85e2e1740e28048245a5e2e1740e63","0x62c0e5b049b4138a03a00127f04e24a409039dc0e523e1f00e0ec40240e7c","0x38a409039a4b807070245a5c03a33180e290240e672f1741c0e1b978ba8c","0x6440e8c0483813900481d0009049fca58f03a00127f04e380e620498c138d","0xc9948120710050280e0a0501c0ec9148120710050280e0a051180e"],"sierra_program_debug_info":{"type_names":[[0,"RangeCheck"],[1,"felt252"],[2,"core::pedersen::HashState"],[3,"cairo::l1_l2::l1_l2::DepositFromL1"],[4,"cairo::l1_l2::l1_l2::Event"],[5,"u128"],[6,"Array"],[7,"core::result::Result::>"],[8,"StorageAddress"],[9,"StorageBaseAddress"],[10,"Snapshot>"],[11,"core::array::Span::"],[12,"core::result::Result::, core::array::Array::>"],[13,"Unit"],[14,"Tuple"],[15,"core::panics::Panic"],[16,"Tuple>"],[17,"core::panics::PanicResult::<((),)>"],[18,"core::result::Result::<(), core::array::Array::>"],[19,"core::integer::u256"],[20,"core::bool"],[21,"NonZero"],[22,"cairo::l1_l2::l1_l2::balances::ContractMemberState"],[23,"Tuple"],[24,"core::panics::PanicResult::<(cairo::l1_l2::l1_l2::balances::ContractMemberState, ())>"],[25,"Box"],[26,"core::option::Option::>"],[27,"ClassHash"],[28,"core::option::Option::"],[29,"cairo::l1_l2::l1_l2::ContractState"],[30,"Tuple"],[31,"core::panics::PanicResult::<(cairo::l1_l2::l1_l2::ContractState, ())>"],[32,"Tuple>"],[33,"Tuple"],[34,"core::panics::PanicResult::<(core::felt252,)>"],[35,"BuiltinCosts"],[36,"System"],[37,"Pedersen"],[38,"core::panics::PanicResult::<(core::array::Span::,)>"],[39,"u32"],[40,"core::option::Option::"],[41,"GasBuiltin"]],"libfunc_names":[[0,"revoke_ap_tracking"],[1,"withdraw_gas"],[2,"branch_align"],[3,"store_temp>"],[4,"function_call"],[5,"store_temp"],[6,"enum_match>"],[7,"struct_deconstruct>"],[8,"array_len"],[9,"snapshot_take"],[10,"drop"],[11,"u32_const<0>"],[12,"rename"],[13,"store_temp"],[14,"u32_eq"],[15,"drop"],[16,"array_new"],[17,"felt252_const<7733229381460288120802334208475838166080759535023995805565484692595>"],[18,"store_temp"],[19,"array_append"],[20,"struct_construct"],[21,"struct_construct>>"],[22,"enum_init,)>, 1>"],[23,"store_temp"],[24,"store_temp"],[25,"store_temp"],[26,"store_temp,)>>"],[27,"get_builtin_costs"],[28,"store_temp"],[29,"withdraw_gas_all"],[30,"struct_construct"],[31,"struct_construct"],[32,"snapshot_take"],[33,"drop"],[34,"store_temp"],[35,"function_call"],[36,"enum_match>"],[37,"struct_deconstruct>"],[38,"snapshot_take"],[39,"store_temp>"],[40,"function_call"],[41,"drop"],[42,"snapshot_take>"],[43,"drop>"],[44,"struct_construct>"],[45,"struct_construct>>"],[46,"enum_init,)>, 0>"],[47,"felt252_const<375233589013918064796019>"],[48,"drop>"],[49,"felt252_const<485748461484230571791265682659113160264223489397539653310998840191492913>"],[50,"function_call"],[51,"enum_match>"],[52,"drop>"],[53,"felt252_const<485748461484230571791265682659113160264223489397539653310998840191492914>"],[54,"function_call"],[55,"felt252_const<485748461484230571791265682659113160264223489397539653310998840191492915>"],[56,"function_call"],[57,"enum_match>"],[58,"drop"],[59,"store_temp"],[60,"function_call"],[61,"felt252_const<485748461484230571791265682659113160264223489397539653310998840191492916>"],[62,"function_call"],[63,"array_snapshot_pop_front"],[64,"enum_init>, 0>"],[65,"store_temp>>"],[66,"store_temp>>"],[67,"jump"],[68,"struct_construct"],[69,"enum_init>, 1>"],[70,"enum_match>>"],[71,"unbox"],[72,"rename"],[73,"enum_init, 0>"],[74,"store_temp>"],[75,"enum_init, 1>"],[76,"struct_deconstruct"],[77,"store_temp"],[78,"function_call"],[79,"struct_construct>"],[80,"enum_init, 0>"],[81,"store_temp>"],[82,"enum_init, 1>"],[83,"store_temp"],[84,"snapshot_take"],[85,"dup"],[86,"felt252_add"],[87,"function_call"],[88,"enum_match>"],[89,"struct_deconstruct>"],[90,"struct_construct>"],[91,"enum_init, 0>"],[92,"store_temp>"],[93,"enum_init, 1>"],[94,"drop"],[95,"felt252_const<0>"],[96,"felt252_sub"],[97,"felt252_is_zero"],[98,"enum_init"],[99,"store_temp"],[100,"drop>"],[101,"enum_init"],[102,"bool_not_impl"],[103,"enum_match"],[104,"felt252_const<6266707773552616950261371518473428817764193460919301733>"],[105,"felt252_const<378966004439632268498427220578784680235448737840>"],[106,"function_call"],[107,"store_temp"],[108,"function_call"],[109,"felt252_const<1627646595364011399485594751105561327407438436945569085029>"],[110,"send_message_to_l1_syscall"],[111,"enum_init>, 0>"],[112,"store_temp>>"],[113,"enum_init>, 1>"],[114,"rename>>"],[115,"function_call>::unwrap::>>"],[116,"enum_match>"],[117,"drop>"],[118,"class_hash_try_from_felt252"],[119,"enum_init, 0>"],[120,"store_temp>"],[121,"enum_init, 1>"],[122,"felt252_const<635936445288268428118008248389254788512490109111352135954440336103015344490>"],[123,"library_call_syscall"],[124,"enum_init, core::array::Array::>, 0>"],[125,"store_temp, core::array::Array::>>"],[126,"enum_init, core::array::Array::>, 1>"],[127,"rename, core::array::Array::>>"],[128,"function_call>::unwrap_syscall>"],[129,"enum_match,)>>"],[130,"drop>>"],[131,"struct_construct"],[132,"store_temp"],[133,"function_call>"],[134,"struct_deconstruct>"],[135,"function_call"],[136,"storage_address_from_base"],[137,"storage_read_syscall"],[138,"enum_init>, 0>"],[139,"store_temp>>"],[140,"enum_init>, 1>"],[141,"rename>>"],[142,"function_call::unwrap_syscall>"],[143,"storage_write_syscall"],[144,"function_call::unwrap_syscall>"],[145,"struct_deconstruct>"],[146,"struct_construct>"],[147,"enum_init, 0>"],[148,"store_temp>"],[149,"enum_init, 1>"],[150,"u128s_from_felt252"],[151,"u128_const<0>"],[152,"struct_construct"],[153,"rename"],[154,"rename"],[155,"struct_deconstruct"],[156,"dup"],[157,"u128_overflowing_sub"],[158,"drop"],[159,"snapshot_take"],[160,"rename"],[161,"u128_eq"],[162,"rename"],[163,"felt252_const<30828113188794245257250221355944970489240709081949230>"],[164,"function_call>::expect::>>"],[165,"struct_construct>"],[166,"enum_init, 0>"],[167,"store_temp>"],[168,"enum_init, 1>"],[169,"enum_match, core::array::Array::>>"],[170,"function_call"],[171,"snapshot_take"],[172,"drop"],[173,"store_temp"],[174,"function_call"],[175,"emit_event_syscall"],[176,"felt252_const<1065622543624526936256554561967983185612257046533136611876836524258158810564>"],[177,"struct_construct"],[178,"struct_deconstruct"],[179,"pedersen"],[180,"storage_base_address_from_felt252"],[181,"store_temp"],[182,"enum_match>>"],[183,"enum_match>>"],[184,"enum_init"],[185,"enum_match"],[186,"felt252_const<1351404224135898955678421854004863207313148821021055261939802623168618957960>"],[187,"function_call"],[188,"dup"],[189,"struct_deconstruct"]],"user_func_names":[[0,"cairo::l1_l2::l1_l2::__wrapper__get_balance"],[1,"cairo::l1_l2::l1_l2::__wrapper__increase_balance"],[2,"cairo::l1_l2::l1_l2::__wrapper__withdraw"],[3,"cairo::l1_l2::l1_l2::__wrapper__withdraw_from_lib"],[4,"cairo::l1_l2::l1_l2::__wrapper__deposit"],[5,"core::Felt252Serde::deserialize"],[6,"cairo::l1_l2::l1_l2::get_balance"],[7,"core::Felt252Serde::serialize"],[8,"cairo::l1_l2::l1_l2::increase_balance"],[9,"cairo::l1_l2::l1_l2::withdraw"],[10,"core::starknet::class_hash::ClassHashSerde::deserialize"],[11,"cairo::l1_l2::l1_l2::withdraw_from_lib"],[12,"cairo::l1_l2::l1_l2::deposit"],[13,"cairo::l1_l2::l1_l2::balances::InternalContractMemberStateImpl::read"],[14,"cairo::l1_l2::l1_l2::balances::InternalContractMemberStateImpl::write"],[15,"core::integer::u256_from_felt252"],[16,"core::integer::U256PartialOrd::lt"],[17,"core::result::ResultTraitImpl::<(), core::array::Array::>::unwrap::>"],[18,"core::starknet::SyscallResultTraitImpl::>::unwrap_syscall"],[19,"cairo::l1_l2::l1_l2::ContractStateEventEmitter::emit::"],[20,"cairo::l1_l2::l1_l2::balances::InternalContractMemberStateImpl::address"],[21,"core::starknet::SyscallResultTraitImpl::::unwrap_syscall"],[22,"core::starknet::SyscallResultTraitImpl::<()>::unwrap_syscall"],[23,"core::result::ResultTraitImpl::<(), core::array::Array::>::expect::>"],[24,"cairo::l1_l2::l1_l2::EventDepositFromL1IntoEvent::into"],[25,"cairo::l1_l2::l1_l2::EventIsEvent::append_keys_and_data"],[26,"cairo::l1_l2::l1_l2::DepositFromL1IsEvent::append_keys_and_data"]]},"contract_class_version":"0.1.0","entry_points_by_type":{"EXTERNAL":[{"selector":"0x15511cc3694f64379908437d6d64458dc76d02482052bfb8a5b33a72c054c77","function_idx":2},{"selector":"0x362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320","function_idx":1},{"selector":"0x3828a1c7b32f69122c14d2b60b415a6620621739d0b65c3228d7001fa92f229","function_idx":3},{"selector":"0x39e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695","function_idx":0}],"L1_HANDLER":[{"selector":"0xc73f681176fc7b3f9693986fd7b14581e8d540519e27400e88b8713932be01","function_idx":4}],"CONSTRUCTOR":[]},"abi":[{"type":"function","name":"get_balance","inputs":[{"name":"user","type":"core::felt252"}],"outputs":[{"type":"core::felt252"}],"state_mutability":"view"},{"type":"function","name":"increase_balance","inputs":[{"name":"user","type":"core::felt252"},{"name":"amount","type":"core::felt252"}],"outputs":[],"state_mutability":"external"},{"type":"function","name":"withdraw","inputs":[{"name":"user","type":"core::felt252"},{"name":"amount","type":"core::felt252"},{"name":"l1_address","type":"core::felt252"}],"outputs":[],"state_mutability":"external"},{"type":"function","name":"withdraw_from_lib","inputs":[{"name":"user","type":"core::felt252"},{"name":"amount","type":"core::felt252"},{"name":"l1_address","type":"core::felt252"},{"name":"message_sender_class_hash","type":"core::starknet::class_hash::ClassHash"}],"outputs":[],"state_mutability":"external"},{"type":"l1_handler","name":"deposit","inputs":[{"name":"from_address","type":"core::felt252"},{"name":"user","type":"core::felt252"},{"name":"amount","type":"core::felt252"}],"outputs":[],"state_mutability":"external"},{"type":"event","name":"cairo::l1_l2::l1_l2::DepositFromL1","kind":"struct","members":[{"name":"user","type":"core::felt252","kind":"key"},{"name":"amount","type":"core::felt252","kind":"key"}]},{"type":"event","name":"cairo::l1_l2::l1_l2::Event","kind":"enum","variants":[{"name":"DepositFromL1","type":"cairo::l1_l2::l1_l2::DepositFromL1","kind":"nested"}]}]} -------------------------------------------------------------------------------- /test/data/simple.cairo: -------------------------------------------------------------------------------- 1 | #[contract] 2 | mod Contract { 3 | struct Storage { 4 | balance: felt252, 5 | } 6 | 7 | #[constructor] 8 | fn constructor(initial_balance: felt252) { 9 | balance::write(initial_balance); 10 | } 11 | 12 | #[external] 13 | fn increase_balance(amount1: felt252, amount2: felt252) { 14 | balance::write(balance::read() + amount1 + amount2); 15 | } 16 | 17 | #[view] 18 | fn get_balance() -> felt252 { 19 | balance::read() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/data/simple.sierra: -------------------------------------------------------------------------------- 1 | {"sierra_program":["0x1","0x1","0x0","0x1","0x1","0x0","0xca","0x36","0x18","0x52616e6765436865636b","0x0","0x4761734275696c74696e","0x66656c74323532","0x4172726179","0x1","0x2","0x536e617073686f74","0x3","0x537472756374","0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62","0x4","0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3","0x456e756d","0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511","0x6","0x753332","0x3288d594b9a45d15bb2fcb7903f06cdb06b27f0ba88186ec4cfaa98307cb972","0x4275696c74696e436f737473","0x53797374656d","0xd3a26a7712a33547a4a74e7594a446ca400cb36a0c2c307b92eff9ce82ff8","0xc","0x5","0x19b3b4955bdcfa379bfc5a4949111c4efdd79128f8676f4d0895419b22e2ad7","0xe","0x2f528e3c691e195fca674982b69c0dc4284f206c3ea4d680220e99b59315a92","0x10","0x426f78","0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7","0x12","0x53746f726167654261736541646472657373","0x53746f7261676541646472657373","0x90d0203c41ad646d024845257a6eceb2f8b59b29ce7420dd518053d2edeedc","0x101dc0399934cc08fa0d6f6f2daead4e4a38cabeea1c743e1fc28d2d6e58e99","0x5d","0x7265766f6b655f61705f747261636b696e67","0x656e61626c655f61705f747261636b696e67","0x77697468647261775f676173","0x6272616e63685f616c69676e","0x73746f72655f74656d70","0x66756e6374696f6e5f63616c6c","0x656e756d5f6d61746368","0x7","0x7374727563745f6465636f6e737472756374","0x61727261795f6c656e","0x7533325f636f6e7374","0x8","0x7533325f6571","0x7374727563745f636f6e737472756374","0x656e756d5f696e6974","0x9","0x6a756d70","0x626f6f6c5f6e6f745f696d706c","0x64726f70","0x6765745f6275696c74696e5f636f737473","0xa","0x77697468647261775f6761735f616c6c","0x64697361626c655f61705f747261636b696e67","0xb","0xd","0x61727261795f6e6577","0x736e617073686f745f74616b65","0xf","0x66656c743235325f636f6e7374","0x4f7574206f6620676173","0x61727261795f617070656e64","0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473","0x496e70757420746f6f2073686f727420666f7220617267756d656e7473","0x11","0x647570","0x61727261795f736e617073686f745f706f705f66726f6e74","0x13","0x756e626f78","0x72656e616d65","0x66656c743235325f616464","0x73746f726167655f626173655f616464726573735f636f6e7374","0x206f38f7e4f15e87567361213c28f235cccdaa1d7fd34c9db1dfe9489c6a091","0x73746f726167655f616464726573735f66726f6d5f62617365","0x15","0x73746f726167655f726561645f73797363616c6c","0x16","0x73746f726167655f77726974655f73797363616c6c","0x17","0x21c","0xffffffffffffffff","0x7d","0x6f","0x60","0x14","0x1b","0x19","0x1a","0x4e","0x1c","0x1d","0x1e","0x1f","0x40","0x20","0x21","0x25","0x26","0x27","0x28","0x22","0x23","0x24","0x29","0x39","0x2a","0x2b","0x2c","0x2d","0x2e","0x2f","0x30","0x31","0x32","0x33","0x34","0x35","0x36","0x37","0x38","0x3a","0x3b","0x3c","0x3d","0x3e","0x3f","0x41","0x44","0x42","0x43","0x45","0x46","0x47","0x48","0x49","0x4a","0x4b","0x4c","0x4d","0x4f","0x50","0x51","0x52","0x53","0x54","0x55","0x56","0x57","0x58","0x59","0x5a","0x5b","0x5c","0x5e","0x5f","0x61","0x62","0x63","0x64","0xe1","0x99","0x9d","0xd1","0xc6","0xbf","0x155","0x147","0x101","0x105","0x136","0x129","0x122","0x169","0x16e","0x179","0x19d","0x197","0x1b5","0x1ba","0x1c5","0x1df","0x1f2","0x1f7","0x202","0x20e","0x218","0x8a","0xee","0x162","0x180","0x1a5","0x1a9","0x1cb","0x1d1","0x1e5","0x208","0x212","0x129c","0x7060f02090e0d02060a0c060b02070a090606080706060502040203020100","0xa090616061502090e10060d02070a02140213100610061202090e02111006","0x706061c09061b061a02090e07060d02070a090619061802090e17060d0207","0x22423090610062202090e090607062102090e0220021f10061e061d02090e","0x2e0706062d1706062c2b06062a020606280909062917060628022702260225","0x607330607320234330606280207330607321006063102302f060628020706","0x606283b06062806060628023a0239380606280237100606363306062a0235","0x63117060631090606360906063e0706063d160606363c06062a0c09062907","0x2907060642410706400706063606073f0607323f06062802073f0607321906","0x70606461b06062c4506062a10090629440706401706063643070640170906","0x32480606280c060628020748060732070606472f0906292b09062909060628","0x2b0607322b06062802072b0607320706064a070606494806062a0607480607","0x6062806073c0607323c06062802073c0607321606063133090629024b0607","0x64a0607510607325106062802075106073202504f060628024e4d07064c10","0x607320252060745060732450606280207450607321b060631380906295106","0x62a5106062a1606062c3b0906295306064a06075306073253060628020753","0x9022b100756170c0755070602070702025506020602025506020202545306","0x2b020c0655060c061002332f075506380617023806550609060c0202550602","0x3c0755063f0617023f0655062f060c020255060209021606573b0655073306","0x61e0633021e0655063c062f020255060209024506581b06550719062b0219","0x20902025302550759480716024806550648063b0259065506023802480655","0x5a06021b025306550651063f02510655064f0619024f065506023c02025506","0x2530655065b063f025b0655060006450200065506023c0202550602090202","0x55060209025e065d5a0655075c0648025c0655065c063f025c06550653061e","0x55075f170c0953025f0655065f0651025f065506024f020255065a06590202","0x7065c026506550661065b0202550602000202550602090264630762616007","0x69095506686766650c5e02680655061b065a02670655063b065a0266065506","0x6c0660020255060209026e066d6c0655076b065f0260065506600610026b6a","0x6710669020255067006640271700755066f0663026f065506026102025506","0x69065b0275065506600610027406550673066b027306550672066a02720655","0x5506020902787776750c067806550674066502770655066a065c0276065506","0x55066a065c027b06550669065b027a06550660061002790655066e06660202","0x667020255060200020255060209026d7c7b7a0c066d065506790665027c06","0x55067e065a027e0655060268027d0655060261020255063b0667020255061b","0x64065b028106550663061002800655067f0666027f0655067e7d076c027e06","0x5506020902836282810c0683065506800665026206550607065c0282065506","0x65c020255063b0667020255061b0667020255065e06590202550602000202","0x26f028706550602610202550686065902868507550684066e028406550607","0x10028a06550689066602890655068887076c028806550688065a0288065506","0x68e0655068a0665028d06550685065c028c06550617065b028b0655060c06","0x255063c067002025506450659020255060200020255060209028e8d8c8b0c","0x6c025d0655065d065a025d0655060271028f0655060261020255063b066702","0x9306550617065b02920655060c0610029106550690066602900655065d8f07","0x20002025506020902959493920c0695065506910665029406550607065c02","0x97065506027102960655060261020255062f06700202550616065902025506","0x55060c0610029906550698066602980655069796076c029706550697065a02","0x9c9b9a0c069d065506990665029c06550607065c029b06550617065b029a06","0x602680223065506026102025506090670020255060200020255060209029d","0x61002580655069f0666029f0655069e23076c029e0655069e065a029e0655","0xc06a306550658066502a206550607065c02a10655062b065b02a006550610","0x22b1007a4170c0755070602070702025506020602025506020202a3a2a1a0","0x63b0238065506023802330655062f0633022f06550609062f020255060209","0x3c0202550602090202a502550738330716020c0655060c0610023306550633","0x602090202a606021b023c06550616063f02160655063b0619023b06550602","0x55063c061e023c0655063f063f023f0655061906450219065506023c020255","0x60200020255060209021e06a7450655071b0648021b0655061b063f021b06","0x748170c095302480655064806510248065506024f02025506450659020255","0x25e06550607065c025a0655064f065b02025506020902535107a84f590755","0x9026006a95f0655075c06730259065506590610025c5b000955065e5a0772","0x6670269640755066106750263065506026102610655065f06740202550602","0x26b6a075506666507770266065506630676026506550669065a0202550664","0x26c065506680669020255066706640268670755066a0663020255066b0659","0x7106550600065b0270065506590610026f0655066e066b026e0655066c066a","0x66602025506020902737271700c06730655066f066502720655065b065c02","0x6502770655065b065c027606550600065b0275065506590610027406550660","0x6550602680279065506026102025506020902787776750c06780655067406","0x6510610027c0655067b0666027b0655067a79076c027a0655067a065a027a","0x7d6d0c067f0655067c0665027e06550607065c027d06550653065b026d0655","0x6e028206550607065c020255061e0659020255060200020255060209027f7e","0x65a0283065506026f02620655060261020255068106590281800755068206","0x2840655060c0610028606550685066602850655068362076c028306550683","0x902898887840c0689065506860665028806550680065c028706550617065b","0x8b0655060268028a0655060261020255060906700202550602000202550602","0x5506100610028d0655068c0666028c0655068b8a076c028b0655068b065a02","0x5d8f8e0c06900655068d0665025d06550607065c028f0655062b065b028e06","0x60209022b1007aa170c075507060207070202550602060202550602020290","0x33062b020c0655060c061002332f075506380617023806550609060c020255","0x3802190655063c0633023c0655062f062f020255060209021606ab3b065507","0x3c0202550602090202ac0255073f190716021906550619063b023f06550602","0x602090202ad06021b021e06550645063f02450655061b0619021b06550602","0x55061e061e021e06550659063f02590655064806450248065506023c020255","0x510659020255060209025306ae510655074f0648024f0655064f063f024f06","0x7af5c5b07550700170c095302000655060006510200065506024f02025506","0x26406550607065c02630655065c065b020255060200020255060209025e5a","0x61065f025b0655065b06100261605f095506696463097802690655063b065a","0x65066302650655060261020255066a0660020255060209026b06b06a065507","0x66b026c06550668066a026806550667066902025506660664026766075506","0x65027106550660065c02700655065f065b026f0655065b0610026e0655066c","0x65b061002730655066b0666020255060209027271706f0c06720655066e06","0x75740c0677065506730665027606550660065c02750655065f065b02740655","0x26802780655060261020255063b0667020255060200020255060209027776","0x10027b0655067a0666027a0655067978076c027906550679065a0279065506","0x67e0655067b0665027d06550607065c026d0655065e065b027c0655065a06","0x255063b066702025506530659020255060200020255060209027e7d6d7c0c","0x8206550602610202550680065902807f07550681066e028106550607065c02","0x6550683066602830655066282076c026206550662065a0262065506026f02","0x550685066502870655067f065c028406550617065b02860655060c06100285","0x2f06700202550616065902025506020002025506020902888784860c068806","0x68a89076c028a0655068a065a028a06550602710289065506026102025506","0x7065c028e06550617065b028d0655060c0610028c0655068b0666028b0655","0x20255060200020255060209025d8f8e8d0c065d0655068c0665028f065506","0x76c029106550691065a029106550602680290065506026102025506090670","0x2950655062b065b0294065506100610029306550692066602920655069190","0x79020606550602062f02979695940c0697065506930665029606550607065c","0x6550607067b021706550609067a020255060209020c06b109070755070606","0x66d022f065506023c0202550602090202b206021b022b06550617067c0210","0xc0238065506100669022b06550633067c02100655060c067b02330655062f","0x3c0655063b067e020255060209021606b33b0655072b067d02380655063806","0x6550638060c023f06550619068002190655063c067f023c0655063c065a02","0x5506023c0202550616065902025506020902451b0706450655063f0681021b","0x24f5907064f065506480681025906550638060c02480655061e0682021e06","0x71006730210170c0955062f2b0772022f06550606065c022b06550602065b","0x5a0216065506073b0762023b065506330674020255060209023806b4330655","0x1e06550617065c02450655060c065b023c0655060916076202160655061606","0x4f06b5590655071b065f021b3f19095506481e45098302480655063c065a02","0x68602530655065106850251065506023c0202550659066002025506020902","0x9065a065506000684025c0655063f065c025b06550619065b020006550653","0x55063f065c025f06550619065b025e0655064f0687020255060209025a5c5b","0x667020255060906670202550602090261605f0906610655065e0684026006","0x684026906550617065c02640655060c065b02630655063806870202550607","0x5506060688020706550602065c0206065506023c026a696409066a06550663","0x63b020c0655060238020906550607068a0207065506028902090707060906","0x38332f09b62b1017095507090c06020c8c020906550609068b020c0655060c","0x23c06550610065c021606550617065b023b0655062b068d02025506020902","0x2f065b023f06550638068f0202550602090202b706021b02190655063b068e","0x690024506550619065d02190655063f068e023c06550633065c0216065506","0x2590655061e0674020255060209024806b81e0655071b0673021b06550645","0x655063c065c025306550616065b02510655064f0692024f065506590691","0x16065b025c065506480694020255060209025b005309065b06550651069302","0x550602067f025f5e5a09065f0655065c0693025e0655063c065c025a065506","0x60c06880217065506090676020c065506023c02090655060706076c020706","0x2f06550607065a022b06550606065c021006550602065b0210170706100655","0x60020255060209023806b93306550717065f02170c090955062f2b10098302","0x65b023c06550616068602160655063b0685023b065506023c020255063306","0x55060209021b3f1909061b0655063c0684023f0655060c065c021906550609","0x550645068402480655060c065c021e06550609065b02450655063806870202","0x3b02170655060238020c06550609068a020906550602890259481e09065906","0x332f09ba2b10075507070c1706021795020c0655060c068b02170655061706","0x23c06550610065b02160655063b0696023b065506023c0202550602090238","0x3806980202550602090202bb06021b023f06550616069702190655062b065c","0x699023f0655061b0697021906550633065c023c0655062f065b021b065506","0x20255060209025906bc4806550745065f02450655061e069a021e0655063f","0x655063c065b025306550651068602510655064f0685024f06550648069b","0x590687020255060209025c5b0009065c065506530684025b06550619065c02","0x5e0906600655065a0684025f06550619065c025e0655063c065b025a065506","0x906920209065506060691020255060209020706bd0606550702069c02605f","0x93021006550607069402025506020902170606170655060c0693020c065506","0x6060685020255060209020706be0606550702069d022b06062b0655061006","0x68702025506020902170606170655060c0684020c06550609068602090655","0x90706023f3b06020c173b06020c162b06062b065506100684021006550607","0xc0090706023f3b06020c173b06020cbf090706023f3b06020c173b06020c02","0x3b0607c302103b073b06c2090706023c3b060907073b060cc1022b17071706","0x3b0609c60706023c3b0609073b0609c50602100907090707c40602453b0609","0xc9023c065306c80245065106c70706023c3b060907"],"sierra_program_debug_info":{"type_names":[],"libfunc_names":[],"user_func_names":[]},"contract_class_version":"0.1.0","entry_points_by_type":{"EXTERNAL":[{"selector":"0x362398bec32bc0ebb411203221a35a0301193a96f317ebe5e40be9f60d15320","function_idx":0},{"selector":"0x39e11d48192e4333233c7eb19d10ad67c362bb28580c604d67884c85da39695","function_idx":1}],"L1_HANDLER":[],"CONSTRUCTOR":[{"selector":"0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194","function_idx":2}]},"abi":[{"type":"function","name":"constructor","inputs":[{"name":"initial_balance","type":"core::felt252"}],"outputs":[],"state_mutability":"external"},{"type":"function","name":"increase_balance","inputs":[{"name":"amount1","type":"core::felt252"},{"name":"amount2","type":"core::felt252"}],"outputs":[],"state_mutability":"external"},{"type":"function","name":"get_balance","inputs":[],"outputs":[{"type":"core::felt252"}],"state_mutability":"view"}]} -------------------------------------------------------------------------------- /test/devnet-persistence.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import { DevnetProvider } from ".."; 4 | import { expect } from "chai"; 5 | 6 | describe("Devnet persistence", async function () { 7 | this.timeout(10_000); // ms 8 | 9 | const WORKDIR = "."; 10 | const DUMP_EXTENSION = ".dump.json"; 11 | 12 | const DUMMY_ADDRESS = "0x1"; 13 | const DUMMY_AMOUNT = 20n; 14 | 15 | const devnetProvider = new DevnetProvider(); 16 | 17 | function removeDumps() { 18 | for (const fileName of fs.readdirSync(WORKDIR)) { 19 | if (fileName.endsWith(DUMP_EXTENSION)) { 20 | const file = path.join(WORKDIR, fileName); 21 | fs.unlinkSync(file); 22 | } 23 | } 24 | } 25 | 26 | beforeEach("clear workdir and Devnet state", async function () { 27 | removeDumps(); 28 | await devnetProvider.restart(); 29 | }); 30 | 31 | afterEach("clear workdir", function () { 32 | removeDumps(); 33 | }); 34 | 35 | function getRandomDumpPath() { 36 | const name = `persisted_devnet_${Math.random().toString().slice(2)}${DUMP_EXTENSION}`; 37 | return path.join(WORKDIR, name); 38 | } 39 | 40 | async function dummyMint() { 41 | const { new_balance } = await devnetProvider.mint(DUMMY_ADDRESS, DUMMY_AMOUNT); 42 | return new_balance; 43 | } 44 | 45 | it("should dump and load", async function () { 46 | const dumpPath = getRandomDumpPath(); 47 | 48 | const balanceBeforeDump = await dummyMint(); 49 | await devnetProvider.dump(dumpPath); 50 | 51 | const balanceBeforeLoad = await dummyMint(); 52 | expect(balanceBeforeLoad).to.equal(balanceBeforeDump + BigInt(DUMMY_AMOUNT)); 53 | 54 | await devnetProvider.load(dumpPath); 55 | const finalBalance = await dummyMint(); 56 | 57 | expect(finalBalance).to.equal(balanceBeforeDump + BigInt(DUMMY_AMOUNT)); 58 | }); 59 | 60 | it("should dump without providing a path in request", async function () { 61 | await dummyMint(); // dumping is skipped if there are no transactions, so we add one 62 | await devnetProvider.dump(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/devnet-provider.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, assert } from "chai"; 2 | import { BalanceUnit, Devnet, DevnetProvider, DevnetProviderError, MintResponse } from ".."; 3 | import * as starknet from "starknet"; 4 | import { 5 | ETH_TOKEN_CONTRACT_ADDRESS, 6 | STRK_TOKEN_CONTRACT_ADDRESS, 7 | getAccountBalance, 8 | getEnvVar, 9 | } from "./util"; 10 | 11 | describe("DevnetProvider", function () { 12 | const devnetProvider = new DevnetProvider(); 13 | const starknetProvider = new starknet.RpcProvider({ nodeUrl: devnetProvider.url }); 14 | 15 | const DUMMY_ADDRESS = "0x1"; 16 | const DUMMY_AMOUNT = 20n; 17 | 18 | beforeEach("restart the state", async function () { 19 | await devnetProvider.restart(); 20 | }); 21 | 22 | it("should have a healthcheck endpoint", async function () { 23 | const alive = await devnetProvider.isAlive(); 24 | expect(alive).to.be.true; 25 | }); 26 | 27 | it("should have predeployed accounts", async function () { 28 | const accounts = await devnetProvider.getPredeployedAccounts(); 29 | expect(accounts).length.to.be.greaterThan(0); 30 | }); 31 | 32 | it("should have configurable timeout", async function () { 33 | const insufficientTimeoutProvider = new DevnetProvider({ timeout: 1 /* ms */ }); 34 | try { 35 | // dummy action that takes more time than the too short timeout 36 | await insufficientTimeoutProvider.mint(DUMMY_ADDRESS, DUMMY_AMOUNT); 37 | assert.fail("Should have timed out, got response instead"); 38 | } catch (err) { 39 | const typedErr = err as DevnetProviderError; 40 | expect(typedErr.message).to.contain("timeout"); 41 | expect(typedErr.message).to.contain("exceeded"); 42 | } 43 | }); 44 | 45 | function assertMintingResponse( 46 | resp: MintResponse, 47 | expectedAmount: bigint, 48 | expectedUnit: BalanceUnit, 49 | ) { 50 | expect(resp.tx_hash).to.match(/^0x[0-9a-fA-F]+/); 51 | expect(resp.new_balance).to.equal(BigInt(expectedAmount)); 52 | expect(resp.unit).to.equal(expectedUnit); 53 | } 54 | 55 | async function assertBalance( 56 | accountAddress: string, 57 | expectedAmount: bigint, 58 | tokenContractAddress: string, 59 | ) { 60 | const actualBalance = await getAccountBalance(accountAddress, starknetProvider, { 61 | tokenContractAddress, 62 | }); 63 | expect(actualBalance).to.equal(expectedAmount); 64 | } 65 | 66 | describe("minting", function () { 67 | it("works for WEI", async function () { 68 | const mintResp = await devnetProvider.mint(DUMMY_ADDRESS, DUMMY_AMOUNT, "WEI"); 69 | assertMintingResponse(mintResp, DUMMY_AMOUNT, "WEI"); 70 | await assertBalance(DUMMY_ADDRESS, DUMMY_AMOUNT, ETH_TOKEN_CONTRACT_ADDRESS); 71 | await assertBalance(DUMMY_ADDRESS, 0n, STRK_TOKEN_CONTRACT_ADDRESS); 72 | }); 73 | 74 | it("works for FRI", async function () { 75 | const mintResp = await devnetProvider.mint(DUMMY_ADDRESS, DUMMY_AMOUNT, "FRI"); 76 | assertMintingResponse(mintResp, DUMMY_AMOUNT, "FRI"); 77 | await assertBalance(DUMMY_ADDRESS, DUMMY_AMOUNT, STRK_TOKEN_CONTRACT_ADDRESS); 78 | await assertBalance(DUMMY_ADDRESS, 0n, ETH_TOKEN_CONTRACT_ADDRESS); 79 | }); 80 | 81 | it("works without specifying the unit", async function () { 82 | const mintResp = await devnetProvider.mint(DUMMY_ADDRESS, DUMMY_AMOUNT); 83 | assertMintingResponse(mintResp, DUMMY_AMOUNT, "FRI"); 84 | await assertBalance(DUMMY_ADDRESS, DUMMY_AMOUNT, STRK_TOKEN_CONTRACT_ADDRESS); 85 | await assertBalance(DUMMY_ADDRESS, 0n, ETH_TOKEN_CONTRACT_ADDRESS); 86 | }); 87 | 88 | it("should reflect the minted amount in predeployed accounts info", async function () { 89 | const accountIndex = 0; 90 | const accountsBefore = await devnetProvider.getPredeployedAccounts(); 91 | const accountBefore = accountsBefore[accountIndex]; 92 | 93 | expect(accountBefore.balance).to.be.null; // balance not included if not requested 94 | 95 | await devnetProvider.mint(accountBefore.address, DUMMY_AMOUNT, "WEI"); 96 | 97 | const accountsAfter = await devnetProvider.getPredeployedAccounts({ 98 | withBalance: true, 99 | }); 100 | const accountAfter = accountsAfter[accountIndex]; 101 | 102 | const expectedAmount = BigInt(accountBefore.initial_balance) + BigInt(DUMMY_AMOUNT); 103 | expect(accountAfter.balance).to.deep.equal({ 104 | eth: { 105 | amount: expectedAmount.toString(), 106 | unit: "WEI", 107 | }, 108 | strk: { amount: accountBefore.initial_balance, unit: "FRI" }, 109 | }); 110 | }); 111 | 112 | it("works with large amount multiple of 10", async function () { 113 | const amount = 10n ** 30n; 114 | const resp = await devnetProvider.mint(DUMMY_ADDRESS, amount, "WEI"); 115 | assertMintingResponse(resp, amount, "WEI"); 116 | await assertBalance(DUMMY_ADDRESS, amount, ETH_TOKEN_CONTRACT_ADDRESS); 117 | }); 118 | 119 | it("works with large amount with non-zero ones digit", async function () { 120 | const amount = 10n ** 30n + 1n; 121 | const resp = await devnetProvider.mint(DUMMY_ADDRESS, amount, "WEI"); 122 | assertMintingResponse(resp, amount, "WEI"); 123 | await assertBalance(DUMMY_ADDRESS, amount, ETH_TOKEN_CONTRACT_ADDRESS); 124 | }); 125 | }); 126 | 127 | describe("block manipulation", function () { 128 | it("should create new block", async function () { 129 | const originalLatestBlock = await starknetProvider.getBlockLatestAccepted(); 130 | 131 | const { block_hash: createdBlockHash } = await devnetProvider.createBlock(); 132 | 133 | const newLatestBlock = await starknetProvider.getBlockLatestAccepted(); 134 | expect(newLatestBlock).to.deep.equal({ 135 | block_hash: createdBlockHash, 136 | block_number: originalLatestBlock.block_number + 1, 137 | }); 138 | }); 139 | 140 | it("should abort blocks", async function () { 141 | const originalLatestBlock = await starknetProvider.getBlockLatestAccepted(); 142 | 143 | const { block_hash: createdBlockHash1 } = await devnetProvider.createBlock(); 144 | const { block_hash: createdBlockHash2 } = await devnetProvider.createBlock(); 145 | 146 | const { aborted } = await devnetProvider.abortBlocks(createdBlockHash1); 147 | expect(aborted).to.deep.equal([createdBlockHash2, createdBlockHash1]); 148 | 149 | const newLatestBlock = await starknetProvider.getBlockLatestAccepted(); 150 | expect(newLatestBlock).to.deep.equal(originalLatestBlock); 151 | }); 152 | 153 | it("should fail when aborting non-existent blocks", async function () { 154 | const nonExistentBlockHash = "0x42"; 155 | try { 156 | const resp = await devnetProvider.abortBlocks(nonExistentBlockHash); 157 | assert.fail(`Should have failed but got: ${resp}`); 158 | } catch (err) { 159 | expect(err).to.deep.equal({ 160 | code: -1, 161 | message: "No block found", 162 | }); 163 | } 164 | }); 165 | }); 166 | 167 | describe("time manipulation", function () { 168 | it("should set time after manually generating a block", async function () { 169 | const originalBlock = await starknetProvider.getBlock("latest"); 170 | 171 | const futureTime = originalBlock.timestamp * 2; 172 | const { block_hash: blockHash } = await devnetProvider.setTime(futureTime); 173 | 174 | // since block generation was not requested as part of time setting 175 | expect(blockHash).to.be.null; 176 | 177 | const { block_hash: newBlockHash } = await devnetProvider.createBlock(); 178 | const newBlock = await starknetProvider.getBlockWithTxHashes(newBlockHash); 179 | // expecting with `equal` may result in unwanted discrepancies 180 | expect(newBlock.timestamp).to.be.greaterThanOrEqual(futureTime); 181 | }); 182 | 183 | it("should generate a block and set time in one request", async function () { 184 | const originalBlock = await starknetProvider.getBlock("latest"); 185 | 186 | const futureTime = originalBlock.timestamp * 2; 187 | const { block_hash: blockHash } = await devnetProvider.setTime(futureTime, { 188 | generateBlock: true, 189 | }); 190 | 191 | const newBlock = await starknetProvider.getBlock("latest"); 192 | expect(newBlock.block_hash).to.equal(blockHash); 193 | expect(newBlock.timestamp).to.be.greaterThanOrEqual(futureTime); 194 | }); 195 | 196 | it("should increase time", async function () { 197 | const originalBlock = await starknetProvider.getBlock("latest"); 198 | 199 | const timeIncrement = 100; 200 | const { block_hash: newBlockHash } = await devnetProvider.increaseTime(timeIncrement); 201 | 202 | const newBlock = await starknetProvider.getBlock("latest"); 203 | expect(newBlock.block_hash).to.equal(newBlockHash); 204 | expect(newBlock.timestamp).to.be.greaterThanOrEqual( 205 | originalBlock.timestamp + timeIncrement, 206 | ); 207 | }); 208 | }); 209 | 210 | it("should retrieve correct config", async function () { 211 | // The existing background Devnet has one config 212 | const oldConfig = await devnetProvider.getConfig(); 213 | 214 | // The newly spawned Devnet shall have its own config with the account number modified 215 | const totalAccounts = 3; 216 | const args = ["--accounts", totalAccounts.toString()]; 217 | const customizedDevnet = await Devnet.spawnCommand(getEnvVar("DEVNET_PATH"), { args }); 218 | 219 | const customizedConfig = await customizedDevnet.provider.getConfig(); 220 | 221 | expect(customizedConfig.total_accounts).to.not.equal(oldConfig.total_accounts); 222 | expect(customizedConfig.total_accounts).to.equal(totalAccounts); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /test/devnet-spawn.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai"; 2 | import { Devnet, DevnetProvider, GithubError } from ".."; 3 | import { getEnvVar, sleep } from "./util"; 4 | import path from "path"; 5 | import tmp from "tmp"; 6 | import fs from "fs"; 7 | 8 | describe("Spawnable Devnet", function () { 9 | this.timeout(5000); 10 | 11 | let devnetPath: string; 12 | let devnetVersion: string; 13 | let oldEnv: NodeJS.ProcessEnv; 14 | 15 | before(function () { 16 | // a predefined path corresponding to a Devnet executable 17 | devnetPath = getEnvVar("DEVNET_PATH"); 18 | 19 | // a semver string corresponding to a compatible Devnet version 20 | devnetVersion = getEnvVar("DEVNET_VERSION"); 21 | 22 | // clone environment to later restore it; some tests may modify it 23 | oldEnv = Object.assign({}, process.env); 24 | }); 25 | 26 | afterEach(function () { 27 | process.env = oldEnv; 28 | }); 29 | 30 | it("should be spawnable by command in PATH", async function () { 31 | const devnetDir = path.dirname(devnetPath); 32 | process.env.PATH += `:${devnetDir}`; 33 | 34 | // command expects Devnet to be available in PATH 35 | const devnet = await Devnet.spawnInstalled(); 36 | expect(await devnet.provider.isAlive()).to.be.true; 37 | }); 38 | 39 | it("should be spawnable via path and killable", async function () { 40 | const devnet = await Devnet.spawnCommand(devnetPath); 41 | expect(await devnet.provider.isAlive()).to.be.true; 42 | 43 | // the subprocess is killed automatically on program exit, 44 | // but here it is demonstrated that it can be killed on request 45 | const success = devnet.kill(); 46 | expect(success).to.be.true; 47 | 48 | // could work without the sleep, but on slower systems it needs some time to die in peace 49 | await sleep(2000); 50 | 51 | expect(await devnet.provider.isAlive()).to.be.false; 52 | }); 53 | 54 | it("should spawn multiple Devnets at different ports", async function () { 55 | const devnet = await Devnet.spawnCommand(devnetPath); 56 | const anotherDevnet = await Devnet.spawnCommand(devnetPath); 57 | 58 | expect(devnet.provider.url).to.not.be.equal(anotherDevnet.provider.url); 59 | 60 | expect(await devnet.provider.isAlive()).to.be.true; 61 | expect(await anotherDevnet.provider.isAlive()).to.be.true; 62 | }); 63 | 64 | it("should fail if command does not exist", async function () { 65 | const invalidCommand = "some-certainly-invald-command"; 66 | try { 67 | await Devnet.spawnCommand(invalidCommand); 68 | assert.fail("Should have failed earlier"); 69 | } catch (err) { 70 | expect(err).to.have.property("code").equal("ENOENT"); 71 | } 72 | }); 73 | 74 | it("should fail if invalid CLI param", async function () { 75 | try { 76 | await Devnet.spawnCommand(devnetPath, { args: ["--faulty-param", "123"] }); 77 | assert.fail("Should have failed earlier"); 78 | } catch (err) { 79 | expect(err).to.contain("Devnet exited"); 80 | expect(err).to.contain("Check Devnet's logged output"); 81 | } 82 | }); 83 | 84 | it("should log errors to a file", async function () { 85 | const stdoutFile = tmp.fileSync(); 86 | const stderrFile = tmp.fileSync(); 87 | try { 88 | await Devnet.spawnCommand(devnetPath, { 89 | args: ["--faulty-param", "123"], 90 | stdout: stdoutFile.fd, 91 | stderr: stderrFile.fd, 92 | }); 93 | assert.fail("Should have failed earlier"); 94 | } catch (err) { 95 | const stdoutContent = fs.readFileSync(stdoutFile.name).toString(); 96 | expect(stdoutContent).to.be.empty; 97 | 98 | const stderrContent = fs.readFileSync(stderrFile.name).toString(); 99 | expect(stderrContent).to.contain("unexpected argument '--faulty-param'"); 100 | } 101 | }); 102 | 103 | it("should log non-error output to a file", async function () { 104 | const stdoutFile = tmp.fileSync(); 105 | const stderrFile = tmp.fileSync(); 106 | 107 | const dummyPort = 1234; // assuming it's free 108 | await Devnet.spawnCommand(devnetPath, { 109 | args: ["--port", dummyPort.toString()], 110 | stdout: stdoutFile.fd, 111 | stderr: stderrFile.fd, 112 | }); 113 | 114 | const stdoutContent = fs.readFileSync(stdoutFile.name).toString(); 115 | expect(stdoutContent).to.contain(`Starknet Devnet listening on 127.0.0.1:${dummyPort}`); 116 | 117 | const stderrContent = fs.readFileSync(stderrFile.name).toString(); 118 | expect(stderrContent).to.be.empty; 119 | }); 120 | 121 | it("should use the specified ports", async function () { 122 | const dummyPort = 2345; // assuming it's free 123 | 124 | const provider = new DevnetProvider({ url: `http://127.0.0.1:${dummyPort}` }); 125 | expect(await provider.isAlive()).to.be.false; 126 | 127 | const devnet = await Devnet.spawnCommand(devnetPath, { 128 | args: ["--port", dummyPort.toString()], 129 | }); 130 | 131 | expect(provider.url).to.equal(devnet.provider.url); 132 | expect(await provider.isAlive()).to.be.true; 133 | }); 134 | 135 | it("should pass CLI args", async function () { 136 | const predeployedAccountsNumber = 13; 137 | const initialBalance = "123"; 138 | const devnet = await Devnet.spawnCommand(devnetPath, { 139 | args: [ 140 | "--accounts", 141 | predeployedAccountsNumber.toString(), 142 | "--initial-balance", 143 | initialBalance, 144 | ], 145 | }); 146 | 147 | const predeployedAccounts = await devnet.provider.getPredeployedAccounts(); 148 | expect(predeployedAccounts).to.have.lengthOf(predeployedAccountsNumber); 149 | expect(predeployedAccounts[0].initial_balance).to.equal(initialBalance); 150 | }); 151 | 152 | it("should spawn by version", async function () { 153 | const devnet = await Devnet.spawnVersion(`v${devnetVersion}`); 154 | expect(await devnet.provider.isAlive()).to.be.true; 155 | }); 156 | 157 | it("should spawn if specifying 'latest'", async function () { 158 | const devnet = await Devnet.spawnVersion("latest"); 159 | expect(await devnet.provider.isAlive()).to.be.true; 160 | }); 161 | 162 | it("should fail if missing 'v' in version specifier", async function () { 163 | try { 164 | await Devnet.spawnVersion(devnetVersion); 165 | assert.fail("Should have failed earlier"); 166 | } catch (err) { 167 | const typedErr = err as GithubError; 168 | expect(typedErr.message).to.contain("Version not found"); 169 | } 170 | }); 171 | 172 | it("should fail if version does not exist", async function () { 173 | try { 174 | await Devnet.spawnVersion("v1.2.345"); 175 | assert.fail("Should have failed earlier"); 176 | } catch (err) { 177 | const typedErr = err as GithubError; 178 | expect(typedErr.message).to.contain("Version not found"); 179 | } 180 | }); 181 | }); 182 | -------------------------------------------------------------------------------- /test/gas.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, assert } from "chai"; 2 | import * as starknet from "starknet"; 3 | import { Devnet } from ".."; 4 | import { 5 | expectHexEquality, 6 | getContractArtifact, 7 | getEnvVar, 8 | getPredeployedAccount, 9 | toPrefixedHex, 10 | } from "./util"; 11 | import { 12 | SIMPLE_CONTRACT_SIERRA_HASH, 13 | SIMPLE_CONTRACT_PATH, 14 | SIMPLE_CONTRACT_CASM_HASH, 15 | } from "./constants"; 16 | 17 | describe("Gas price modification", function () { 18 | this.timeout(10_000); // ms 19 | 20 | // Defined in beforeEach hook 21 | let devnet: Devnet; 22 | let starknetProvider: starknet.RpcProvider; 23 | let account: starknet.Account; 24 | 25 | // Gas prices 26 | const initialL1GasPrice = 1000n; 27 | const initialL1DataGasPrice = 10n; 28 | const initialL2GasPrice = 10n; 29 | const loweredL1DataGasPrice = initialL1DataGasPrice - 1n; 30 | const loweredL2GasPrice = initialL2GasPrice - 1n; 31 | 32 | // Data used in declaration tx 33 | const contractArtifact = getContractArtifact(SIMPLE_CONTRACT_PATH); 34 | const compiledClassHash = SIMPLE_CONTRACT_CASM_HASH; 35 | const txConfig = { 36 | resourceBounds: { 37 | l1_gas: { 38 | max_amount: "0x0", 39 | max_price_per_unit: toPrefixedHex(initialL1GasPrice), 40 | }, 41 | l1_data_gas: { 42 | max_amount: toPrefixedHex(1000n), 43 | max_price_per_unit: toPrefixedHex(loweredL1DataGasPrice), 44 | }, 45 | l2_gas: { 46 | max_amount: toPrefixedHex(BigInt(1e19)), 47 | max_price_per_unit: toPrefixedHex(loweredL2GasPrice), 48 | }, 49 | }, 50 | }; 51 | 52 | beforeEach("Set up Devnet and account", async function () { 53 | devnet = await Devnet.spawnCommand(getEnvVar("DEVNET_PATH"), { 54 | args: [ 55 | "--gas-price-fri", 56 | initialL1GasPrice.toString(), 57 | "--data-gas-price-fri", 58 | initialL1DataGasPrice.toString(), 59 | "--l2-gas-price-fri", 60 | initialL2GasPrice.toString(), 61 | ], 62 | }); 63 | starknetProvider = new starknet.RpcProvider({ nodeUrl: devnet.provider.url }); 64 | account = await getPredeployedAccount(devnet.provider, starknetProvider); 65 | 66 | await expectInsufficientResourceDeclaration(txConfig); 67 | }); 68 | 69 | async function expectInsufficientResourceDeclaration(txConfig: starknet.UniversalDetails) { 70 | try { 71 | await account.declare({ contract: contractArtifact, compiledClassHash }, txConfig); 72 | assert.fail("Should have failed"); 73 | } catch (err) { 74 | const rpcError = err as starknet.RpcError; 75 | expect(rpcError.baseError).to.deep.equal({ 76 | code: 53, 77 | message: 78 | "The transaction's resources don't cover validation or the minimal transaction fee", 79 | }); 80 | } 81 | } 82 | 83 | it("should lower all prices at once and generate a new block that allows tx execution", async function () { 84 | // Lower the required gas prices 85 | const modifiedPrices = await devnet.provider.setGasPrice( 86 | { 87 | l1DataGasPrice: loweredL1DataGasPrice, 88 | l2GasPrice: loweredL2GasPrice, 89 | }, 90 | true, // create new block with modified gas prices 91 | ); 92 | expect(modifiedPrices.l1_data_gas_price).to.equal(loweredL1DataGasPrice); 93 | expect(modifiedPrices.l1_data_gas_price).to.equal(loweredL1DataGasPrice); 94 | 95 | const declaration = await account.declare( 96 | { contract: contractArtifact, compiledClassHash }, 97 | txConfig, 98 | ); 99 | expectHexEquality(declaration.class_hash, SIMPLE_CONTRACT_SIERRA_HASH); 100 | }); 101 | 102 | it("should sequentially lower the prices and allow tx execution after block creation", async function () { 103 | // lower l1_data_gas price; still expect failure 104 | let modifiedPrices = await devnet.provider.setGasPrice({ 105 | l1DataGasPrice: loweredL1DataGasPrice, 106 | }); 107 | await expectInsufficientResourceDeclaration(txConfig); 108 | expect(modifiedPrices.l1_data_gas_price).to.equal(loweredL1DataGasPrice); 109 | 110 | // lower l2_gas price; still expect failure 111 | modifiedPrices = await devnet.provider.setGasPrice({ l2GasPrice: loweredL2GasPrice }); 112 | await expectInsufficientResourceDeclaration(txConfig); 113 | expect(modifiedPrices.l1_data_gas_price).to.equal(loweredL1DataGasPrice); 114 | expect(modifiedPrices.l2_gas_price).to.equal(loweredL2GasPrice); 115 | 116 | // assert successful declaration after gas prices are actually changed 117 | await devnet.provider.createBlock(); 118 | const declaration = await account.declare( 119 | { contract: contractArtifact, compiledClassHash }, 120 | txConfig, 121 | ); 122 | expectHexEquality(declaration.class_hash, SIMPLE_CONTRACT_SIERRA_HASH); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /test/l1-l2-postman.test.ts: -------------------------------------------------------------------------------- 1 | import * as starknet from "starknet"; 2 | import { DevnetProvider } from ".."; 3 | import * as ethers from "ethers"; 4 | import { expect } from "chai"; 5 | import { expectHexEquality, getContractArtifact, getPredeployedAccount } from "./util"; 6 | 7 | const HEX_REGEX = /^0x[0-9A-Fa-f]+/; 8 | 9 | /** Postman is the named of Starknet's L1-L2 messaging utility. */ 10 | describe("Postman", function () { 11 | this.timeout(60_000); // ms 12 | 13 | /** Assumes there is a running Devnet instance. */ 14 | const devnetProvider = new DevnetProvider(); 15 | const l2Provider = new starknet.RpcProvider({ nodeUrl: devnetProvider.url }); 16 | 17 | /** 18 | * Assumes a running L1 provider, e.g. anvil: https://github.com/foundry-rs/foundry/tree/master/crates/anvil 19 | * Using the default host and port. 20 | */ 21 | const L1_URL = "http://127.0.0.1:8545"; 22 | const l1Provider = new ethers.JsonRpcProvider(L1_URL); 23 | 24 | const user = 1n; 25 | 26 | let l2Account: starknet.Account; 27 | let l2Contract: starknet.Contract; 28 | /** Address of deployed mock Starknet messaging contract on L1. */ 29 | let messagingContractAddress: string; 30 | let l1L2Example: ethers.Contract; 31 | 32 | before(async function () { 33 | await devnetProvider.restart(); 34 | 35 | // Load the messaging contract needed for L1-L2 communication. By omitting the contract 36 | // address, we let Devnet deploy it and return one for us. A custom messaging contract 37 | // can be deployed and its address provided to the loading function, as witnessed in a 38 | // later test. The contract sources can be found in the same directory as the artifacts. 39 | const messagingLoadResponse = await devnetProvider.postman.loadL1MessagingContract( 40 | L1_URL, 41 | // If specifying a custom `deployer_account_private_key`, set `address` to null 42 | ); 43 | messagingContractAddress = messagingLoadResponse.messaging_contract_address; 44 | 45 | // First deploy the L2 contract. 46 | l2Account = await getPredeployedAccount(devnetProvider, l2Provider); 47 | const l2Sierra = getContractArtifact("test/data/l1_l2.sierra"); 48 | const l2ContractDeployment = await l2Account.declareAndDeploy({ 49 | contract: l2Sierra, 50 | compiledClassHash: "0x02548c46a426421b5156ebbdd9a1ee0a32ec4588af5c9a68d636725cfa11d300", 51 | }); 52 | l2Contract = new starknet.Contract( 53 | l2Sierra.abi, 54 | l2ContractDeployment.deploy.contract_address, 55 | l2Provider, 56 | ); 57 | l2Contract.connect(l2Account); 58 | 59 | // Deploy the L1 contract. It needs to know the messaging contract's address. 60 | const l1Signers = await l1Provider.listAccounts(); 61 | const l1Signer = l1Signers[0]; 62 | 63 | const l1L2ExampleArtifact = getContractArtifact("test/data/L1L2Example.json"); 64 | const l1L2ExampleFactory = new ethers.ContractFactory( 65 | l1L2ExampleArtifact.abi, 66 | l1L2ExampleArtifact.bytecode, 67 | l1Signer, 68 | ); 69 | l1L2Example = (await l1L2ExampleFactory.deploy( 70 | messagingContractAddress, 71 | )) as ethers.Contract; 72 | await l1L2Example.waitForDeployment(); 73 | }); 74 | 75 | /** 76 | * Deploy a custom messaging contract if you need to, otherwise letting Devnet deploy one for 77 | * you is enough, as done in the before() hook. 78 | */ 79 | it("should deploy a custom messaging contract", async () => { 80 | const l1Signer = (await l1Provider.listAccounts())[0]; 81 | const messagingArtifact = getContractArtifact("test/data/MockStarknetMessaging.json"); 82 | 83 | const messagingFactory = new ethers.ContractFactory( 84 | messagingArtifact.abi, 85 | messagingArtifact.bytecode, 86 | l1Signer, 87 | ); 88 | 89 | const ctorArg = 5 * 60; // messasge cancellation delay in seconds 90 | const messagingContract = (await messagingFactory.deploy(ctorArg)) as ethers.Contract; 91 | await messagingContract.waitForDeployment(); 92 | const deploymentAddress = await messagingContract.getAddress(); 93 | 94 | const { messaging_contract_address: loadedAddress } = 95 | await devnetProvider.postman.loadL1MessagingContract(L1_URL, deploymentAddress); 96 | 97 | expectHexEquality(loadedAddress, deploymentAddress); 98 | }); 99 | 100 | /** This is also done in before(), but showcased separately here. */ 101 | it("should load the already deployed contract if the address is provided", async () => { 102 | const { messaging_contract_address: loadedFrom } = 103 | await devnetProvider.postman.loadL1MessagingContract(L1_URL, messagingContractAddress); 104 | 105 | expectHexEquality(loadedFrom, messagingContractAddress); 106 | }); 107 | 108 | it("should exchange messages between L1 and L2", async () => { 109 | // Increase the L2 contract balance to 100 and withdraw 10 from it. 110 | await l2Provider.waitForTransaction( 111 | (await l2Contract.increase_balance(user, 100)).transaction_hash, 112 | ); 113 | const l1L2ExampleAddress = await l1L2Example.getAddress(); 114 | await l2Provider.waitForTransaction( 115 | (await l2Contract.withdraw(user, 10, l1L2ExampleAddress)).transaction_hash, 116 | ); 117 | 118 | expect(await l2Contract.get_balance(user)).to.equal(90n); 119 | 120 | // Flushing the L2 messages so that they can be consumed by the L1. 121 | const flushL2Response = await devnetProvider.postman.flush(); 122 | expect(flushL2Response.messages_to_l2).to.be.empty; 123 | const flushL2Messages = flushL2Response.messages_to_l1; 124 | 125 | expect(flushL2Messages).to.have.a.lengthOf(1); 126 | expectHexEquality(flushL2Messages[0].from_address, l2Contract.address); 127 | expectHexEquality(flushL2Messages[0].to_address, l1L2ExampleAddress); 128 | 129 | // Check the L1 balance and withdraw 10 which will consume the L2 message. 130 | expect(await l1L2Example.userBalances(user)).to.equal(0n); 131 | await l1L2Example.withdraw(l2Contract.address, user, 10); 132 | expect(await l1L2Example.userBalances(user)).to.equal(10n); 133 | 134 | // Deposit to the L2 contract, L1 balance should be decreased and L2 balance increased by 2. 135 | const depositAmount = 2n; 136 | const l1Fee = 1n; 137 | await l1L2Example.deposit(l2Contract.address, user, depositAmount, { 138 | value: l1Fee, 139 | }); 140 | expect(await l1L2Example.userBalances(user)).to.equal(8n); 141 | expect(await l2Contract.get_balance(user)).to.equal(90n); 142 | 143 | // Flushing the L1 messages so that they can be consumed by the L2. 144 | const flushL1Response = await devnetProvider.postman.flush(); 145 | const flushL1Messages = flushL1Response.messages_to_l2; 146 | expect(flushL1Messages).to.have.a.lengthOf(1); 147 | expect(flushL1Response.messages_to_l1).to.be.empty; 148 | 149 | expectHexEquality(flushL1Messages[0].l1_contract_address, l1L2ExampleAddress); 150 | expectHexEquality(flushL1Messages[0].l2_contract_address, l2Contract.address); 151 | expect(BigInt(flushL1Messages[0].paid_fee_on_l1)).to.equal(l1Fee); 152 | expect(flushL1Messages[0].nonce).to.match(HEX_REGEX); 153 | expect(flushL1Messages[0].entry_point_selector).to.equal( 154 | starknet.selector.getSelector("deposit"), 155 | ); 156 | expect(flushL1Messages[0].payload.map(BigInt)).to.deep.equal([user, depositAmount]); 157 | 158 | expect(await l2Contract.get_balance(user)).to.equal(92n); 159 | }); 160 | 161 | it("should mock messaging from L1 to L2", async () => { 162 | const initialBalance = await l2Contract.get_balance(user); 163 | const depositAmount = 1n; 164 | const { transaction_hash } = await devnetProvider.postman.sendMessageToL2( 165 | l2Contract.address, 166 | starknet.selector.getSelector("deposit"), 167 | messagingContractAddress, 168 | [user, depositAmount], 169 | 0, // nonce 170 | 1, // paid fee on l1 171 | ); 172 | expect(transaction_hash).to.match(HEX_REGEX); 173 | 174 | const tx = await l2Provider.getTransactionReceipt(transaction_hash); 175 | expect(tx.isSuccess()).to.be.true; 176 | 177 | expect(await l2Contract.get_balance(user)).to.equal(initialBalance + depositAmount); 178 | }); 179 | 180 | it("should mock messaging from L2 to L1", async () => { 181 | const initialBalance = await l2Contract.get_balance(user); 182 | 183 | // create balance on L2, withdraw a part of it 184 | const incrementAmount = 10_000_000n; 185 | await l2Contract.increase_balance(user, incrementAmount); 186 | 187 | const withdrawAmount = 10n; 188 | const withdrawTx = await l2Contract.withdraw( 189 | user, 190 | withdrawAmount, 191 | messagingContractAddress, 192 | ); 193 | await l2Provider.waitForTransaction(withdrawTx.transaction_hash); 194 | 195 | const { message_hash } = await devnetProvider.postman.consumeMessageFromL2( 196 | l2Contract.address, 197 | messagingContractAddress, 198 | [0, user, withdrawAmount], 199 | ); 200 | expect(message_hash).to.match(HEX_REGEX); 201 | 202 | expect(await l2Contract.get_balance(user)).to.equal( 203 | initialBalance + incrementAmount - withdrawAmount, 204 | ); 205 | }); 206 | }); 207 | -------------------------------------------------------------------------------- /test/util.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { toRpcBlockId } from ".."; 3 | 4 | describe("toRpcBlockId", function () { 5 | it("should fail for invalid input", function () { 6 | for (const invalidValue of ["0x", -4, "abc", "123", "Pending", "LLatest"]) { 7 | try { 8 | toRpcBlockId(invalidValue); 9 | expect.fail("Should have failed earlier"); 10 | } catch (err) { 11 | expect(err).to.have.property("message").that.contains("Invalid block ID"); 12 | } 13 | } 14 | }); 15 | 16 | it("should work for valid block number", function () { 17 | for (const validValue of [1, 42, 10000000000000]) { 18 | expect(toRpcBlockId(validValue)).to.deep.equal({ 19 | block_number: validValue, 20 | }); 21 | } 22 | }); 23 | 24 | it("should work for valid block hash", function () { 25 | for (const validValue of [ 26 | "0x1", 27 | "0x111111111111111111111111111", 28 | "0xab123", 29 | "0x0987ab123", 30 | "0xabcdef0123456789", 31 | ]) { 32 | expect(toRpcBlockId(validValue)).to.deep.equal({ 33 | block_hash: validValue, 34 | }); 35 | } 36 | }); 37 | 38 | it("should work for valid block tag", function () { 39 | for (const validValue of ["latest", "pending"]) { 40 | expect(toRpcBlockId(validValue)).to.deep.equal(validValue); 41 | } 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | import * as starknet from "starknet"; 2 | import { readFileSync } from "fs"; 3 | import { expect } from "chai"; 4 | import { DevnetProvider } from ".."; 5 | 6 | export function getContractArtifact(contractPath: string) { 7 | return starknet.json.parse(readFileSync(contractPath).toString("ascii")); 8 | } 9 | 10 | export function expectHexEquality(h1: string, h2: string) { 11 | expect(BigInt(h1).toString()).to.equal(BigInt(h2).toString()); 12 | } 13 | 14 | export function toPrefixedHex(b: bigint): string { 15 | return "0x" + b.toString(16); 16 | } 17 | 18 | export async function getPredeployedAccount( 19 | devnetProvider: DevnetProvider, 20 | starknetProvider: starknet.Provider, 21 | ) { 22 | const predeployedAccountData = (await devnetProvider.getPredeployedAccounts())[0]; 23 | 24 | return new starknet.Account( 25 | starknetProvider, 26 | predeployedAccountData.address, 27 | predeployedAccountData.private_key, 28 | ); 29 | } 30 | 31 | /** 32 | * Return the value associated to the variable name, or throw an error if not defined. 33 | */ 34 | export function getEnvVar(varName: string): string { 35 | if (varName in process.env) { 36 | return process.env[varName] as string; 37 | } 38 | throw new Error(`Environment variable not defined: ${varName}`); 39 | } 40 | 41 | export async function sleep(millis: number): Promise { 42 | await new Promise((resolve, _) => setTimeout(resolve, millis)); 43 | } 44 | 45 | export const ETH_TOKEN_CONTRACT_ADDRESS = 46 | "0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7"; 47 | export const STRK_TOKEN_CONTRACT_ADDRESS = 48 | "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d"; 49 | 50 | interface TokenBalanceConfig { 51 | tokenContractAddress?: string; 52 | blockIdentifier?: starknet.BlockIdentifier; 53 | } 54 | 55 | /** 56 | * @param accountAddress the address of the account whose balance you would like to know 57 | * @param provider the provider to the network where the account is deployed 58 | * @param config object holding the address of the ERC20 token contract to query (defaults to ETH) and block ID (defaults to pending) 59 | * @returns the balance of `accountAddress` in the specified token contract at the specified block 60 | */ 61 | export async function getAccountBalance( 62 | accountAddress: string, 63 | provider: starknet.Provider, 64 | config: TokenBalanceConfig = {}, 65 | ): Promise { 66 | const tokenContractAddress = config.tokenContractAddress ?? ETH_TOKEN_CONTRACT_ADDRESS; 67 | const blockIdentifier = config.blockIdentifier ?? starknet.BlockTag.PENDING; 68 | const tokenClass = await provider.getClassAt(tokenContractAddress, blockIdentifier); 69 | const tokenContract = new starknet.Contract(tokenClass.abi, tokenContractAddress, provider); 70 | 71 | return tokenContract.withOptions({ blockIdentifier }).balanceOf(accountAddress); 72 | } 73 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "include": ["src"], 13 | "exclude": ["node_modules", "dist", "test"] 14 | } 15 | --------------------------------------------------------------------------------