├── .changeset ├── README.md └── config.json ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── pull_request_template.md └── workflows │ ├── add-issues-to-devx-project.yml │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── TODO.md ├── TRANSITIONS.md ├── babel.config.json ├── bin └── index.js ├── cadence ├── contracts │ └── FlowManager.cdc ├── scripts │ ├── check-manager.cdc │ ├── get-account-address.cdc │ ├── get-block-offset.cdc │ ├── get-contract-address.cdc │ ├── get-manager-address.cdc │ ├── get-timestamp-offset.cdc │ └── get_balance.cdc └── transactions │ ├── create-account.cdc │ ├── deploy-contract.cdc │ ├── init-manager.cdc │ ├── mint_tokens.cdc │ ├── register-contract.cdc │ ├── scratch.cdc │ ├── set-block-offset.cdc │ ├── set-timestamp-offset.cdc │ └── update-contract.cdc ├── check-headers.sh ├── docs ├── README.md ├── accounts.md ├── api.md ├── contracts.md ├── emulator.md ├── examples │ ├── basic.md │ └── metadata.md ├── execute-scripts.md ├── flow-token.md ├── generator.md ├── index.md ├── init.md ├── install.md ├── jest-helpers.md ├── send-transactions.md ├── structure.md ├── templates.md └── types.md ├── examples ├── 01-get-account-address.test.js ├── 02-deploy-contract-by-name.test.js ├── 03-deploy-contract.test.js ├── 04-get-contract-address.test.js ├── 05-emulator-management.test.js ├── 06-flow-management.test.js ├── 07-block-offset.test.js ├── 08-execute-script.test.js ├── 09-send-transaction.test.js ├── 10-templates.test.js ├── 100-pass-array-of-dictionaries.test.js ├── 101-pass-int-dictionary.test.js ├── 102-pass-string-to-int-dictionary.test.js ├── README.md ├── cadence │ ├── contracts │ │ ├── Greeting.cdc │ │ └── Hello.cdc │ ├── scripts │ │ ├── hello.cdc │ │ ├── log-args.cdc │ │ └── replace-address.cdc │ └── transactions │ │ └── log-signers.cdc ├── flow.json └── run.js ├── flow.json ├── js-testing-banner.svg ├── package-lock.json ├── package.json ├── src ├── account.js ├── cli │ ├── commands │ │ ├── index.js │ │ ├── init.js │ │ └── make.js │ ├── index.js │ ├── templates │ │ ├── babel-config.js │ │ ├── jest-config.js │ │ └── test.js │ └── utils │ │ └── index.js ├── contract.js ├── crypto.js ├── deploy-code.js ├── emulator │ ├── emulator.js │ ├── index.js │ └── logger.js ├── exports.js ├── file.js ├── flow-config.js ├── flow-token.js ├── generated │ ├── contracts │ │ ├── FlowManager.js │ │ └── index.js │ ├── index.js │ ├── scripts │ │ ├── checkManager.js │ │ ├── getAccountAddress.js │ │ ├── getBalance.js │ │ ├── getBlockOffset.js │ │ ├── getContractAddress.js │ │ ├── getManagerAddress.js │ │ ├── getTimestampOffset.js │ │ └── index.js │ └── transactions │ │ ├── createAccount.js │ │ ├── deployContract.js │ │ ├── index.js │ │ ├── initManager.js │ │ ├── mintTokens.js │ │ ├── registerContract.js │ │ ├── scratch.js │ │ ├── setBlockOffset.js │ │ ├── setTimestampOffset.js │ │ └── updateContract.js ├── imports.js ├── index.js ├── init.js ├── interaction.js ├── invariant.js ├── jest-asserts.js ├── manager.js ├── storage.js ├── templates.js ├── transformers.js └── utils.js ├── test ├── basic │ ├── cadence.test.js │ ├── imports.test.js │ └── logger.test.js ├── cadence │ ├── contracts │ │ ├── HelloWorld.cdc │ │ └── utility │ │ │ └── Message.cdc │ ├── scripts │ │ ├── get-message.cdc │ │ ├── log-message.cdc │ │ ├── log-passed-message.cdc │ │ ├── read-mocked-block-offset.cdc │ │ └── read-mocked-timestamp-offset.cdc │ └── transactions │ │ ├── log-message.cdc │ │ └── log-signer-address.cdc ├── flow.json ├── integration │ ├── account.test.js │ ├── crypto.test.js │ ├── deploy.test.js │ ├── imports.test.js │ ├── interaction.test.js │ ├── metadata.test.js │ ├── storage.test.js │ ├── transformers.test.js │ ├── usage.test.js │ └── utilities.test.js └── util │ ├── index.test.js │ ├── permute.js │ ├── timeout.const.js │ └── validate-key-pair.js └── webpack.config.js /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | {"repo": "onflow/flow-js-testing"} 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "public", 11 | "baseBranch": "master", 12 | "updateInternalDependencies": "patch", 13 | "ignore": [] 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "eslint:recommended", 4 | "plugin:jest/recommended", 5 | "plugin:prettier/recommended", 6 | ], 7 | env: { 8 | node: true, 9 | es6: true, 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 12, 13 | sourceType: "module", 14 | }, 15 | rules: { 16 | "jest/expect-expect": "off", 17 | }, 18 | plugins: ["jest"], 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Reporting a Problem/Bug 3 | about: Reporting a Problem/Bug 4 | title: "" 5 | labels: bug, Feedback 6 | --- 7 | 8 | ### Instructions 9 | 10 | Please fill out the template below to the best of your ability and include a label indicating which tool/service you were working with when you encountered the problem. 11 | 12 | ### Problem 13 | 14 | 15 | 16 | ### Steps to Reproduce 17 | 18 | 19 | 20 | ### Acceptance Criteria 21 | 22 | 23 | 24 | ### Context 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Requesting a Feature or Improvement 3 | about: "For feature requests. Please search for existing issues first. Also see CONTRIBUTING.md" 4 | title: "" 5 | labels: Feedback, Feature 6 | --- 7 | 8 | ## Instructions 9 | 10 | Please fill out the template below to the best of your ability. 11 | 12 | ### Issue To Be Solved 13 | 14 | (Replace this text: 15 | Please present a concise description of the problem to be addressed by this feature request. 16 | Please be clear what parts of the problem are considered to be in-scope and out-of-scope.) 17 | 18 | ### (Optional): Suggest A Solution 19 | 20 | (Replace this text: A concise description of your preferred solution. Things to address include: 21 | 22 | - Details of the technical implementation 23 | - Tradeoffs made in design decisions 24 | - Caveats and considerations for the future 25 | 26 | If there are multiple solutions, please present each one separately. Save comparisons for the very end.) 27 | 28 | ### (Optional): Context 29 | 30 | (Replace this text: 31 | What are you currently working on that this is blocking?) 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Closes #??? 2 | 3 | ## Description 4 | 5 | 9 | 10 | --- 11 | 12 | For contributor use: 13 | 14 | - [ ] Targeted PR against `master` branch 15 | - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work 16 | - [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-js-testing/blob/master/CONTRIBUTING.md#styleguides) 17 | - [ ] Updated relevant documentation 18 | - [ ] Re-reviewed `Files changed` in the Github PR explorer 19 | - [ ] Added appropriate labels 20 | -------------------------------------------------------------------------------- /.github/workflows/add-issues-to-devx-project.yml: -------------------------------------------------------------------------------- 1 | name: Adds all issues to the DevEx project board. 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add issue to project 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/add-to-project@v0.4.1 14 | with: 15 | project-url: https://github.com/orgs/onflow/projects/13 16 | github-token: ${{ secrets.GH_ACTION_FOR_PROJECTS }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - feature/* 8 | pull_request: 9 | branches: 10 | - master 11 | - feature/* 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 16 23 | 24 | - name: Cache Node.js Modules 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.npm 28 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 29 | restore-keys: | 30 | ${{ runner.OS }}-node- 31 | ${{ runner.OS }}- 32 | 33 | - name: Check License Headers 34 | run: ./check-headers.sh 35 | 36 | - name: Install Flow CLI 37 | run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" 38 | 39 | - name: Get Flow CLI version 40 | id: testbed 41 | run: | 42 | echo "flow-version=$(flow version --output=json | jq -r '.version')" >> $GITHUB_OUTPUT 43 | echo "package-version=$(grep version package.json | sed 's/.*"version": "\(.*\)".*/\1/')" >> $GITHUB_OUTPUT 44 | echo "fcl-version=$(grep 'fcl":' package.json | sed 's/.*"@onflow\/fcl": "\(.*\)".*/\1/')" >> $GITHUB_OUTPUT 45 | 46 | - name: Output Flow Version 47 | uses: marocchino/sticky-pull-request-comment@v2 48 | with: 49 | # pass output from the previous step by id. 50 | header: Flow Version 51 | message: | 52 | ### Dependency Testbed 53 | - **Flow CLI**: `${{ steps.testbed.outputs.flow-version }}` 54 | - **FCL**: `${{ steps.testbed.outputs.fcl-version }}` 55 | 56 | ### Release Version 57 | The package containing these changes will be released with version **${{ steps.testbed.outputs.package-version }}** 58 | 59 | - name: Install Dependencies 60 | run: npm ci 61 | 62 | - name: Lint 63 | run: npm run lint 64 | 65 | - name: Find PR Number 66 | uses: jwalton/gh-find-current-pr@v1 67 | id: currentPr 68 | 69 | - name: Test Coverage 70 | uses: ArtiomTr/jest-coverage-report-action@v2.2.1 71 | id: coverage 72 | with: 73 | skip-step: install 74 | prnumber: ${{ steps.currentPr.outputs.number }} 75 | output: report-markdown 76 | 77 | - name: Output As Comment 78 | uses: marocchino/sticky-pull-request-comment@v2 79 | with: 80 | header: Coverage Report 81 | message: ${{ steps.coverage.outputs.report }} 82 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repo 13 | uses: actions/checkout@v3 14 | with: 15 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 16 | fetch-depth: 0 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 16 22 | 23 | - name: Install Flow CLI 24 | # We will need Flow CLI in order to run tests, so we need to install it 25 | run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/refs/heads/master/install.sh)" 26 | 27 | - name: Install Dependencies 28 | run: npm ci 29 | 30 | - name: Create Release Pull Request or Publish to npm 31 | id: changesets 32 | uses: changesets/action@v1 33 | with: 34 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 35 | publish: npm run release 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .cache 3 | .history 4 | node_modules 5 | dist 6 | .DS_Store 7 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .changeset 2 | .github 3 | node_modules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/generated -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true 4 | } 5 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @onflow/flow-develop-experience-tools @MaxStalker 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at . 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series 85 | of actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or 92 | permanent ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within 112 | the community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, available at 118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 119 | 120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 121 | enforcement ladder](https://github.com/mozilla/diversity). 122 | 123 | [homepage]: https://www.contributor-covenant.org 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | https://www.contributor-covenant.org/faq. Translations are available at 127 | https://www.contributor-covenant.org/translations. 128 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The following is a set of guidelines for contributing. 4 | These are mostly guidelines, not rules. 5 | Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ## Table Of Contents 8 | 9 | [Getting Started](#project-overview) 10 | 11 | [How Can I Contribute?](#how-can-i-contribute) 12 | 13 | - [Reporting Bugs](#reporting-bugs) 14 | - [Suggesting Enhancements](#suggesting-enhancements) 15 | - [Your First Code Contribution](#your-first-code-contribution) 16 | - [Pull Requests](#pull-requests) 17 | 18 | [Styleguides](#styleguides) 19 | 20 | - [Git Commit Messages](#git-commit-messages) 21 | - [Styleguide](#styleguide) 22 | 23 | [Additional Notes](#additional-notes) 24 | 25 | ## How Can I Contribute? 26 | 27 | ### Reporting Bugs 28 | 29 | #### Before Submitting A Bug Report 30 | 31 | - **Search existing issues** to see if the problem has already been reported. 32 | If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. 33 | 34 | #### How Do I Submit A (Good) Bug Report? 35 | 36 | Explain the problem and include additional details to help maintainers reproduce the problem: 37 | 38 | - **Use a clear and descriptive title** for the issue to identify the problem. 39 | - **Describe the exact steps which reproduce the problem** in as many details as possible. 40 | When listing steps, **don't just say what you did, but explain how you did it**. 41 | - **Provide specific examples to demonstrate the steps**. 42 | Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. 43 | If you're providing snippets in the issue, 44 | use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 45 | - **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 46 | - **Explain which behavior you expected to see instead and why.** 47 | - **Include error messages and stack traces** which show the output / crash and clearly demonstrate the problem. 48 | 49 | Provide more context by answering these questions: 50 | 51 | - **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens 52 | and under which conditions it normally happens. 53 | 54 | Include details about your configuration and environment: 55 | 56 | - **What is the version you're using**? 57 | - **What's the name and version of the Operating System you're using**? 58 | 59 | ### Suggesting Enhancements 60 | 61 | #### Before Submitting An Enhancement Suggestion 62 | 63 | - **Perform a cursory search** to see if the enhancement has already been suggested. 64 | If it has, add a comment to the existing issue instead of opening a new one. 65 | 66 | #### How Do I Submit A (Good) Enhancement Suggestion? 67 | 68 | Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). 69 | Create an issue and provide the following information: 70 | 71 | - **Use a clear and descriptive title** for the issue to identify the suggestion. 72 | - **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 73 | - **Provide specific examples to demonstrate the steps**. 74 | Include copy/pasteable snippets which you use in those examples, 75 | as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 76 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 77 | - **Explain why this enhancement would be useful** to users. 78 | 79 | ### Your First Code Contribution 80 | 81 | Unsure where to begin contributing? 82 | You can start by looking through these "Good first issue" and "Help wanted" issues: 83 | 84 | - [Good first issues](https://github.com/onflow/flow-js-testing/labels/good%20first%20issue): 85 | issues which should only require a few lines of code, and a test or two. 86 | - [Help wanted issues](https://github.com/onflow/flow-js-testing/labels/help%20wanted): 87 | issues which should be a bit more involved than "Good first issue" issues. 88 | 89 | Both issue lists are sorted by total number of comments. 90 | While not perfect, number of comments is a reasonable proxy for impact a given change will have. 91 | 92 | ### Pull Requests 93 | 94 | The process described here has several goals: 95 | 96 | - Maintain code quality 97 | - Fix problems that are important to users 98 | - Engage the community in working toward the best possible UX 99 | - Enable a sustainable system for the maintainers to review contributions 100 | 101 | Please follow the [styleguides](#styleguides) to have your contribution considered by the maintainers. 102 | Reviewer(s) may ask you to complete additional design work, tests, 103 | or other changes before your pull request can be ultimately accepted. 104 | 105 | ## Styleguides 106 | 107 | Before contributing, make sure to examine the project to get familiar with the patterns and style already being used. 108 | 109 | ### Git Commit Messages 110 | 111 | - Use the present tense ("Add feature" not "Added feature") 112 | - Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 113 | - Limit the first line to 72 characters or less 114 | - Reference issues and pull requests liberally after the first line 115 | 116 | ### Styleguide 117 | 118 | We try to follow the coding guidelines: 119 | 120 | - Code should pass the linter: `npm run lint` 121 | - Code should be commented 122 | 123 | ## Additional Notes 124 | 125 | Thank you for your interest in contributing! 126 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Flow 2 | Copyright 2019-2021 Dapper Labs, Inc. 3 | 4 | This product includes software developed at Dapper Labs, Inc. (https://www.dapperlabs.com/). 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | The library is deprecated and is not supported/maintained anymore 4 | 5 | Use [Cadence Testing Framework](https://cadence-lang.org/docs/testing-framework) instead 6 | 7 |
8 | 9 | Logo 10 | 11 | 12 |

13 | Test your Flow applications written in Cadence with ease 14 |
15 | Read the docs 16 |
17 |
18 | Report Bug 19 | · 20 | Contribute 21 | · 22 | Installation 23 |

24 |
25 | 26 | # JavaScript Testing Framework for Flow Network 27 | 28 | This repository contains utility methods which, in conjunction with testing libraries like `Jest`, 29 | can be used to speed up your productivity while building Flow dapps with Cadence. 30 | 31 | ## Requirements 32 | 33 | ### Node 34 | 35 | We are assuming you are using this framework under Node environment. You will need at least version **12.0.0** 36 | 37 | ### Emulator 38 | 39 | Most of the methods will not work, unless you have Flow Emulator running in the background. 40 | You can install it alongside Flow CLI. Please refer to [Install Flow CLI](https://docs.onflow.org/flow-cli/install) 41 | for instructions. 42 | 43 | If you have it already installed, run the `flow init` in your terminal to create `flow.json` config file in the root directory of your tests. 44 | 45 | In order to use the emulator within your tests, please refer to the [documentation](https://docs.onflow.org/flow-js-testing/emulator/). 46 | 47 | ## Playground Integration 48 | 49 | Every Playground project has the ability to `export` it's content as a set of files with Cadence template code and 50 | basic test environment "out of the box". 51 | 52 | If you want to use this functionality: 53 | 54 | - Press "Export" button in the top right corner 55 | - Pick the name of the project - or keep auto-generated version 56 | - Press "Export" button within popup window 57 | 58 | Playground will create a `zip` file for you, which you can save wherever you like. 59 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Responsible Disclosure Policy 2 | 3 | Flow was built from the ground up with security in mind. Our code, infrastructure, and development methodology helps us keep our users safe. 4 | 5 | We really appreciate the community's help. Responsible disclosure of vulnerabilities helps to maintain the security and privacy of everyone. 6 | 7 | If you care about making a difference, please follow the guidelines below. 8 | 9 | # **Guidelines For Responsible Disclosure** 10 | 11 | We ask that all researchers adhere to these guidelines. 12 | 13 | ## **Rules of Engagement** 14 | 15 | - Make every effort to avoid unauthorized access, use, and disclosure of personal information. 16 | - Avoid actions which could impact user experience, disrupt production systems, change, or destroy data during security testing. 17 | - Don’t perform any attack that is intended to cause Denial of Service to the network, hosts, or services on any port or using any protocol. 18 | - Use our provided communication channels to securely report vulnerability information to us. 19 | - Keep information about any bug or vulnerability you discover confidential between us until we publicly disclose it. 20 | - Please don’t use scanners to crawl us and hammer endpoints. They’re noisy and we already do this. If you find anything this way, we have likely already identified it. 21 | - Never attempt non-technical attacks such as social engineering, phishing, or physical attacks against our employees, users, or infrastructure. 22 | 23 | ## **In Scope URIs** 24 | 25 | Be careful that you're looking at domains and systems that belong to us and not someone else. When in doubt, please ask us. Maybe ask us anyway. 26 | 27 | Bottom line, we suggest that you limit your testing to infrastructure that is clearly ours. 28 | 29 | ## **Out of Scope URIs** 30 | 31 | The following base URIs are explicitly out of scope: 32 | 33 | - None 34 | 35 | ## **Things Not To Do** 36 | 37 | In the interests of your safety, our safety, and for our customers, the following test types are prohibited: 38 | 39 | - Physical testing such as office and data-centre access (e.g. open doors, tailgating, card reader attacks, physically destructive testing) 40 | - Social engineering (e.g. phishing, vishing) 41 | - Testing of applications or systems NOT covered by the ‘In Scope’ section, or that are explicitly out of scope. 42 | - Network level Denial of Service (DoS/DDoS) attacks 43 | 44 | ## **Sensitive Data** 45 | 46 | In the interests of protecting privacy, we never want to receive: 47 | 48 | - Personally identifiable information (PII) 49 | - Payment card (e.g. credit card) data 50 | - Financial information (e.g. bank records) 51 | - Health or medical information 52 | - Accessed or cracked credentials in cleartext 53 | 54 | ## **Our Commitment To You** 55 | 56 | If you follow these guidelines when researching and reporting an issue to us, we commit to: 57 | 58 | - Not send lawyers after you related to your research under this policy; 59 | - Work with you to understand and resolve any issues within a reasonable timeframe, including an initial confirmation of your report within 72 hours of submission; and 60 | - At a minimum, we will recognize your contribution in our Disclosure Acknowledgements if you are the first to report the issue and we make a code or configuration change based on the issue. 61 | 62 | ## **Disclosure Acknowledgements** 63 | 64 | We're happy to acknowledge contributors. Security acknowledgements can be found here. 65 | 66 | ## Rewards 67 | 68 | We run closed bug bounty programs, but beyond that we also pay out rewards, once per eligible bug, to the first responsibly disclosing third party. Rewards are based on the seriousness of the bug, but the minimum is $100 and we have and are willing to pay $5,000 or more at our sole discretion. 69 | 70 | ### **Elligibility** 71 | 72 | To qualify, the bug must fall within our scope and rules and meet the following criteria: 73 | 74 | 1. **Previously unknown** - When reported, we must not have already known of the issue, either by internal discovery or separate disclosure. 75 | 2. **Material impact** - Demonstrable exploitability where, if exploited, the bug would materially affect the confidentiality, integrity, or availability of our services. 76 | 3. **Requires action** - The bug requires some mitigation. It is both valid and actionable. 77 | 78 | ## **Reporting Security Findings To Us** 79 | 80 | Reports are welcome! Please definitely reach out to us if you have a security concern. 81 | 82 | We prefer you to please send us an email: security@onflow.org 83 | 84 | Note: If you believe you may have found a security vulnerability in our open source repos, to be on the safe side, do NOT open a public issue. 85 | 86 | We encourage you to encrypt the information you send us using our PGP key at [keys.openpgp.org/security@onflow.org](https://keys.openpgp.org/vks/v1/by-fingerprint/AE3264F330AB51F7DBC52C400BB5D3D7516D168C) 87 | 88 | Please include the following details with your report: 89 | 90 | - A description of the location and potential impact of the finding(s); 91 | - A detailed description of the steps required to reproduce the issue; and 92 | - Any POC scripts, screenshots, and compressed screen captures, where feasible. 93 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] Reintroduce automatic import replacement with overrides, simplifying the process 2 | of executing a script or transaction to passing a name and a list of arguments 3 | - [ ] Init setting to align framework with running emulator 4 | -------------------------------------------------------------------------------- /TRANSITIONS.md: -------------------------------------------------------------------------------- 1 | # Transitions 2 | 3 | ## 0002 Depreaction of `builtInMethods` code transformer 4 | 5 | - **Date:** Aug 8 2022 6 | - **Type:** Depreaction of `builtInMethods` code transformer 7 | 8 | The `builtInMethods` code transformer is now deprecated and will not be exported by the Flow JS Testing library in future versions. It is now applied by default to all cadence code processed by the Flow JS Testing library and passing this transformer manually is redundant and uncessary. 9 | 10 | It was previously used to replace segments of cadence code related to `setBlockOffset`/`setTimestampOffset` utilties, but its implementation has now been handled internally by the Flow JS Testing library. 11 | 12 | Please remove this transformer from all your existing `sendTransaction` & `executeScript` calls if you were using any block or timestamp offset utilities. 13 | 14 | ## 0001 Deprecate `emulator.start()` port argument 15 | 16 | - **Date:** Jun 28 2022 17 | - **Type:** Depreaction of `port` argument for `emulator.start()` 18 | 19 | `emulator.start` was previously called with the arguments: `emulator.start(port, options = {})`. The `port` argument has now been removed and manual specification of the ports is no longer recommended. 20 | 21 | However, the `adminPort`, `restPort`, and `grpcPort` of the emulator may be overriden as fields in `options` (i.e. `options.restPort = 1234`) if absolutely necessary - however their use is not advisable and may cause unintended consequences. 22 | 23 | Instead, it is recommended omit supplying a static a port and allow flow-js-testing to automatically determine available ports to supply the emululator. Flow-js-testing will automatically configure @onflow/fcl to use these ports for all of its functionality. 24 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "current"}}]] 3 | } 4 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require("esm")(module /*, options*/)("../src/cli").run(process.argv) 4 | -------------------------------------------------------------------------------- /cadence/contracts/FlowManager.cdc: -------------------------------------------------------------------------------- 1 | access(all) contract FlowManager { 2 | 3 | /// Account Manager 4 | access(all) event AccountAdded(address: Address) 5 | 6 | access(all) struct Mapper { 7 | access(all) let accounts: {String: Address} 8 | 9 | access(all) view fun getAddress(_ name: String): Address? { 10 | return self.accounts[name] 11 | } 12 | 13 | access(all) fun setAddress(_ name: String, address: Address){ 14 | self.accounts[name] = address 15 | emit FlowManager.AccountAdded(address: address) 16 | } 17 | 18 | init(){ 19 | self.accounts = {} 20 | } 21 | } 22 | 23 | access(all) view fun getAccountAddress(_ name: String): Address?{ 24 | let accountManager = self.account 25 | .capabilities.borrow<&FlowManager.Mapper>(self.accountManagerPath)! 26 | 27 | return accountManager.getAddress(name) 28 | } 29 | 30 | access(all) let defaultAccounts: {Address : String} 31 | 32 | access(all) fun resolveDefaultAccounts(_ address: Address): Address{ 33 | let alias = self.defaultAccounts[address]! 34 | return self.getAccountAddress(alias)! 35 | } 36 | 37 | access(all) let accountManagerStorage: StoragePath 38 | access(all) let contractManagerStorage: StoragePath 39 | access(all) let accountManagerPath: PublicPath 40 | access(all) let contractManagerPath: PublicPath 41 | 42 | /// Environment Manager 43 | access(all) event BlockOffsetChanged(offset: UInt64) 44 | access(all) event TimestampOffsetChanged(offset: UFix64) 45 | 46 | access(all) struct MockBlock { 47 | access(all) let id: [UInt8; 32] 48 | access(all) let height: UInt64 49 | access(all) let view: UInt64 50 | access(all) let timestamp: UFix64 51 | 52 | init(_ id: [UInt8; 32], _ height: UInt64, _ view: UInt64, _ timestamp: UFix64){ 53 | self.id = id 54 | self.height = height 55 | self.view = view 56 | self.timestamp = timestamp 57 | } 58 | } 59 | 60 | access(all) fun setBlockOffset(_ offset: UInt64){ 61 | self.blockOffset = offset 62 | emit FlowManager.BlockOffsetChanged(offset: offset) 63 | } 64 | 65 | access(all) fun setTimestampOffset(_ offset: UFix64){ 66 | self.timestampOffset = offset 67 | emit FlowManager.TimestampOffsetChanged(offset: offset) 68 | } 69 | 70 | access(all) view fun getBlockHeight(): UInt64 { 71 | var block = getCurrentBlock() 72 | return block.height + self.blockOffset 73 | } 74 | 75 | access(all) view fun getBlockTimestamp(): UFix64 { 76 | var block = getCurrentBlock() 77 | return block.timestamp + self.timestampOffset 78 | } 79 | 80 | access(all) fun getBlock(): MockBlock { 81 | var block = getCurrentBlock() 82 | let mockBlock = MockBlock(block.id, block.height, block.view, block.timestamp); 83 | return mockBlock 84 | } 85 | 86 | access(all) var blockOffset: UInt64; 87 | access(all) var timestampOffset: UFix64; 88 | 89 | 90 | // Initialize contract 91 | init(){ 92 | // Environment defaults 93 | self.blockOffset = 0; 94 | self.timestampOffset = 0.0; 95 | 96 | // Account Manager initialization 97 | let accountManager = Mapper() 98 | let contractManager = Mapper() 99 | 100 | self.defaultAccounts = { 101 | 0x01: "Alice", 102 | 0x02: "Bob", 103 | 0x03: "Charlie", 104 | 0x04: "Dave", 105 | 0x05: "Eve" 106 | } 107 | 108 | self.accountManagerStorage = /storage/testSuiteAccountManager 109 | self.contractManagerStorage = /storage/testSuiteContractManager 110 | 111 | self.accountManagerPath = /public/testSuiteAccountManager 112 | self.contractManagerPath = /public/testSuiteContractManager 113 | 114 | // Destroy previously stored values 115 | self.account.storage.load(from: self.accountManagerStorage) 116 | self.account.storage.load(from: self.contractManagerStorage) 117 | 118 | self.account.storage.save(accountManager, to: self.accountManagerStorage) 119 | self.account.storage.save(contractManager, to: self.contractManagerStorage) 120 | 121 | 122 | self.account.capabilities.publish( 123 | self.account.capabilities.storage.issue<&Mapper>( 124 | self.accountManagerStorage 125 | ), at: self.accountManagerPath) 126 | 127 | self.account.capabilities.publish( 128 | self.account.capabilities.storage.issue<&Mapper>( 129 | self.contractManagerStorage 130 | ), at: self.contractManagerPath) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /cadence/scripts/check-manager.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main(){ 4 | // the body can be empty, cause script will throw error if FlowManager is not 5 | // added to service address 6 | } 7 | -------------------------------------------------------------------------------- /cadence/scripts/get-account-address.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main(name: String, managerAccount: Address):Address? { 4 | let manager = getAccount(managerAccount) 5 | let linkPath = FlowManager.accountManagerPath 6 | let accountManager = manager.capabilities.borrow<&FlowManager.Mapper>(linkPath)! 7 | 8 | return accountManager.getAddress(name) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /cadence/scripts/get-block-offset.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main():UInt64 { 4 | return FlowManager.blockOffset 5 | } 6 | -------------------------------------------------------------------------------- /cadence/scripts/get-contract-address.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main(name: String, managerAccount: Address):Address? { 4 | let manager = getAccount(managerAccount) 5 | let linkPath = FlowManager.contractManagerPath 6 | let contractManager = manager.capabilities.borrow<&FlowManager.Mapper>(linkPath)! 7 | 8 | return contractManager.getAddress(name) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /cadence/scripts/get-manager-address.cdc: -------------------------------------------------------------------------------- 1 | access(all) fun main(serviceAddress: Address): Address? { 2 | let account = getAccount(serviceAddress) 3 | 4 | let ref = account.capabilities.borrow<&[Address]>(/public/flowManagerAddress)! 5 | 6 | return ref[0] 7 | } 8 | -------------------------------------------------------------------------------- /cadence/scripts/get-timestamp-offset.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main():UFix64 { 4 | return FlowManager.timestampOffset 5 | } 6 | -------------------------------------------------------------------------------- /cadence/scripts/get_balance.cdc: -------------------------------------------------------------------------------- 1 | // This script reads the balance field 2 | // of an account's ExampleToken Balance 3 | 4 | import FungibleToken from 0x1 5 | import ExampleToken from 0x1 6 | import FungibleTokenMetadataViews from 0x1 7 | 8 | access(all) fun main(address: Address): UFix64 { 9 | let vaultData = ExampleToken.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? 10 | ?? panic("Could not get vault data view for the contract") 11 | 12 | return getAccount(address).capabilities.borrow<&{FungibleToken.Balance}>( 13 | vaultData.metadataPath 14 | )?.balance 15 | ?? panic("Could not borrow Balance reference to the Vault") 16 | } 17 | -------------------------------------------------------------------------------- /cadence/transactions/create-account.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | transaction (_ name: String?, pubKey: [String], manager: Address) { 4 | prepare( admin: auth(BorrowValue) &Account) { 5 | let newAccount = Account(payer:admin) 6 | for key in pubKey { 7 | let keyData = RLP.decodeList(key.decodeHex()) 8 | let rawSign = RLP.decodeString(keyData[1])[0] 9 | let rawHash = RLP.decodeString(keyData[2])[0] 10 | newAccount.keys.add( 11 | publicKey: PublicKey( 12 | publicKey: RLP.decodeString(keyData[0]), 13 | signatureAlgorithm: SignatureAlgorithm(rawValue: rawSign)! 14 | ), 15 | hashAlgorithm: HashAlgorithm(rawValue: rawHash)!, 16 | weight: UFix64(Int32.fromBigEndianBytes(RLP.decodeString(keyData[3]))!)! 17 | ) 18 | } 19 | 20 | if name != nil { 21 | let linkPath = FlowManager.accountManagerPath 22 | let accountManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 23 | 24 | // Create a record in account database 25 | let address = newAccount.address 26 | accountManager.setAddress(name!, address: address) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cadence/transactions/deploy-contract.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | transaction(name:String, code: String, manager: Address ##ARGS-WITH-TYPES##) { 4 | prepare(acct: auth(AddContract) &Account) { 5 | let decoded = code.decodeHex() 6 | acct.contracts.add( 7 | name: name, 8 | code: decoded, 9 | ##ARGS-LIST## 10 | ) 11 | 12 | let linkPath = FlowManager.contractManagerPath 13 | let contractManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 14 | 15 | let address = acct.address 16 | contractManager.setAddress(name, address: address) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cadence/transactions/init-manager.cdc: -------------------------------------------------------------------------------- 1 | transaction ( code: String ) { 2 | prepare( admin: &Account) { 3 | admin.contracts.add( 4 | name: "FlowManager", 5 | code: code.decodeHex(), 6 | ) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cadence/transactions/mint_tokens.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from 0x1 2 | import ExampleToken from 0x1 3 | import FungibleTokenMetadataViews from 0x1 4 | 5 | transaction(recipient: Address, amount: UFix64) { 6 | let tokenAdmin: &FlowToken.Administrator 7 | let tokenReceiver: &{FungibleToken.Receiver} 8 | let supplyBefore: UFix64 9 | 10 | prepare(signer: auth(BorrowValue) &Account) { 11 | self.supplyBefore = ExampleToken.totalSupply 12 | 13 | // Borrow a reference to the admin object 14 | self.tokenAdmin = signer.storage.borrow<&ExampleToken.Administrator>(from: ExampleToken.AdminStoragePath) 15 | ?? panic("Signer is not the token admin") 16 | 17 | let vaultData = ExampleToken.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? 18 | ?? panic("Could not get vault data view for the contract") 19 | 20 | self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(vaultData.receiverPath) 21 | ?? panic("Could not borrow receiver reference to the Vault") 22 | } 23 | 24 | execute { 25 | let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) 26 | let mintedVault <- minter.mintTokens(amount: amount) 27 | 28 | self.tokenReceiver.deposit(from: <-mintedVault) 29 | 30 | destroy minter 31 | } 32 | 33 | post { 34 | ExampleToken.totalSupply == self.supplyBefore + amount: "The total supply must be increased by the amount" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cadence/transactions/register-contract.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | transaction(name: String, address: Address) { 4 | prepare(signer: auth(BorrowValue) &Account){ 5 | let linkPath = FlowManager.contractManagerPath 6 | let contractManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 7 | contractManager.setAddress(name, address: address) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cadence/transactions/scratch.cdc: -------------------------------------------------------------------------------- 1 | transaction{ 2 | prepare(acct: &Account){ 3 | log(acct.address) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cadence/transactions/set-block-offset.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | transaction(offset: UInt64){ 4 | prepare(signer: &Account){ 5 | FlowManager.setBlockOffset(offset) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /cadence/transactions/set-timestamp-offset.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | transaction(offset: UFix64){ 4 | prepare(signer: &Account){ 5 | FlowManager.setTimestampOffset(offset) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /cadence/transactions/update-contract.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | transaction(name:String, code: String, manager: Address ##ARGS-WITH-TYPES##) { 4 | prepare(acct: auth(AddContract, UpdateContract) &Account){ 5 | let decoded = code.decodeHex() 6 | 7 | if acct.contracts.get(name: name) == nil { 8 | acct.contracts.add( 9 | name: name, 10 | code: decoded, 11 | ##ARGS-LIST## 12 | ) 13 | } else { 14 | acct.contracts.update(name: name, code: decoded) 15 | } 16 | 17 | let linkPath = FlowManager.contractManagerPath 18 | let contractManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 19 | 20 | let address = acct.address 21 | contractManager.setAddress(name, address: address) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /check-headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | files=$(find ./src -path ./src/generated -prune -false -o -name \*.js -type f -print0 | xargs -0 egrep -L '(Licensed under the Apache License)|(Code generated from|by)') 4 | if [ -n "$files" ]; then 5 | echo "Missing license header in:" 6 | echo "$files" 7 | exit 1 8 | fi 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/README.md 4 | -------------------------------------------------------------------------------- /docs/accounts.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/accounts.md 4 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/api.md 4 | -------------------------------------------------------------------------------- /docs/contracts.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/contracts.md 4 | -------------------------------------------------------------------------------- /docs/emulator.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/emulator.md 4 | -------------------------------------------------------------------------------- /docs/examples/basic.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/examples/basic.md 4 | -------------------------------------------------------------------------------- /docs/examples/metadata.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/examples/metadata.md 4 | -------------------------------------------------------------------------------- /docs/execute-scripts.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/execute-scripts.md 4 | -------------------------------------------------------------------------------- /docs/flow-token.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/flow-token.md 4 | -------------------------------------------------------------------------------- /docs/generator.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/generator.md 4 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/index.md 4 | -------------------------------------------------------------------------------- /docs/init.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/init.md 4 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/install.md 4 | -------------------------------------------------------------------------------- /docs/jest-helpers.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/jest-helpers.md 4 | -------------------------------------------------------------------------------- /docs/send-transactions.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/send-transactions.md 4 | -------------------------------------------------------------------------------- /docs/structure.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/structure.md 4 | -------------------------------------------------------------------------------- /docs/templates.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/templates.md 4 | -------------------------------------------------------------------------------- /docs/types.md: -------------------------------------------------------------------------------- 1 | # This document has been moved to a new location: 2 | 3 | https://github.com/onflow/docs/tree/main/docs/tooling/flow-js-testing/types.md 4 | -------------------------------------------------------------------------------- /examples/01-get-account-address.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {init, emulator, getAccountAddress, isAddress} from "../src" 3 | 4 | beforeEach(async () => { 5 | const basePath = path.resolve(__dirname, "./cadence") 6 | 7 | await init(basePath) 8 | await emulator.start() 9 | }) 10 | 11 | test("get account address", async () => { 12 | const Alice = await getAccountAddress("Alice") 13 | 14 | // Expect Alice to be address of Alice's account 15 | expect(isAddress(Alice)).toBe(true) 16 | }) 17 | 18 | afterEach(async () => { 19 | await emulator.stop() 20 | }) 21 | -------------------------------------------------------------------------------- /examples/02-deploy-contract-by-name.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | deployContractByName, 6 | executeScript, 7 | shallResolve, 8 | shallPass, 9 | } from "../src" 10 | 11 | beforeEach(async () => { 12 | // Init framework 13 | const basePath = path.resolve(__dirname, "./cadence") 14 | await init(basePath) 15 | 16 | // Start Emulator 17 | await emulator.start() 18 | }) 19 | 20 | test("deploy contract by name", async () => { 21 | // Deploy contract Greeting with single argument 22 | await shallPass( 23 | deployContractByName({ 24 | name: "Greeting", 25 | args: ["Hello from Emulator"], 26 | }) 27 | ) 28 | 29 | // Read contract field via script 30 | const [greetingMessage] = await shallResolve( 31 | executeScript({ 32 | code: ` 33 | import Greeting from 0x1 34 | 35 | access(all) fun main(): String{ 36 | return Greeting.message 37 | } 38 | `, 39 | }) 40 | ) 41 | expect(greetingMessage).toBe("Hello from Emulator") 42 | 43 | // Deploy contract Hello with no arguments 44 | await deployContractByName({name: "Hello"}) 45 | const [helloMessage] = await shallResolve( 46 | executeScript({ 47 | code: ` 48 | import Hello from 0x01 49 | 50 | access(all) fun main():String{ 51 | return Hello.message 52 | } 53 | `, 54 | }) 55 | ) 56 | expect(helloMessage).toBe("Hi!") 57 | }) 58 | 59 | afterEach(async () => { 60 | // Stop Emulator 61 | await emulator.stop() 62 | }) 63 | -------------------------------------------------------------------------------- /examples/03-deploy-contract.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | getAccountAddress, 6 | deployContract, 7 | executeScript, 8 | shallResolve, 9 | shallPass, 10 | } from "../src" 11 | 12 | beforeEach(async () => { 13 | const basePath = path.resolve(__dirname, "../cadence") 14 | 15 | await init(basePath) 16 | await emulator.start() 17 | }) 18 | 19 | test("deploy contract", async () => { 20 | // We can specify, which account will hold the contract 21 | const to = await getAccountAddress("Alice") 22 | 23 | const name = "Wallet" 24 | const code = ` 25 | access(all) contract Wallet{ 26 | access(all) let balance: UInt 27 | init(balance: UInt){ 28 | self.balance = balance 29 | } 30 | } 31 | ` 32 | const args = ["1337"] 33 | 34 | await shallPass(deployContract({to, name, code, args})) 35 | 36 | const [balance] = await shallResolve( 37 | executeScript({ 38 | code: ` 39 | import Wallet from 0x01 40 | access(all) fun main(): UInt{ 41 | return Wallet.balance 42 | } 43 | `, 44 | }) 45 | ) 46 | expect(balance).toBe("1337") 47 | }) 48 | 49 | afterEach(async () => { 50 | await emulator.stop() 51 | }) 52 | -------------------------------------------------------------------------------- /examples/04-get-contract-address.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | deployContractByName, 6 | getContractAddress, 7 | isAddress, 8 | } from "../src" 9 | 10 | beforeEach(async () => { 11 | const basePath = path.resolve(__dirname, "./cadence") 12 | 13 | await init(basePath) 14 | await emulator.start() 15 | }) 16 | 17 | test("get contract address", async () => { 18 | // if we omit "to" it will be deployed to Service Account 19 | // but let's pretend we don't know where it will be deployed :) 20 | await deployContractByName({name: "Hello"}) 21 | 22 | const contractAddress = await getContractAddress("Hello") 23 | 24 | // Expect contractAddress to be address 25 | expect(isAddress(contractAddress)).toBe(true) 26 | }) 27 | 28 | afterEach(async () => { 29 | await emulator.stop() 30 | }) 31 | -------------------------------------------------------------------------------- /examples/05-emulator-management.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {emulator, init, executeScript} from "../src" 3 | 4 | beforeEach(async () => { 5 | const basePath = path.resolve(__dirname, "./cadence") 6 | 7 | await init(basePath) 8 | 9 | // Let's enable logging initially 10 | const logging = true 11 | 12 | // Enable only debug messages 13 | // emulator.addFilter("debug"); 14 | // emulator.addFilter("service"); 15 | emulator.addFilter("info") 16 | 17 | // Start emulator instance on available ports 18 | await emulator.start({logging}) 19 | }) 20 | 21 | // eslint-disable-next-line jest/expect-expect 22 | test("emulator management", async () => { 23 | // Let's define simple method to log message to emulator console 24 | const logMessage = async message => { 25 | return executeScript({ 26 | code: ` 27 | access(all) fun main(){ 28 | log("------------> ${message}") 29 | } 30 | `, 31 | }) 32 | } 33 | // This line will be visible in emulator output 34 | emulator.setLogging(true) 35 | await logMessage("Now you see me...") 36 | 37 | // Next turn it OFF 38 | emulator.setLogging(false) 39 | // Next log will not be visible in emulator output 40 | await logMessage("NOW YOU DON'T!") 41 | 42 | // And ON back again 43 | emulator.setLogging(true) 44 | await logMessage("Easy right?") 45 | 46 | // Now let's disable debug messages and only show "info" messages 47 | emulator.clearFilters() 48 | await logMessage("this won't be visible as well") 49 | 50 | // Then silently turn it off 51 | emulator.setLogging(false) 52 | }) 53 | 54 | afterEach(async () => { 55 | // Stop running emulator 56 | await emulator.stop() 57 | }) 58 | -------------------------------------------------------------------------------- /examples/06-flow-management.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | getAccountAddress, 6 | getFlowBalance, 7 | mintFlow, 8 | shallResolve, 9 | } from "../src" 10 | 11 | beforeEach(async () => { 12 | const basePath = path.resolve(__dirname, "./cadence") 13 | 14 | await init(basePath) 15 | await emulator.start() 16 | }) 17 | 18 | test("flow management", async () => { 19 | // Get address for account with alias "Alice" 20 | const Alice = await getAccountAddress("Alice") 21 | 22 | // Get initial balance 23 | const [initialBalance] = await shallResolve(getFlowBalance(Alice)) 24 | expect(initialBalance).toBe("0.00100000") 25 | 26 | // Add 1.0 FLOW tokens to Alice account 27 | await mintFlow(Alice, "1.0") 28 | 29 | // Check updated balance 30 | const [updatedBalance] = await shallResolve(getFlowBalance(Alice)) 31 | const expectedBalance = parseFloat(initialBalance) + 1.0 32 | expect(parseFloat(updatedBalance)).toBe(expectedBalance) 33 | 34 | await emulator.stop() 35 | }) 36 | -------------------------------------------------------------------------------- /examples/07-block-offset.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | getBlockOffset, 6 | setBlockOffset, 7 | builtInMethods, 8 | executeScript, 9 | shallResolve, 10 | } from "../src" 11 | 12 | beforeEach(async () => { 13 | const basePath = path.resolve(__dirname, "./cadence") 14 | 15 | await init(basePath) 16 | await emulator.start() 17 | }) 18 | 19 | test("block offset", async () => { 20 | const [initialBlockOffset] = await shallResolve(getBlockOffset()) 21 | expect(initialBlockOffset).toBe("0") 22 | 23 | // "getCurrentBlock().height" in your Cadence code will be replaced by Manager to a mocked value 24 | const code = ` 25 | access(all) fun main(): UInt64 { 26 | return getCurrentBlock().height 27 | } 28 | ` 29 | 30 | // We can check that non-transformed code still works just fine 31 | const [normalResult] = await shallResolve(executeScript({code})) 32 | expect(normalResult).toBe("1") 33 | 34 | // Offset current block height by 42 35 | await setBlockOffset("42") 36 | // Let's check that offset value on Manager is actually changed to 42 37 | const [blockOffset] = await getBlockOffset() 38 | expect(blockOffset).toBe("42") 39 | 40 | // "transformers" field expects array of functions to operate update the code. 41 | // We will pass single operator "builtInMethods" provided by the framework to alter how getCurrentBlock().height is calculated 42 | const transformers = [builtInMethods] 43 | const [transformedResult] = await shallResolve( 44 | executeScript({code, transformers}) 45 | ) 46 | expect(transformedResult).toBe("44") 47 | }) 48 | 49 | afterEach(async () => { 50 | // Stop the emulator 51 | await emulator.stop() 52 | }) 53 | -------------------------------------------------------------------------------- /examples/08-execute-script.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {init, emulator, executeScript, shallResolve} from "../src" 3 | 4 | beforeEach(async () => { 5 | const basePath = path.resolve(__dirname, "./cadence") 6 | 7 | await init(basePath) 8 | await emulator.start() 9 | }) 10 | 11 | test("execute script", async () => { 12 | // We have created a file called "log-args.cdc" under "./cadence/scripts" folder. 13 | // It's available for use since we configured framework to use "./cadence" folder as root 14 | const code = ` 15 | access(all) fun main(a: Int, b: Bool, c: String, d: UFix64, e: [Int], f: {String: String}, res: Int): Int{ 16 | log(a) 17 | log(b) 18 | log(c) 19 | log(d) 20 | log(e) 21 | log(f) 22 | 23 | return res 24 | } 25 | ` 26 | 27 | // args is just an array of values in the same order as they are defined in script code 28 | const args = [ 29 | "1337", 30 | true, 31 | "Hello, Cadence", 32 | "1.337", 33 | ["1", "3", "3", "7"], 34 | { 35 | name: "Cadence", 36 | status: "active", 37 | }, 38 | "42", 39 | ] 40 | const name = "log-args" 41 | 42 | const [fromCode, , logsFromCode] = await shallResolve( 43 | executeScript({code, args}) 44 | ) 45 | const [fromFile, , logsFromFile] = await shallResolve( 46 | executeScript({name, args}) 47 | ) 48 | 49 | // Expect logs to be as expected 50 | const expectedLogs = [ 51 | "1337", 52 | "true", 53 | "Hello, Cadence", 54 | "1.33700000", 55 | "[1, 3, 3, 7]", 56 | '{"name": "Cadence", "status": "active"}', 57 | ] 58 | expect(logsFromCode).toEqual(expectedLogs) 59 | expect(logsFromFile).toEqual(expectedLogs) 60 | 61 | expect(fromCode).toBe(fromFile) 62 | expect(fromCode).toBe("42") 63 | expect(fromFile).toBe("42") 64 | 65 | // "executeScript" also supports short form, accepting name of the file in "scripts folder 66 | // and array of arguments 67 | const [shortForm] = await executeScript("hello") 68 | expect(shortForm).toBe("Hello from Cadence") 69 | }) 70 | 71 | afterEach(async () => { 72 | await emulator.stop() 73 | }) 74 | -------------------------------------------------------------------------------- /examples/09-send-transaction.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | getAccountAddress, 6 | sendTransaction, 7 | shallPass, 8 | } from "../src" 9 | 10 | beforeEach(async () => { 11 | const basePath = path.resolve(__dirname, "./cadence") 12 | 13 | await init(basePath) 14 | await emulator.start() 15 | }) 16 | 17 | test("send transaction", async () => { 18 | emulator.addFilter(`debug`) 19 | 20 | const Alice = await getAccountAddress("Alice") 21 | const Bob = await getAccountAddress("Bob") 22 | 23 | const name = "log-signers" 24 | const code = ` 25 | transaction(message: String){ 26 | prepare(first: &Account, second: &Account){ 27 | log(message) 28 | log(first.address) 29 | log(second.address) 30 | } 31 | } 32 | ` 33 | const signers = [Alice, Bob] 34 | const args = ["Hello from Cadence"] 35 | 36 | // There are several ways to call "sendTransaction" 37 | // 1. Providing "code" field for Cadence template 38 | const [txInlineResult] = await shallPass( 39 | sendTransaction({code, signers, args}) 40 | ) 41 | // 2. Providing "name" field to read Cadence template from file in "./transaction" folder 42 | const [txFileResult, , fileLogs] = await shallPass( 43 | sendTransaction({name, signers, args}) 44 | ) 45 | 46 | // 3. Providing name of the file in short form (name, signers, args) 47 | const [txShortResult, , inlineLogs] = await shallPass( 48 | sendTransaction(name, signers, args) 49 | ) 50 | 51 | // Expect logs to be as expected 52 | const expectedLogs = ["Hello from Cadence", Alice.toString(), Bob.toString()] 53 | expect(fileLogs).toEqual(expectedLogs) 54 | expect(inlineLogs).toEqual(expectedLogs) 55 | 56 | // Check that all transaction results are the same 57 | expect(txFileResult).toEqual(txInlineResult) 58 | expect(txShortResult).toEqual(txInlineResult) 59 | }) 60 | 61 | afterEach(async () => { 62 | await emulator.stop() 63 | }) 64 | -------------------------------------------------------------------------------- /examples/10-templates.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | init, 4 | emulator, 5 | getTemplate, 6 | getContractCode, 7 | getScriptCode, 8 | getTransactionCode, 9 | } from "../src" 10 | 11 | beforeEach(async () => { 12 | const basePath = path.resolve(__dirname, "./cadence") 13 | 14 | await init(basePath) 15 | await emulator.start() 16 | }) 17 | 18 | test("templates", async () => { 19 | const addressMap = { 20 | Profile: "0xf8d6e0586b0a20c7", 21 | } 22 | 23 | const withPath = getTemplate( 24 | path.resolve(__dirname, "./cadence/scripts/replace-address.cdc"), 25 | addressMap 26 | ) 27 | 28 | const contractTemplate = await getContractCode({name: "Greeting", addressMap}) 29 | 30 | const transactionTemplate = await getTransactionCode({ 31 | name: "log-signers", 32 | addressMap, 33 | }) 34 | 35 | const scriptTemplate = await getScriptCode({name: "log-args", addressMap}) 36 | 37 | expect(withPath).toBeDefined() 38 | expect(contractTemplate).toBeDefined() 39 | expect(transactionTemplate).toBeDefined() 40 | expect(scriptTemplate).toBeDefined() 41 | }) 42 | 43 | afterEach(async () => { 44 | await emulator.stop() 45 | }) 46 | -------------------------------------------------------------------------------- /examples/100-pass-array-of-dictionaries.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {init, emulator, executeScript, shallResolve} from "../src" 3 | 4 | beforeEach(async () => { 5 | const basePath = path.resolve(__dirname, "./cadence") 6 | 7 | await init(basePath) 8 | await emulator.start() 9 | }) 10 | 11 | test("pass array of dictionaries", async () => { 12 | const code = ` 13 | access(all) fun main(meta: [{String:String}], key: String): String?{ 14 | return meta[0]![key] 15 | } 16 | ` 17 | const args = [ 18 | [ 19 | { 20 | name: "Giovanni Giorgio", 21 | nickname: "Giorgio", 22 | }, 23 | ], 24 | "name", 25 | ] 26 | 27 | const [result] = await shallResolve(executeScript({code, args})) 28 | expect(result).toBe("Giovanni Giorgio") 29 | }) 30 | 31 | afterEach(async () => { 32 | // Stop the emulator 33 | await emulator.stop() 34 | }) 35 | -------------------------------------------------------------------------------- /examples/101-pass-int-dictionary.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {init, emulator, executeScript, shallResolve} from "../src" 3 | 4 | beforeEach(async () => { 5 | const basePath = path.resolve(__dirname, "./cadence") 6 | 7 | await init(basePath) 8 | await emulator.start() 9 | }) 10 | 11 | test("pass int dictionary", async () => { 12 | const code = ` 13 | access(all) fun main(data: {UInt32: UInt32}, key: UInt32): UInt32?{ 14 | return data[key] 15 | } 16 | ` 17 | 18 | const args = [{0: "1", 1: "42"}, "1"] 19 | 20 | const [result] = await shallResolve(executeScript({code, args})) 21 | expect(result).toBe("42") 22 | }) 23 | 24 | afterEach(async () => { 25 | // Stop the emulator 26 | await emulator.stop() 27 | }) 28 | -------------------------------------------------------------------------------- /examples/102-pass-string-to-int-dictionary.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {init, emulator, executeScript, shallResolve} from "../src" 3 | 4 | beforeEach(async () => { 5 | const basePath = path.resolve(__dirname, "./cadence") 6 | 7 | await init(basePath) 8 | await emulator.start() 9 | }) 10 | 11 | test("pass string to int dictionary", async () => { 12 | const code = ` 13 | access(all) fun main(data: {String: UInt32}, key: String): UInt32?{ 14 | return data[key] 15 | } 16 | ` 17 | 18 | const args = [{cadence: "0", test: "1337"}, "cadence"] 19 | 20 | const [result] = await shallResolve(executeScript({code, args})) 21 | expect(result).toBe("0") 22 | }) 23 | 24 | afterEach(async () => { 25 | // Stop the emulator 26 | await emulator.stop() 27 | }) 28 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Together with examples in this folder you can find a `run.js` file which will allow you to easily run any of the provided examples. 2 | Simply run `node run {fullFileName | number}` like this: 3 | 4 | ```shell 5 | node run 01 6 | ``` 7 | 8 | and Node will run the script for you. 9 | 10 | If argument you provided will not hit any files, tool will give you a handy list of available examples. 11 | Have fun hacking! 12 | -------------------------------------------------------------------------------- /examples/cadence/contracts/Greeting.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from 0x1 2 | 3 | access(all) contract Greeting{ 4 | access(all) let message: String 5 | 6 | init(message: String){ 7 | self.message = message 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/cadence/contracts/Hello.cdc: -------------------------------------------------------------------------------- 1 | access(all) contract Hello{ 2 | access(all) let message: String 3 | 4 | init(){ 5 | self.message = "Hi!" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/cadence/scripts/hello.cdc: -------------------------------------------------------------------------------- 1 | access(all) fun main(): String{ 2 | return "Hello from Cadence" 3 | } 4 | -------------------------------------------------------------------------------- /examples/cadence/scripts/log-args.cdc: -------------------------------------------------------------------------------- 1 | access(all) fun main(a: Int, b: Bool, c: String, d: UFix64, e: [Int], f: {String: String}, res: Int): Int{ 2 | log(a) 3 | log(b) 4 | log(c) 5 | log(d) 6 | log(e) 7 | log(f) 8 | 9 | return res 10 | } 11 | -------------------------------------------------------------------------------- /examples/cadence/scripts/replace-address.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from 0x01 2 | import Profile from 0xProfile 3 | 4 | access(all) fun main(): Int{ 5 | return 42 6 | } 7 | -------------------------------------------------------------------------------- /examples/cadence/transactions/log-signers.cdc: -------------------------------------------------------------------------------- 1 | transaction(message: String){ 2 | prepare(first: &Account, second: &Account){ 3 | log(message) 4 | log(first.address) 5 | log(second.address) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "default": { 4 | "port": 3569, 5 | "serviceAccount": "emulator-account" 6 | } 7 | }, 8 | "contracts": {}, 9 | "networks": { 10 | "emulator": "127.0.0.1:3569", 11 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 12 | "testnet": "access.devnet.nodes.onflow.org:9000" 13 | }, 14 | "accounts": { 15 | "emulator-account": { 16 | "address": "f8d6e0586b0a20c7", 17 | "key": "98e4e163c9494dbfc2dc271f48941ed7113890d5fddc2cfa8a603836a09806b8" 18 | } 19 | }, 20 | "deployments": {} 21 | } 22 | -------------------------------------------------------------------------------- /examples/run.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const fs = require("fs") 3 | 4 | // Regexp to match files in folder 5 | const exampleMatcher = /(\.\/)?\w{1,3}-.*\.js/g 6 | 7 | const printTitle = (exampleName, padSymbol) => { 8 | if (exampleName.endsWith(".js")) { 9 | exampleName = exampleName.slice(0, -3) 10 | } 11 | const fixedName = exampleName 12 | .split("-") 13 | .map((item, i) => { 14 | return i === 0 ? `#${item} -` : item[0].toUpperCase() + item.slice(1) 15 | }) 16 | .join(" ") 17 | const title = `Launching Example ${fixedName}` 18 | const divider = "".padEnd(title.length + 4, padSymbol) 19 | 20 | console.log(divider) 21 | console.log(title) 22 | console.log(divider) 23 | } 24 | 25 | fs.readdir(__dirname, (err, files) => { 26 | const examples = files.filter(file => file.match(exampleMatcher)) 27 | let [exampleName] = process.argv.slice(2) 28 | 29 | let filepath 30 | if (exampleName.match(exampleMatcher)) { 31 | filepath = path.resolve(__dirname, exampleName) 32 | } else { 33 | const name = examples.find(item => item.includes(exampleName)) 34 | if (!name) { 35 | filepath = null 36 | } else { 37 | filepath = path.resolve(__dirname, name) 38 | } 39 | } 40 | 41 | if (filepath) { 42 | const title = filepath.match(/\d{1,3}-.*.js$/)[0] 43 | printTitle(title, "=") 44 | 45 | process.chdir("../") 46 | // eslint-disable-next-line jest/no-jest-import 47 | require("jest").run(`--runTestsByPath ${filepath}`) 48 | } else { 49 | console.log(`Example "${exampleName}" not found!\n`) 50 | console.log("Try one of available examples:", examples) 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "networks": { 3 | "emulator": "127.0.0.1:3569", 4 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 5 | "sandboxnet": "access.sandboxnet.nodes.onflow.org:9000", 6 | "testnet": "access.devnet.nodes.onflow.org:9000" 7 | }, 8 | "accounts": { 9 | "emulator-account": { 10 | "address": "f8d6e0586b0a20c7", 11 | "key": "992e51111af4107f1521afffa297788e4b7f83a2012811d6d1ba73d6296216de" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@onflow/flow-js-testing", 3 | "version": "0.6.0", 4 | "description": "This package will expose a set of utility methods, to allow Cadence code testing with libraries like Jest", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/onflow/flow-js-testing" 8 | }, 9 | "bin": { 10 | "flow-js-testing": "./bin/index.js" 11 | }, 12 | "scripts": { 13 | "build": "microbundle --no-compress", 14 | "generate-code": "node_modules/.bin/flow-generate -i ./cadence -o ./src/generated", 15 | "lint": "eslint -c .eslintrc.js src", 16 | "check-headers": "sh ./check-headers.sh", 17 | "prettify": "prettier --write ./src", 18 | "start": "microbundle watch", 19 | "test": "jest --runInBand --coverage", 20 | "changeset": "changeset", 21 | "prerelease": "npm run build && npm run test", 22 | "release": "changeset publish" 23 | }, 24 | "keywords": [ 25 | "flow", 26 | "cadence", 27 | "testing" 28 | ], 29 | "source": "src/index.js", 30 | "main": "dist/index.js", 31 | "module": "dist/index.module.js", 32 | "unpkg": "dist/index.umd.js", 33 | "sideEffects": false, 34 | "author": "Maksim Daunarovich", 35 | "license": "Apache-2.0", 36 | "prettier": { 37 | "semi": false, 38 | "trailingComma": "es5", 39 | "bracketSpacing": false, 40 | "arrowParens": "avoid" 41 | }, 42 | "jest": { 43 | "collectCoverageFrom": [ 44 | "src/**/*.js", 45 | "!src/**/index.js", 46 | "!src/generated/**/*.js", 47 | "!src/cli/**/*.js", 48 | "!src/exports.js" 49 | ], 50 | "testPathIgnorePatterns": [ 51 | "/node_modules/" 52 | ], 53 | "testMatch": [ 54 | "**/(test)/**/?(*.)+(spec|test).[jt]s?(x)" 55 | ] 56 | }, 57 | "dependencies": { 58 | "@onflow/fcl": "1.3.2", 59 | "@onflow/flow-cadut": "^0.3.0-stable-cadence.1", 60 | "elliptic": "^6.5.4", 61 | "esm": "^3.2.25", 62 | "jest-environment-uint8array": "^1.0.0", 63 | "js-sha256": "^0.9.0", 64 | "js-sha3": "^0.8.0", 65 | "rimraf": "^3.0.2", 66 | "rlp": "^2.2.6", 67 | "semver": "^7.6.2", 68 | "yargs": "^17.0.1" 69 | }, 70 | "devDependencies": { 71 | "@babel/core": "^7.21.0", 72 | "@babel/preset-env": "^7.14.5", 73 | "@changesets/changelog-github": "^0.4.5", 74 | "@changesets/cli": "^2.23.0", 75 | "@onflow/flow-cadut-generator": "^0.1.1-stable-cadence.0", 76 | "babel-jest": "^27.0.2", 77 | "eslint": "^7.24.0", 78 | "eslint-config-prettier": "^8.5.0", 79 | "eslint-plugin-jest": "^24.3.6", 80 | "eslint-plugin-prettier": "^4.2.1", 81 | "jest": "^27.0.3", 82 | "jest-esm-transformer": "^1.0.0", 83 | "microbundle": "^0.13.0", 84 | "prettier": "^2.2.1" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/account.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {executeScript, sendTransaction} from "./interaction" 20 | import {getManagerAddress} from "./manager" 21 | 22 | import registry from "./generated" 23 | import {config} from "@onflow/fcl" 24 | import {pubFlowKey} from "./crypto" 25 | import {isObject} from "./utils" 26 | 27 | export async function createAccount({name, keys}) { 28 | if (!keys) { 29 | keys = [ 30 | { 31 | privateKey: await config().get("PRIVATE_KEY"), 32 | }, 33 | ] 34 | } 35 | 36 | // If public key is encoded already, don't change 37 | // If provided as KeyObject (private key) generate public key 38 | // TODO since old key API is deprecated, might want to pass the key in a different way (struct?) 39 | keys = await Promise.all( 40 | keys.map(key => (isObject(key) ? pubFlowKey(key) : key)) 41 | ) 42 | 43 | const managerAddress = await getManagerAddress() 44 | const addressMap = { 45 | FlowManager: managerAddress, 46 | } 47 | 48 | const code = await registry.transactions.createAccountTemplate(addressMap) 49 | const args = [name, keys, managerAddress] 50 | 51 | const [result, error] = await sendTransaction({ 52 | code, 53 | args, 54 | }) 55 | if (error) throw error 56 | const {events} = result 57 | const event = events.find(event => event.type.includes("AccountAdded")) 58 | const address = event?.data?.address 59 | 60 | return address 61 | } 62 | 63 | /** 64 | * Returns address of account specified by name. If account with that name doesn't exist it will be created 65 | * and assigned provided name as alias 66 | * @param {string} accountName - name of the account 67 | * @returns {Promise} 68 | */ 69 | export const getAccountAddress = async accountName => { 70 | const name = 71 | accountName || 72 | `deployment-account-${(Math.random() * Math.pow(10, 8)).toFixed(0)}` 73 | 74 | const managerAddress = await getManagerAddress() 75 | 76 | const addressMap = { 77 | FlowManager: managerAddress, 78 | } 79 | 80 | let accountAddress 81 | 82 | const code = await registry.scripts.getAccountAddressTemplate(addressMap) 83 | 84 | const args = [name, managerAddress] 85 | 86 | const [result] = await executeScript({ 87 | code, 88 | args, 89 | service: true, 90 | }) 91 | accountAddress = result 92 | 93 | if (accountAddress === null) { 94 | accountAddress = await createAccount({name}) 95 | } 96 | return accountAddress 97 | } 98 | -------------------------------------------------------------------------------- /src/cli/commands/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export {default as make} from "./make" 20 | export {default as init} from "./init" 21 | -------------------------------------------------------------------------------- /src/cli/commands/init.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {execSync} from "child_process" 20 | 21 | import {writeFile} from "../utils" 22 | import babelConfig from "../templates/babel-config" 23 | import jestConfig from "../templates/jest-config" 24 | 25 | const command = { 26 | command: "init", 27 | describe: "Install dependencies and prepare config files", 28 | handler: () => { 29 | console.log("\n🔧 Installing dependencies") 30 | execSync("npm init --yes", {stdio: [0, 1, 2]}) 31 | execSync( 32 | "npm install --save-dev @babel/core @babel/preset-env babel-jest jest jest-environment-node @onflow/flow-js-testing", 33 | { 34 | stdio: [0, 1, 2], 35 | } 36 | ) 37 | 38 | console.log("🏄 Generating Flow config") 39 | execSync("flow init --reset") 40 | 41 | console.log("🧪 Creating Babel and Jest config files") 42 | writeFile("./babel.config.json", babelConfig) 43 | writeFile("./jest.config.json", jestConfig) 44 | 45 | console.log("👍 Done! \n") 46 | console.log( 47 | "\n 👉 You can create new test file with 'npx @onflow/flow-js-testing make' command \n" 48 | ) 49 | }, 50 | } 51 | 52 | export default command 53 | -------------------------------------------------------------------------------- /src/cli/commands/make.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {writeFile} from "../utils" 20 | import testTemplate from "../templates/test" 21 | 22 | const hashedTimestamp = () => { 23 | const s = new Date().getTime().toString() 24 | var hash = 0 25 | if (s.length === 0) { 26 | return hash 27 | } 28 | for (var i = 0; i < s.length; i++) { 29 | var char = s.charCodeAt(i) 30 | hash = (hash << 5) - hash + char 31 | hash = hash & hash // Convert to 32bit integer 32 | } 33 | return hash 34 | } 35 | 36 | const command = { 37 | command: "make [name]", 38 | aliases: ["new"], 39 | describe: "Generate test suite", 40 | builder: yargs => { 41 | return yargs 42 | .positional("name", { 43 | describe: "- test suite and file prefix to use", 44 | }) 45 | .option("clear", { 46 | alias: "c", 47 | type: "boolean", 48 | description: "Exclude comments from test suite code", 49 | }) 50 | .option("base-path", { 51 | alias: "b", 52 | type: "string", 53 | description: "Exclude comments from test suite code", 54 | }) 55 | }, 56 | handler: args => { 57 | const name = args.name || `test-suite${hashedTimestamp()}` 58 | const basePath = args.basePath || "../cadence" 59 | const clear = args.clear 60 | 61 | console.log(`\n🔧 Generating test suite "${name}"`) 62 | const content = testTemplate(name, basePath, !clear) 63 | writeFile(`./${name}.test.js`, content) 64 | 65 | console.log("👍 Done! \n") 66 | }, 67 | } 68 | 69 | export default command 70 | -------------------------------------------------------------------------------- /src/cli/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import yargs from "yargs/yargs" 20 | import {init, make} from "./commands" 21 | 22 | export async function run(args) { 23 | yargs(args.slice(2)).command(make).command(init).argv 24 | } 25 | -------------------------------------------------------------------------------- /src/cli/templates/babel-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export default `{ 20 | "presets": [ 21 | [ 22 | "@babel/preset-env", 23 | { 24 | "targets": { 25 | "node": "current" 26 | } 27 | } 28 | ] 29 | ] 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /src/cli/templates/jest-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export default `{ 20 | "testEnvironment": "node", 21 | "verbose": true, 22 | "coveragePathIgnorePatterns": ["/node_modules/"], 23 | "testTimeout": 50000 24 | } 25 | ` 26 | -------------------------------------------------------------------------------- /src/cli/templates/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export default (name, basePath, comments) => `import path from "path"; 20 | import { emulator, init } from "@onflow/flow-js-testing"; 21 | ${comments ? "\n// Increase timeout if your tests failing due to timeout" : ""} 22 | jest.setTimeout(10000); 23 | 24 | describe("${name}", ()=>{ 25 | beforeEach(async () => { 26 | const basePath = path.resolve(__dirname, "${basePath}"); 27 | const logging = false; 28 | 29 | await init(basePath); 30 | return emulator.start({ logging }); 31 | }); 32 | ${comments ? "\n // Stop emulator, so it could be restarted" : ""} 33 | afterEach(async () => { 34 | return emulator.stop(); 35 | }); 36 | 37 | test("basic assertion", async () => { 38 | ${comments ? "// WRITE YOUR ASSERTS HERE" : ""} 39 | }) 40 | }) 41 | ` 42 | -------------------------------------------------------------------------------- /src/cli/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {dirname} from "path" 20 | import fs from "fs" 21 | 22 | export const writeFile = (path, data) => { 23 | const targetDir = dirname(path) 24 | fs.mkdirSync(targetDir, {recursive: true}) 25 | return fs.writeFileSync(path, data, {encoding: "utf8"}) 26 | } 27 | -------------------------------------------------------------------------------- /src/contract.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {getManagerAddress} from "./manager" 20 | import {executeScript} from "./interaction" 21 | import {defaultsByName} from "./file" 22 | 23 | import registry from "./generated" 24 | 25 | /** 26 | * Returns address of the account where contract specified by name is currently deployed 27 | * @param {string} name - name of the account to look for 28 | * @param {boolean} [useDefaults=false] - whether we shall look into default addressed first 29 | * @returns {Promise} 30 | */ 31 | export const getContractAddress = async (name, useDefaults = false) => { 32 | // TODO: Maybe try to automatically deploy contract? 🤔 33 | 34 | if (useDefaults) { 35 | const defaultContract = defaultsByName[name] 36 | if (defaultContract !== undefined) { 37 | return defaultContract 38 | } 39 | } 40 | 41 | const managerAddress = await getManagerAddress() 42 | const addressMap = {FlowManager: managerAddress} 43 | 44 | const code = await registry.scripts.getContractAddressTemplate(addressMap) 45 | const args = [name, managerAddress] 46 | const [contractAddress] = await executeScript({ 47 | code, 48 | args, 49 | service: true, 50 | }) 51 | 52 | return contractAddress 53 | } 54 | -------------------------------------------------------------------------------- /src/deploy-code.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {sendTransaction} from "./interaction" 20 | import {getServiceAddress} from "./utils" 21 | import {defaultsByName, getContractCode} from "./file" 22 | 23 | import txRegistry from "./generated/transactions" 24 | import {isObject} from "./utils" 25 | import { 26 | extractContractParameters, 27 | generateSchema, 28 | splitArgs, 29 | } from "@onflow/flow-cadut" 30 | import {replaceImportAddresses, resolveImports} from "./imports" 31 | import {applyTransformers, builtInMethods} from "./transformers" 32 | 33 | const {updateContractTemplate, deployContractTemplate} = txRegistry 34 | 35 | export const hexContract = contract => 36 | Buffer.from(contract, "utf8").toString("hex") 37 | 38 | const extractParameters = async params => { 39 | let ixName, ixTo, ixAddressMap, ixArgs, ixUpdate 40 | 41 | if (isObject(params[0])) { 42 | const [props] = params 43 | const {name, to, addressMap, args, update} = props 44 | 45 | if (!name) { 46 | throw Error("'name' field is missing") 47 | } 48 | 49 | ixName = name 50 | ixTo = to 51 | ixArgs = args 52 | ixAddressMap = addressMap 53 | ixUpdate = update 54 | } else { 55 | ;[ixName, ixTo, ixAddressMap, ixArgs, ixUpdate] = params 56 | } 57 | 58 | const serviceAddress = await getServiceAddress() 59 | const addressMap = { 60 | ...defaultsByName, 61 | FlowManager: serviceAddress, 62 | ...ixAddressMap, 63 | } 64 | 65 | return { 66 | name: ixName, 67 | to: ixTo, 68 | args: ixArgs, 69 | update: ixUpdate, 70 | addressMap, 71 | } 72 | } 73 | 74 | /** 75 | * Deploys a contract by name to specified account 76 | * Returns transaction result. 77 | * @param {string} props.to - If no address is supplied, the contract will be deployed to the emulator service account. 78 | * @param {string} props.name - The name of the contract to look for. This should match a .cdc file located at the specified `basePath`. 79 | * @param {{string:string}} [props.addressMap={}] - name/address map to use as lookup table for addresses in import statements. 80 | * @param {boolean} [props.update=false] - flag to indicate whether the contract shall be replaced. 81 | * @returns {Promise} 82 | */ 83 | export const deployContractByName = async (...props) => { 84 | const params = await extractParameters(props) 85 | const {to, name, addressMap, args, update = false} = params 86 | 87 | const resolvedAddress = to || (await getServiceAddress()) 88 | const contractCode = await getContractCode({name, addressMap}) 89 | 90 | const ixName = /[\\/]/.test(name) ? null : name 91 | 92 | return deployContract({ 93 | to: resolvedAddress, 94 | code: contractCode, 95 | name: ixName, 96 | args, 97 | update, 98 | }) 99 | } 100 | 101 | /** 102 | * Deploys contract as Cadence code to specified account 103 | * Returns transaction result. 104 | * @param {Object} props 105 | * @param {string} props.code - Cadence code for contract to be deployed 106 | * @param {string} props.to - If no address is supplied, the contract 107 | * will be deployed to the emulator service account 108 | * @param {string} props.name - The name of the contract to look for. This should match 109 | * a .cdc file located at the specified `basePath` 110 | * @param {{string:string}} [props.addressMap={}] - name/address map to use as lookup table for addresses in import statements. 111 | * @param {boolean} [props.update=false] - flag to indicate whether the contract shall be replaced 112 | * @param {[(code: string) => string]} [props.transformers] - code transformers to apply to contract 113 | */ 114 | export const deployContract = async props => { 115 | const { 116 | to, 117 | code: rawContractCode, 118 | name, 119 | args, 120 | update, 121 | transformers = [], 122 | } = props 123 | 124 | const params = await extractContractParameters(rawContractCode) 125 | const ixName = name || params.contractName 126 | 127 | // TODO: extract name from contract code 128 | const containerAddress = to || (await getServiceAddress()) 129 | const managerAddress = await getServiceAddress() 130 | 131 | // Resolve contract import addresses 132 | const deployedContracts = await resolveImports(rawContractCode) 133 | const serviceAddress = await getServiceAddress() 134 | const addressMap = { 135 | ...defaultsByName, 136 | ...deployedContracts, 137 | FlowManager: serviceAddress, 138 | } 139 | 140 | // Replace contract import addresses 141 | let contractCode = replaceImportAddresses(rawContractCode, addressMap) 142 | 143 | // Apply code transformers 144 | contractCode = await applyTransformers(contractCode, [ 145 | ...transformers, 146 | builtInMethods, 147 | ]) 148 | 149 | const hexedCode = hexContract(contractCode) 150 | 151 | let code = update 152 | ? await updateContractTemplate(addressMap) 153 | : await deployContractTemplate(addressMap) 154 | 155 | let deployArgs = [ixName, hexedCode, managerAddress] 156 | 157 | if (args) { 158 | deployArgs = deployArgs.concat(args) 159 | const schema = generateSchema(params.args).map(item => splitArgs(item)[0]) 160 | 161 | const argLetter = "abcdefghijklmnopqrstuvwxyz" 162 | let argList = [] 163 | for (let i = 0; i < schema.length; i++) { 164 | const value = schema[i] 165 | argList.push(`${argLetter[i]}: ${value}`) 166 | } 167 | 168 | code = code.replace("##ARGS-WITH-TYPES##", `, ${params.args}`) 169 | code = code.replace("##ARGS-LIST##", argList) 170 | } else { 171 | code = code.replace("##ARGS-WITH-TYPES##", ``) 172 | code = code.replace("##ARGS-LIST##", "") 173 | } 174 | 175 | const signers = [containerAddress] 176 | 177 | return sendTransaction({ 178 | code, 179 | args: deployArgs, 180 | signers, 181 | }) 182 | } 183 | -------------------------------------------------------------------------------- /src/emulator/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export {default as emulator} from "./emulator" 20 | export {LOGGER_LEVELS} from "./logger" 21 | -------------------------------------------------------------------------------- /src/emulator/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {EventEmitter} from "events" 20 | 21 | /** 22 | * Enum of all logger levels 23 | * @readonly 24 | * @enum {number} 25 | */ 26 | export const LOGGER_LEVELS = { 27 | PANIC: 5, 28 | FATAL: 4, 29 | ERROR: 3, 30 | WARN: 2, 31 | INFO: 1, 32 | DEBUG: 0, 33 | TRACE: -1, 34 | } 35 | 36 | // eslint-disable-next-line no-control-regex 37 | const LOG_REGEXP = /LOG:.*?\s+(.*)/ 38 | 39 | export class Logger extends EventEmitter { 40 | constructor(options) { 41 | super(options) 42 | this.handleMessage = this.handleMessage.bind(this) 43 | this.process = null 44 | this.setMaxListeners(100) 45 | } 46 | 47 | /** 48 | * Sets the emulator process to monitor logs of 49 | * @param {import("child_process").ChildProcessWithoutNullStreams} process 50 | * @returns {void} 51 | */ 52 | setProcess(process) { 53 | if (this.process) { 54 | this.process.stdout.removeListener("data", this.handleMessage) 55 | this.process.stderr.removeListener("data", this.handleMessage) 56 | } 57 | 58 | this.process = process 59 | this.process.stdout.on("data", this.handleMessage) 60 | this.process.stderr.on("data", this.handleMessage) 61 | } 62 | 63 | handleMessage(buffer) { 64 | const logs = this.parseDataBuffer(buffer) 65 | logs.forEach(({level, msg, ...data}) => { 66 | // Handle log special case 67 | const levelMatch = 68 | level === LOGGER_LEVELS.INFO || level === LOGGER_LEVELS.DEBUG 69 | 70 | const logMatch = LOG_REGEXP.test(msg) 71 | if (levelMatch && logMatch) { 72 | let logMessage = msg.match(LOG_REGEXP).at(1) 73 | // if message is string, remove from surrounding and unescape 74 | if (/^"(.*)"/.test(logMessage)) { 75 | logMessage = logMessage 76 | .substring(1, logMessage.length - 1) 77 | .replace(/\\"/g, '"') 78 | } 79 | 80 | this.emit("log", logMessage) 81 | } 82 | 83 | // Emit emulator message to listeners 84 | this.emit("message", {level, msg, ...data}) 85 | }) 86 | } 87 | 88 | fixJSON(msg) { 89 | // TODO: Test this functionality 90 | const split = msg.split("\n").filter(item => item !== "") 91 | return split.length > 1 ? `[${split.join(",")}]` : split[0] 92 | } 93 | 94 | parseDataBuffer(dataBuffer) { 95 | const data = dataBuffer.toString() 96 | try { 97 | if (data.includes("msg")) { 98 | let messages = JSON.parse(this.fixJSON(data)) 99 | 100 | // Make data into array if not array 101 | messages = [].concat(messages) 102 | 103 | // Map string levels to enum 104 | messages = messages.map(m => ({ 105 | ...m, 106 | level: LOGGER_LEVELS[m.level.toUpperCase()], 107 | })) 108 | 109 | return messages 110 | } 111 | } catch (e) { 112 | console.error(e) 113 | } 114 | return [] 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/exports.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import registry from "./generated" 20 | 21 | export const {setBlockOffset} = registry.transactions 22 | export const {getBlockOffset} = registry.scripts 23 | -------------------------------------------------------------------------------- /src/file.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import fs from "fs" 20 | import path from "path" 21 | import {config} from "@onflow/fcl" 22 | 23 | import {fixShorthandImports, replaceImportAddresses} from "./imports" 24 | import {isObject} from "./utils" 25 | 26 | export const readFile = path => { 27 | const code = fs.readFileSync(path, "utf8") 28 | // TODO remove once flow-cadut is updated to support short-hand imports 29 | return fixShorthandImports(code) 30 | } 31 | 32 | /** 33 | * Address map with access by name for contracts deployed to emulator by default. 34 | * @type {{FlowFees: string, FlowToken: string, FungibleToken: string}} 35 | */ 36 | export const defaultsByName = { 37 | FlowToken: "0x0ae53cb6e3f42a79", 38 | FungibleToken: "0xee82856bf20e2aa6", 39 | FungibleTokenMetadataViews: "0xee82856bf20e2aa6", 40 | FungibleTokenSwitchboard: "0xee82856bf20e2aa6", 41 | FlowFees: "0xe5a8b7f23e8b548f", 42 | FlowStorageFees: "0xf8d6e0586b0a20c7", 43 | 44 | FlowIDTableStaking: "0xf8d6e0586b0a20c7", 45 | FlowEpoch: "0xf8d6e0586b0a20c7", 46 | FlowClusterQC: "0xf8d6e0586b0a20c7", 47 | FlowDKG: "0xf8d6e0586b0a20c7", 48 | FlowStakingCollection: "0xf8d6e0586b0a20c7", 49 | 50 | FlowServiceAccount: "0xf8d6e0586b0a20c7", 51 | RandomBeaconHistory: "0xf8d6e0586b0a20c7", 52 | NodeVersionBeacon: "0xf8d6e0586b0a20c7", 53 | 54 | EVM: "0xf8d6e0586b0a20c7", 55 | 56 | FUSD: "0xf8d6e0586b0a20c7", 57 | NonFungibleToken: "0xf8d6e0586b0a20c7", 58 | MetadataViews: "0xf8d6e0586b0a20c7", 59 | ViewResolver: "0xf8d6e0586b0a20c7", 60 | NFTStorefront: "0xf8d6e0586b0a20c7", 61 | NFTStorefrontV2: "0xf8d6e0586b0a20c7", 62 | } 63 | 64 | /** 65 | * Address map with access by address for contracts deployed to emulator by default. 66 | * @type {{"0xe5a8b7f23e8b548f": string, "0xf8d6e0586b0a20c7": string, "0xee82856bf20e2aa6": string, "0x0ae53cb6e3f42a79": string}} 67 | */ 68 | export const defaultsByAddress = { 69 | "0xe5a8b7f23e8b548f": "0xe5a8b7f23e8b548f", // FlowFees 70 | "0xf8d6e0586b0a20c7": "0xf8d6e0586b0a20c7", // FlowStorageFees 71 | "0x0ae53cb6e3f42a79": "0x0ae53cb6e3f42a79", // FlowToken 72 | "0xee82856bf20e2aa6": "0xee82856bf20e2aa6", // FungibleToken 73 | } 74 | 75 | const SCRIPT = "scripts" 76 | const TRANSACTION = "transactions" 77 | const CONTRACT = "contracts" 78 | 79 | export const templateType = { 80 | SCRIPT, 81 | TRANSACTION, 82 | CONTRACT, 83 | } 84 | 85 | export const getPath = async (name, type = TRANSACTION) => { 86 | const configBase = await config().get("BASE_PATH") 87 | 88 | // We can simply overwrite "configBase" variable, but I believe it's better to leave it unchanged 89 | let basePath = configBase 90 | 91 | // It's possible to pass a set of paths via object, so we need to check if that's the case 92 | if (isObject(configBase)) { 93 | const typePath = configBase[type] 94 | 95 | // if there is a specific path for this type, then we shall resolve it 96 | if (typePath) { 97 | return path.resolve(typePath, `./${name}.cdc`) 98 | } 99 | 100 | // otherwise use "base" value 101 | basePath = configBase.base 102 | } 103 | 104 | return path.resolve(basePath, `./${type}/${name}.cdc`) 105 | } 106 | 107 | /** 108 | * Returns Cadence template for specified file. Replaces imports using provided address map 109 | * @param file - name of the file to look for. 110 | * @param {{string:string}} [addressMap={}] - name/address map to use as lookup table for addresses in import statements. 111 | * @param {boolean} [byAddress=false] - flag to indicate if address map is address to address type. 112 | * @returns {string} 113 | */ 114 | export const getTemplate = (file, addressMap = {}, byAddress = false) => { 115 | const rawCode = readFile(file) 116 | 117 | const defaults = byAddress ? defaultsByAddress : defaultsByName 118 | 119 | return addressMap 120 | ? replaceImportAddresses(rawCode, { 121 | ...defaults, 122 | ...addressMap, 123 | }) 124 | : rawCode 125 | } 126 | 127 | /** 128 | * Returns contract template using name of the file in "contracts" folder containing the code. 129 | * @param name - name of the contract template in "contract" folder. 130 | * @param {{string:string}} [addressMap={}] - name/address map to use as lookup table for addresses in import statements. 131 | * @returns {Promise} 132 | */ 133 | export const getContractCode = async ({name, addressMap}) => { 134 | const path = await getPath(name, templateType.CONTRACT) 135 | return getTemplate(path, addressMap) 136 | } 137 | 138 | /** 139 | * Returns transaction template using name of the file in "transactions" folder containing the code. 140 | * @param name - name of the transaction template in "transactions" folder. 141 | * @param {{string:string}} [addressMap={}] - name/address map to use as lookup table for addresses in import statements. 142 | * @returns {Promise} 143 | */ 144 | export const getTransactionCode = async ({name, addressMap}) => { 145 | const path = await getPath(name, templateType.TRANSACTION) 146 | return getTemplate(path, addressMap) 147 | } 148 | 149 | /** 150 | * Returns script template using name of the file in "scripts" folder containing the code. 151 | * @param name - name of the script template in "scripts" folder. 152 | * @param {{string:string}} [addressMap={}] - name/address map to use as lookup table for addresses in import statements. 153 | * @returns {Promise} 154 | */ 155 | export const getScriptCode = async ({name, addressMap}) => { 156 | const path = await getPath(name, templateType.SCRIPT) 157 | return getTemplate(path, addressMap) 158 | } 159 | -------------------------------------------------------------------------------- /src/flow-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import path from "path" 20 | import fs from "fs" 21 | 22 | const TARGET = "flow.json" 23 | let configPath = null 24 | let config = null 25 | 26 | function isDir(dir) { 27 | return fs.lstatSync(dir).isDirectory() 28 | } 29 | 30 | function listFiles(dir) { 31 | return new Set(fs.readdirSync(dir)) 32 | } 33 | 34 | function parentDir(dir) { 35 | return path.dirname(dir) 36 | } 37 | 38 | function findTarget(dir) { 39 | if (!isDir(dir)) throw new Error(`Not a directory: ${dir}`) 40 | return listFiles(dir).has(TARGET) ? path.resolve(dir, TARGET) : null 41 | } 42 | 43 | export function getConfigPath(dir) { 44 | if (configPath != null) return configPath 45 | 46 | const filePath = findTarget(dir) 47 | if (filePath == null) { 48 | if (dir === parentDir(dir)) { 49 | throw new Error("No flow.json found") 50 | } 51 | return getConfigPath(parentDir(dir)) 52 | } 53 | 54 | configPath = filePath 55 | return configPath 56 | } 57 | 58 | export function flowConfig() { 59 | if (config != null) return config 60 | 61 | const filePath = getConfigPath(process.cwd()) 62 | const content = fs.readFileSync(filePath, "utf8") 63 | config = JSON.parse(content) 64 | 65 | return config 66 | } 67 | -------------------------------------------------------------------------------- /src/flow-token.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {defaultsByName} from "./file" 20 | import {replaceImportAddresses} from "./imports" 21 | import {executeScript, sendTransaction} from "./interaction" 22 | import {makeGetBalance, makeMintTransaction} from "./templates" 23 | 24 | /** 25 | * Returns current FlowToken balance of account specified by address 26 | * @param {string} address - address of account to check 27 | * @returns {Promise<*>} 28 | */ 29 | export const getFlowBalance = async address => { 30 | const raw = await makeGetBalance("FlowToken") 31 | const code = replaceImportAddresses(raw, defaultsByName) 32 | const args = [address] 33 | 34 | return executeScript({code, args}) 35 | } 36 | 37 | /** 38 | * Sends transaction to mint specified amount of FlowToken and send it to recipient. 39 | * Returns result of the transaction. 40 | * @param {string} recipient - address of recipient account 41 | * @param {string} amount - amount to mint and send 42 | * @returns {Promise<*>} 43 | */ 44 | export const mintFlow = async (recipient, amount) => { 45 | const raw = await makeMintTransaction("FlowToken") 46 | const code = replaceImportAddresses(raw, defaultsByName) 47 | const args = [recipient, amount] 48 | return sendTransaction({code, args}) 49 | } 50 | -------------------------------------------------------------------------------- /src/generated/contracts/FlowManager.js: -------------------------------------------------------------------------------- 1 | /** pragma type contract **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | deployContract, 8 | } from '@onflow/flow-cadut' 9 | 10 | export const CODE = ` 11 | access(all) contract FlowManager { 12 | 13 | /// Account Manager 14 | access(all) event AccountAdded(address: Address) 15 | 16 | access(all) struct Mapper { 17 | access(all) let accounts: {String: Address} 18 | 19 | access(all) view fun getAddress(_ name: String): Address? { 20 | return self.accounts[name] 21 | } 22 | 23 | access(all) fun setAddress(_ name: String, address: Address){ 24 | self.accounts[name] = address 25 | emit FlowManager.AccountAdded(address: address) 26 | } 27 | 28 | init(){ 29 | self.accounts = {} 30 | } 31 | } 32 | 33 | access(all) view fun getAccountAddress(_ name: String): Address?{ 34 | let accountManager = self.account 35 | .capabilities.borrow<&FlowManager.Mapper>(self.accountManagerPath)! 36 | 37 | return accountManager.getAddress(name) 38 | } 39 | 40 | access(all) let defaultAccounts: {Address : String} 41 | 42 | access(all) fun resolveDefaultAccounts(_ address: Address): Address{ 43 | let alias = self.defaultAccounts[address]! 44 | return self.getAccountAddress(alias)! 45 | } 46 | 47 | access(all) let accountManagerStorage: StoragePath 48 | access(all) let contractManagerStorage: StoragePath 49 | access(all) let accountManagerPath: PublicPath 50 | access(all) let contractManagerPath: PublicPath 51 | 52 | /// Environment Manager 53 | access(all) event BlockOffsetChanged(offset: UInt64) 54 | access(all) event TimestampOffsetChanged(offset: UFix64) 55 | 56 | access(all) struct MockBlock { 57 | access(all) let id: [UInt8; 32] 58 | access(all) let height: UInt64 59 | access(all) let view: UInt64 60 | access(all) let timestamp: UFix64 61 | 62 | init(_ id: [UInt8; 32], _ height: UInt64, _ view: UInt64, _ timestamp: UFix64){ 63 | self.id = id 64 | self.height = height 65 | self.view = view 66 | self.timestamp = timestamp 67 | } 68 | } 69 | 70 | access(all) fun setBlockOffset(_ offset: UInt64){ 71 | self.blockOffset = offset 72 | emit FlowManager.BlockOffsetChanged(offset: offset) 73 | } 74 | 75 | access(all) fun setTimestampOffset(_ offset: UFix64){ 76 | self.timestampOffset = offset 77 | emit FlowManager.TimestampOffsetChanged(offset: offset) 78 | } 79 | 80 | access(all) view fun getBlockHeight(): UInt64 { 81 | var block = getCurrentBlock() 82 | return block.height + self.blockOffset 83 | } 84 | 85 | access(all) view fun getBlockTimestamp(): UFix64 { 86 | var block = getCurrentBlock() 87 | return block.timestamp + self.timestampOffset 88 | } 89 | 90 | access(all) fun getBlock(): MockBlock { 91 | var block = getCurrentBlock() 92 | let mockBlock = MockBlock(block.id, block.height, block.view, block.timestamp); 93 | return mockBlock 94 | } 95 | 96 | access(all) var blockOffset: UInt64; 97 | access(all) var timestampOffset: UFix64; 98 | 99 | 100 | // Initialize contract 101 | init(){ 102 | // Environment defaults 103 | self.blockOffset = 0; 104 | self.timestampOffset = 0.0; 105 | 106 | // Account Manager initialization 107 | let accountManager = Mapper() 108 | let contractManager = Mapper() 109 | 110 | self.defaultAccounts = { 111 | 0x01: "Alice", 112 | 0x02: "Bob", 113 | 0x03: "Charlie", 114 | 0x04: "Dave", 115 | 0x05: "Eve" 116 | } 117 | 118 | self.accountManagerStorage = /storage/testSuiteAccountManager 119 | self.contractManagerStorage = /storage/testSuiteContractManager 120 | 121 | self.accountManagerPath = /public/testSuiteAccountManager 122 | self.contractManagerPath = /public/testSuiteContractManager 123 | 124 | // Destroy previously stored values 125 | self.account.storage.load(from: self.accountManagerStorage) 126 | self.account.storage.load(from: self.contractManagerStorage) 127 | 128 | self.account.storage.save(accountManager, to: self.accountManagerStorage) 129 | self.account.storage.save(contractManager, to: self.contractManagerStorage) 130 | 131 | 132 | self.account.capabilities.publish( 133 | self.account.capabilities.storage.issue<&Mapper>( 134 | self.accountManagerStorage 135 | ), at: self.accountManagerPath) 136 | 137 | self.account.capabilities.publish( 138 | self.account.capabilities.storage.issue<&Mapper>( 139 | self.contractManagerStorage 140 | ), at: self.contractManagerPath) 141 | } 142 | } 143 | 144 | `; 145 | 146 | /** 147 | * Method to generate cadence code for FlowManager contract 148 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 149 | */ 150 | export const FlowManagerTemplate = async (addressMap = {}) => { 151 | const envMap = await getEnvironment(); 152 | const fullMap = { 153 | ...envMap, 154 | ...addressMap, 155 | }; 156 | 157 | // If there are any missing imports in fullMap it will be reported via console 158 | reportMissingImports(CODE, fullMap, `FlowManager =>`) 159 | 160 | return replaceImportAddresses(CODE, fullMap); 161 | }; 162 | 163 | /** 164 | * Deploys FlowManager transaction to the network 165 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 166 | * @param Array<*> args - list of arguments 167 | * param Array - list of signers 168 | */ 169 | export const deployFlowManager = async (props = {}) => { 170 | const { addressMap = {} } = props; 171 | const code = await FlowManagerTemplate(addressMap); 172 | const name = "FlowManager" 173 | 174 | return deployContract({ code, name, processed: true, ...props }) 175 | } 176 | -------------------------------------------------------------------------------- /src/generated/contracts/index.js: -------------------------------------------------------------------------------- 1 | import { FlowManagerTemplate, deployFlowManager } from "./FlowManager"; 2 | export { FlowManagerTemplate, deployFlowManager }; 3 | export default { FlowManagerTemplate, deployFlowManager }; 4 | -------------------------------------------------------------------------------- /src/generated/index.js: -------------------------------------------------------------------------------- 1 | import contracts from "./contracts"; 2 | import scripts from "./scripts"; 3 | import transactions from "./transactions"; 4 | export { contracts, scripts, transactions }; 5 | export default { contracts, scripts, transactions }; 6 | -------------------------------------------------------------------------------- /src/generated/scripts/checkManager.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | access(all) fun main(){ 15 | // the body can be empty, cause script will throw error if FlowManager is not 16 | // added to service address 17 | } 18 | 19 | `; 20 | 21 | /** 22 | * Method to generate cadence code for checkManager script 23 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 24 | */ 25 | export const checkManagerTemplate = async (addressMap = {}) => { 26 | const envMap = await getEnvironment(); 27 | const fullMap = { 28 | ...envMap, 29 | ...addressMap, 30 | }; 31 | 32 | // If there are any missing imports in fullMap it will be reported via console 33 | reportMissingImports(CODE, fullMap, `checkManager =>`) 34 | 35 | return replaceImportAddresses(CODE, fullMap); 36 | }; 37 | 38 | export const checkManager = async (props = {}) => { 39 | const { addressMap = {}, args = [] } = props 40 | const code = await checkManagerTemplate(addressMap); 41 | 42 | reportMissing("arguments", args.length, 0, `checkManager =>`); 43 | 44 | return executeScript({code, processed: true, ...props}) 45 | } 46 | -------------------------------------------------------------------------------- /src/generated/scripts/getAccountAddress.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | access(all) fun main(name: String, managerAccount: Address):Address? { 15 | let manager = getAccount(managerAccount) 16 | let linkPath = FlowManager.accountManagerPath 17 | let accountManager = manager.capabilities.borrow<&FlowManager.Mapper>(linkPath)! 18 | 19 | return accountManager.getAddress(name) 20 | 21 | } 22 | `; 23 | 24 | /** 25 | * Method to generate cadence code for getAccountAddress script 26 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 27 | */ 28 | export const getAccountAddressTemplate = async (addressMap = {}) => { 29 | const envMap = await getEnvironment(); 30 | const fullMap = { 31 | ...envMap, 32 | ...addressMap, 33 | }; 34 | 35 | // If there are any missing imports in fullMap it will be reported via console 36 | reportMissingImports(CODE, fullMap, `getAccountAddress =>`) 37 | 38 | return replaceImportAddresses(CODE, fullMap); 39 | }; 40 | 41 | export const getAccountAddress = async (props = {}) => { 42 | const { addressMap = {}, args = [] } = props 43 | const code = await getAccountAddressTemplate(addressMap); 44 | 45 | reportMissing("arguments", args.length, 2, `getAccountAddress =>`); 46 | 47 | return executeScript({code, processed: true, ...props}) 48 | } 49 | -------------------------------------------------------------------------------- /src/generated/scripts/getBalance.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | // This script reads the balance field 13 | // of an account's ExampleToken Balance 14 | 15 | import FungibleToken from 0x1 16 | import ExampleToken from 0x1 17 | import FungibleTokenMetadataViews from 0x1 18 | 19 | access(all) fun main(address: Address): UFix64 { 20 | let vaultData = ExampleToken.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? 21 | ?? panic("Could not get vault data view for the contract") 22 | 23 | return getAccount(address).capabilities.borrow<&{FungibleToken.Balance}>( 24 | vaultData.metadataPath 25 | )?.balance 26 | ?? panic("Could not borrow Balance reference to the Vault") 27 | } 28 | `; 29 | 30 | /** 31 | * Method to generate cadence code for getBalance script 32 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 33 | */ 34 | export const getBalanceTemplate = async (addressMap = {}) => { 35 | const envMap = await getEnvironment(); 36 | const fullMap = { 37 | ...envMap, 38 | ...addressMap, 39 | }; 40 | 41 | // If there are any missing imports in fullMap it will be reported via console 42 | reportMissingImports(CODE, fullMap, `getBalance =>`) 43 | 44 | return replaceImportAddresses(CODE, fullMap); 45 | }; 46 | 47 | export const getBalance = async (props = {}) => { 48 | const { addressMap = {}, args = [] } = props 49 | const code = await getBalanceTemplate(addressMap); 50 | 51 | reportMissing("arguments", args.length, 1, `getBalance =>`); 52 | 53 | return executeScript({code, processed: true, ...props}) 54 | } 55 | -------------------------------------------------------------------------------- /src/generated/scripts/getBlockOffset.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | access(all) fun main():UInt64 { 15 | return FlowManager.blockOffset 16 | } 17 | 18 | `; 19 | 20 | /** 21 | * Method to generate cadence code for getBlockOffset script 22 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 23 | */ 24 | export const getBlockOffsetTemplate = async (addressMap = {}) => { 25 | const envMap = await getEnvironment(); 26 | const fullMap = { 27 | ...envMap, 28 | ...addressMap, 29 | }; 30 | 31 | // If there are any missing imports in fullMap it will be reported via console 32 | reportMissingImports(CODE, fullMap, `getBlockOffset =>`) 33 | 34 | return replaceImportAddresses(CODE, fullMap); 35 | }; 36 | 37 | export const getBlockOffset = async (props = {}) => { 38 | const { addressMap = {}, args = [] } = props 39 | const code = await getBlockOffsetTemplate(addressMap); 40 | 41 | reportMissing("arguments", args.length, 0, `getBlockOffset =>`); 42 | 43 | return executeScript({code, processed: true, ...props}) 44 | } 45 | -------------------------------------------------------------------------------- /src/generated/scripts/getContractAddress.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | access(all) fun main(name: String, managerAccount: Address):Address? { 15 | let manager = getAccount(managerAccount) 16 | let linkPath = FlowManager.contractManagerPath 17 | let contractManager = manager.capabilities.borrow<&FlowManager.Mapper>(linkPath)! 18 | 19 | return contractManager.getAddress(name) 20 | 21 | } 22 | `; 23 | 24 | /** 25 | * Method to generate cadence code for getContractAddress script 26 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 27 | */ 28 | export const getContractAddressTemplate = async (addressMap = {}) => { 29 | const envMap = await getEnvironment(); 30 | const fullMap = { 31 | ...envMap, 32 | ...addressMap, 33 | }; 34 | 35 | // If there are any missing imports in fullMap it will be reported via console 36 | reportMissingImports(CODE, fullMap, `getContractAddress =>`) 37 | 38 | return replaceImportAddresses(CODE, fullMap); 39 | }; 40 | 41 | export const getContractAddress = async (props = {}) => { 42 | const { addressMap = {}, args = [] } = props 43 | const code = await getContractAddressTemplate(addressMap); 44 | 45 | reportMissing("arguments", args.length, 2, `getContractAddress =>`); 46 | 47 | return executeScript({code, processed: true, ...props}) 48 | } 49 | -------------------------------------------------------------------------------- /src/generated/scripts/getManagerAddress.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | access(all) fun main(serviceAddress: Address): Address? { 13 | let account = getAccount(serviceAddress) 14 | 15 | let ref = account.capabilities.borrow<&[Address]>(/public/flowManagerAddress)! 16 | 17 | return ref[0] 18 | } 19 | 20 | `; 21 | 22 | /** 23 | * Method to generate cadence code for getManagerAddress script 24 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 25 | */ 26 | export const getManagerAddressTemplate = async (addressMap = {}) => { 27 | const envMap = await getEnvironment(); 28 | const fullMap = { 29 | ...envMap, 30 | ...addressMap, 31 | }; 32 | 33 | // If there are any missing imports in fullMap it will be reported via console 34 | reportMissingImports(CODE, fullMap, `getManagerAddress =>`) 35 | 36 | return replaceImportAddresses(CODE, fullMap); 37 | }; 38 | 39 | export const getManagerAddress = async (props = {}) => { 40 | const { addressMap = {}, args = [] } = props 41 | const code = await getManagerAddressTemplate(addressMap); 42 | 43 | reportMissing("arguments", args.length, 1, `getManagerAddress =>`); 44 | 45 | return executeScript({code, processed: true, ...props}) 46 | } 47 | -------------------------------------------------------------------------------- /src/generated/scripts/getTimestampOffset.js: -------------------------------------------------------------------------------- 1 | /** pragma type script **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | } from '@onflow/flow-cadut' 9 | import { executeScript } from '../../interaction' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | access(all) fun main():UFix64 { 15 | return FlowManager.timestampOffset 16 | } 17 | 18 | `; 19 | 20 | /** 21 | * Method to generate cadence code for getTimestampOffset script 22 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 23 | */ 24 | export const getTimestampOffsetTemplate = async (addressMap = {}) => { 25 | const envMap = await getEnvironment(); 26 | const fullMap = { 27 | ...envMap, 28 | ...addressMap, 29 | }; 30 | 31 | // If there are any missing imports in fullMap it will be reported via console 32 | reportMissingImports(CODE, fullMap, `getTimestampOffset =>`) 33 | 34 | return replaceImportAddresses(CODE, fullMap); 35 | }; 36 | 37 | export const getTimestampOffset = async (props = {}) => { 38 | const { addressMap = {}, args = [] } = props 39 | const code = await getTimestampOffsetTemplate(addressMap); 40 | 41 | reportMissing("arguments", args.length, 0, `getTimestampOffset =>`); 42 | 43 | return executeScript({code, processed: true, ...props}) 44 | } 45 | -------------------------------------------------------------------------------- /src/generated/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { checkManagerTemplate, checkManager } from "./checkManager"; 2 | import { getAccountAddressTemplate, getAccountAddress } from "./getAccountAddress"; 3 | import { getBalanceTemplate, getBalance } from "./getBalance"; 4 | import { getBlockOffsetTemplate, getBlockOffset } from "./getBlockOffset"; 5 | import { getContractAddressTemplate, getContractAddress } from "./getContractAddress"; 6 | import { getManagerAddressTemplate, getManagerAddress } from "./getManagerAddress"; 7 | import { getTimestampOffsetTemplate, getTimestampOffset } from "./getTimestampOffset"; 8 | export { 9 | checkManagerTemplate, 10 | checkManager, 11 | getAccountAddressTemplate, 12 | getAccountAddress, 13 | getBalanceTemplate, 14 | getBalance, 15 | getBlockOffsetTemplate, 16 | getBlockOffset, 17 | getContractAddressTemplate, 18 | getContractAddress, 19 | getManagerAddressTemplate, 20 | getManagerAddress, 21 | getTimestampOffsetTemplate, 22 | getTimestampOffset, 23 | }; 24 | export default { 25 | checkManagerTemplate, 26 | checkManager, 27 | getAccountAddressTemplate, 28 | getAccountAddress, 29 | getBalanceTemplate, 30 | getBalance, 31 | getBlockOffsetTemplate, 32 | getBlockOffset, 33 | getContractAddressTemplate, 34 | getContractAddress, 35 | getManagerAddressTemplate, 36 | getManagerAddress, 37 | getTimestampOffsetTemplate, 38 | getTimestampOffset, 39 | }; 40 | -------------------------------------------------------------------------------- /src/generated/transactions/createAccount.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | transaction (_ name: String?, pubKey: [String], manager: Address) { 15 | prepare( admin: auth(BorrowValue) &Account) { 16 | let newAccount = Account(payer:admin) 17 | for key in pubKey { 18 | let keyData = RLP.decodeList(key.decodeHex()) 19 | let rawSign = RLP.decodeString(keyData[1])[0] 20 | let rawHash = RLP.decodeString(keyData[2])[0] 21 | newAccount.keys.add( 22 | publicKey: PublicKey( 23 | publicKey: RLP.decodeString(keyData[0]), 24 | signatureAlgorithm: SignatureAlgorithm(rawValue: rawSign)! 25 | ), 26 | hashAlgorithm: HashAlgorithm(rawValue: rawHash)!, 27 | weight: UFix64(Int32.fromBigEndianBytes(RLP.decodeString(keyData[3]))!)! 28 | ) 29 | } 30 | 31 | if name != nil { 32 | let linkPath = FlowManager.accountManagerPath 33 | let accountManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 34 | 35 | // Create a record in account database 36 | let address = newAccount.address 37 | accountManager.setAddress(name!, address: address) 38 | } 39 | } 40 | } 41 | 42 | `; 43 | 44 | /** 45 | * Method to generate cadence code for createAccount transaction 46 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 47 | */ 48 | export const createAccountTemplate = async (addressMap = {}) => { 49 | const envMap = await getEnvironment(); 50 | const fullMap = { 51 | ...envMap, 52 | ...addressMap, 53 | }; 54 | 55 | // If there are any missing imports in fullMap it will be reported via console 56 | reportMissingImports(CODE, fullMap, `createAccount =>`) 57 | 58 | return replaceImportAddresses(CODE, fullMap); 59 | }; 60 | 61 | 62 | /** 63 | * Sends createAccount transaction to the network 64 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 65 | * @param Array<*> props.args - list of arguments 66 | * @param Array<*> props.signers - list of signers 67 | */ 68 | export const createAccount = async (props = {}) => { 69 | const { addressMap, args = [], signers = [] } = props; 70 | const code = await createAccountTemplate(addressMap); 71 | 72 | reportMissing("arguments", args.length, 3, `createAccount =>`); 73 | reportMissing("signers", signers.length, 1, `createAccount =>`); 74 | 75 | return sendTransaction({code, processed: true, ...props}) 76 | } 77 | -------------------------------------------------------------------------------- /src/generated/transactions/deployContract.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | transaction(name:String, code: String, manager: Address ##ARGS-WITH-TYPES##) { 15 | prepare(acct: auth(AddContract) &Account) { 16 | let decoded = code.decodeHex() 17 | acct.contracts.add( 18 | name: name, 19 | code: decoded, 20 | ##ARGS-LIST## 21 | ) 22 | 23 | let linkPath = FlowManager.contractManagerPath 24 | let contractManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 25 | 26 | let address = acct.address 27 | contractManager.setAddress(name, address: address) 28 | } 29 | } 30 | 31 | `; 32 | 33 | /** 34 | * Method to generate cadence code for deployContract transaction 35 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 36 | */ 37 | export const deployContractTemplate = async (addressMap = {}) => { 38 | const envMap = await getEnvironment(); 39 | const fullMap = { 40 | ...envMap, 41 | ...addressMap, 42 | }; 43 | 44 | // If there are any missing imports in fullMap it will be reported via console 45 | reportMissingImports(CODE, fullMap, `deployContract =>`) 46 | 47 | return replaceImportAddresses(CODE, fullMap); 48 | }; 49 | 50 | 51 | /** 52 | * Sends deployContract transaction to the network 53 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 54 | * @param Array<*> props.args - list of arguments 55 | * @param Array<*> props.signers - list of signers 56 | */ 57 | export const deployContract = async (props = {}) => { 58 | const { addressMap, args = [], signers = [] } = props; 59 | const code = await deployContractTemplate(addressMap); 60 | 61 | reportMissing("arguments", args.length, 3, `deployContract =>`); 62 | reportMissing("signers", signers.length, 1, `deployContract =>`); 63 | 64 | return sendTransaction({code, processed: true, ...props}) 65 | } 66 | -------------------------------------------------------------------------------- /src/generated/transactions/index.js: -------------------------------------------------------------------------------- 1 | import { createAccountTemplate, createAccount } from "./createAccount"; 2 | import { deployContractTemplate, deployContract } from "./deployContract"; 3 | import { initManagerTemplate, initManager } from "./initManager"; 4 | import { mintTokensTemplate, mintTokens } from "./mintTokens"; 5 | import { registerContractTemplate, registerContract } from "./registerContract"; 6 | import { scratchTemplate, scratch } from "./scratch"; 7 | import { setBlockOffsetTemplate, setBlockOffset } from "./setBlockOffset"; 8 | import { setTimestampOffsetTemplate, setTimestampOffset } from "./setTimestampOffset"; 9 | import { updateContractTemplate, updateContract } from "./updateContract"; 10 | export { 11 | createAccountTemplate, 12 | createAccount, 13 | deployContractTemplate, 14 | deployContract, 15 | initManagerTemplate, 16 | initManager, 17 | mintTokensTemplate, 18 | mintTokens, 19 | registerContractTemplate, 20 | registerContract, 21 | scratchTemplate, 22 | scratch, 23 | setBlockOffsetTemplate, 24 | setBlockOffset, 25 | setTimestampOffsetTemplate, 26 | setTimestampOffset, 27 | updateContractTemplate, 28 | updateContract, 29 | }; 30 | export default { 31 | createAccountTemplate, 32 | createAccount, 33 | deployContractTemplate, 34 | deployContract, 35 | initManagerTemplate, 36 | initManager, 37 | mintTokensTemplate, 38 | mintTokens, 39 | registerContractTemplate, 40 | registerContract, 41 | scratchTemplate, 42 | scratch, 43 | setBlockOffsetTemplate, 44 | setBlockOffset, 45 | setTimestampOffsetTemplate, 46 | setTimestampOffset, 47 | updateContractTemplate, 48 | updateContract, 49 | }; 50 | -------------------------------------------------------------------------------- /src/generated/transactions/initManager.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | transaction ( code: String ) { 13 | prepare( admin: &Account) { 14 | admin.contracts.add( 15 | name: "FlowManager", 16 | code: code.decodeHex(), 17 | ) 18 | } 19 | } 20 | 21 | `; 22 | 23 | /** 24 | * Method to generate cadence code for initManager transaction 25 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 26 | */ 27 | export const initManagerTemplate = async (addressMap = {}) => { 28 | const envMap = await getEnvironment(); 29 | const fullMap = { 30 | ...envMap, 31 | ...addressMap, 32 | }; 33 | 34 | // If there are any missing imports in fullMap it will be reported via console 35 | reportMissingImports(CODE, fullMap, `initManager =>`) 36 | 37 | return replaceImportAddresses(CODE, fullMap); 38 | }; 39 | 40 | 41 | /** 42 | * Sends initManager transaction to the network 43 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 44 | * @param Array<*> props.args - list of arguments 45 | * @param Array<*> props.signers - list of signers 46 | */ 47 | export const initManager = async (props = {}) => { 48 | const { addressMap, args = [], signers = [] } = props; 49 | const code = await initManagerTemplate(addressMap); 50 | 51 | reportMissing("arguments", args.length, 1, `initManager =>`); 52 | reportMissing("signers", signers.length, 1, `initManager =>`); 53 | 54 | return sendTransaction({code, processed: true, ...props}) 55 | } 56 | -------------------------------------------------------------------------------- /src/generated/transactions/mintTokens.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FungibleToken from 0x1 13 | import ExampleToken from 0x1 14 | import FungibleTokenMetadataViews from 0x1 15 | 16 | transaction(recipient: Address, amount: UFix64) { 17 | let tokenAdmin: &FlowToken.Administrator 18 | let tokenReceiver: &{FungibleToken.Receiver} 19 | let supplyBefore: UFix64 20 | 21 | prepare(signer: auth(BorrowValue) &Account) { 22 | self.supplyBefore = ExampleToken.totalSupply 23 | 24 | // Borrow a reference to the admin object 25 | self.tokenAdmin = signer.storage.borrow<&ExampleToken.Administrator>(from: ExampleToken.AdminStoragePath) 26 | ?? panic("Signer is not the token admin") 27 | 28 | let vaultData = ExampleToken.resolveContractView(resourceType: nil, viewType: Type()) as! FungibleTokenMetadataViews.FTVaultData? 29 | ?? panic("Could not get vault data view for the contract") 30 | 31 | self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(vaultData.receiverPath) 32 | ?? panic("Could not borrow receiver reference to the Vault") 33 | } 34 | 35 | execute { 36 | let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) 37 | let mintedVault <- minter.mintTokens(amount: amount) 38 | 39 | self.tokenReceiver.deposit(from: <-mintedVault) 40 | 41 | destroy minter 42 | } 43 | 44 | post { 45 | ExampleToken.totalSupply == self.supplyBefore + amount: "The total supply must be increased by the amount" 46 | } 47 | } 48 | 49 | `; 50 | 51 | /** 52 | * Method to generate cadence code for mintTokens transaction 53 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 54 | */ 55 | export const mintTokensTemplate = async (addressMap = {}) => { 56 | const envMap = await getEnvironment(); 57 | const fullMap = { 58 | ...envMap, 59 | ...addressMap, 60 | }; 61 | 62 | // If there are any missing imports in fullMap it will be reported via console 63 | reportMissingImports(CODE, fullMap, `mintTokens =>`) 64 | 65 | return replaceImportAddresses(CODE, fullMap); 66 | }; 67 | 68 | 69 | /** 70 | * Sends mintTokens transaction to the network 71 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 72 | * @param Array<*> props.args - list of arguments 73 | * @param Array<*> props.signers - list of signers 74 | */ 75 | export const mintTokens = async (props = {}) => { 76 | const { addressMap, args = [], signers = [] } = props; 77 | const code = await mintTokensTemplate(addressMap); 78 | 79 | reportMissing("arguments", args.length, 2, `mintTokens =>`); 80 | reportMissing("signers", signers.length, 1, `mintTokens =>`); 81 | 82 | return sendTransaction({code, processed: true, ...props}) 83 | } 84 | -------------------------------------------------------------------------------- /src/generated/transactions/registerContract.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | transaction(name: String, address: Address) { 15 | prepare(signer: auth(BorrowValue) &Account){ 16 | let linkPath = FlowManager.contractManagerPath 17 | let contractManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 18 | contractManager.setAddress(name, address: address) 19 | } 20 | } 21 | 22 | `; 23 | 24 | /** 25 | * Method to generate cadence code for registerContract transaction 26 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 27 | */ 28 | export const registerContractTemplate = async (addressMap = {}) => { 29 | const envMap = await getEnvironment(); 30 | const fullMap = { 31 | ...envMap, 32 | ...addressMap, 33 | }; 34 | 35 | // If there are any missing imports in fullMap it will be reported via console 36 | reportMissingImports(CODE, fullMap, `registerContract =>`) 37 | 38 | return replaceImportAddresses(CODE, fullMap); 39 | }; 40 | 41 | 42 | /** 43 | * Sends registerContract transaction to the network 44 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 45 | * @param Array<*> props.args - list of arguments 46 | * @param Array<*> props.signers - list of signers 47 | */ 48 | export const registerContract = async (props = {}) => { 49 | const { addressMap, args = [], signers = [] } = props; 50 | const code = await registerContractTemplate(addressMap); 51 | 52 | reportMissing("arguments", args.length, 2, `registerContract =>`); 53 | reportMissing("signers", signers.length, 1, `registerContract =>`); 54 | 55 | return sendTransaction({code, processed: true, ...props}) 56 | } 57 | -------------------------------------------------------------------------------- /src/generated/transactions/scratch.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | transaction{ 13 | prepare(acct: &Account){ 14 | log(acct.address) 15 | } 16 | } 17 | `; 18 | 19 | /** 20 | * Method to generate cadence code for scratch transaction 21 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 22 | */ 23 | export const scratchTemplate = async (addressMap = {}) => { 24 | const envMap = await getEnvironment(); 25 | const fullMap = { 26 | ...envMap, 27 | ...addressMap, 28 | }; 29 | 30 | // If there are any missing imports in fullMap it will be reported via console 31 | reportMissingImports(CODE, fullMap, `scratch =>`) 32 | 33 | return replaceImportAddresses(CODE, fullMap); 34 | }; 35 | 36 | 37 | /** 38 | * Sends scratch transaction to the network 39 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 40 | * @param Array<*> props.args - list of arguments 41 | * @param Array<*> props.signers - list of signers 42 | */ 43 | export const scratch = async (props = {}) => { 44 | const { addressMap, args = [], signers = [] } = props; 45 | const code = await scratchTemplate(addressMap); 46 | 47 | reportMissing("arguments", args.length, 0, `scratch =>`); 48 | reportMissing("signers", signers.length, 1, `scratch =>`); 49 | 50 | return sendTransaction({code, processed: true, ...props}) 51 | } 52 | -------------------------------------------------------------------------------- /src/generated/transactions/setBlockOffset.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | transaction(offset: UInt64){ 15 | prepare(signer: &Account){ 16 | FlowManager.setBlockOffset(offset) 17 | } 18 | } 19 | 20 | `; 21 | 22 | /** 23 | * Method to generate cadence code for setBlockOffset transaction 24 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 25 | */ 26 | export const setBlockOffsetTemplate = async (addressMap = {}) => { 27 | const envMap = await getEnvironment(); 28 | const fullMap = { 29 | ...envMap, 30 | ...addressMap, 31 | }; 32 | 33 | // If there are any missing imports in fullMap it will be reported via console 34 | reportMissingImports(CODE, fullMap, `setBlockOffset =>`) 35 | 36 | return replaceImportAddresses(CODE, fullMap); 37 | }; 38 | 39 | 40 | /** 41 | * Sends setBlockOffset transaction to the network 42 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 43 | * @param Array<*> props.args - list of arguments 44 | * @param Array<*> props.signers - list of signers 45 | */ 46 | export const setBlockOffset = async (props = {}) => { 47 | const { addressMap, args = [], signers = [] } = props; 48 | const code = await setBlockOffsetTemplate(addressMap); 49 | 50 | reportMissing("arguments", args.length, 1, `setBlockOffset =>`); 51 | reportMissing("signers", signers.length, 1, `setBlockOffset =>`); 52 | 53 | return sendTransaction({code, processed: true, ...props}) 54 | } 55 | -------------------------------------------------------------------------------- /src/generated/transactions/setTimestampOffset.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | transaction(offset: UFix64){ 15 | prepare(signer: &Account){ 16 | FlowManager.setTimestampOffset(offset) 17 | } 18 | } 19 | 20 | `; 21 | 22 | /** 23 | * Method to generate cadence code for setTimestampOffset transaction 24 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 25 | */ 26 | export const setTimestampOffsetTemplate = async (addressMap = {}) => { 27 | const envMap = await getEnvironment(); 28 | const fullMap = { 29 | ...envMap, 30 | ...addressMap, 31 | }; 32 | 33 | // If there are any missing imports in fullMap it will be reported via console 34 | reportMissingImports(CODE, fullMap, `setTimestampOffset =>`) 35 | 36 | return replaceImportAddresses(CODE, fullMap); 37 | }; 38 | 39 | 40 | /** 41 | * Sends setTimestampOffset transaction to the network 42 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 43 | * @param Array<*> props.args - list of arguments 44 | * @param Array<*> props.signers - list of signers 45 | */ 46 | export const setTimestampOffset = async (props = {}) => { 47 | const { addressMap, args = [], signers = [] } = props; 48 | const code = await setTimestampOffsetTemplate(addressMap); 49 | 50 | reportMissing("arguments", args.length, 1, `setTimestampOffset =>`); 51 | reportMissing("signers", signers.length, 1, `setTimestampOffset =>`); 52 | 53 | return sendTransaction({code, processed: true, ...props}) 54 | } 55 | -------------------------------------------------------------------------------- /src/generated/transactions/updateContract.js: -------------------------------------------------------------------------------- 1 | /** pragma type transaction **/ 2 | 3 | import { 4 | getEnvironment, 5 | replaceImportAddresses, 6 | reportMissingImports, 7 | reportMissing, 8 | sendTransaction 9 | } from '@onflow/flow-cadut' 10 | 11 | export const CODE = ` 12 | import FlowManager from 0x01 13 | 14 | transaction(name:String, code: String, manager: Address ##ARGS-WITH-TYPES##) { 15 | prepare(acct: auth(AddContract, UpdateContract) &Account){ 16 | let decoded = code.decodeHex() 17 | 18 | if acct.contracts.get(name: name) == nil { 19 | acct.contracts.add( 20 | name: name, 21 | code: decoded, 22 | ##ARGS-LIST## 23 | ) 24 | } else { 25 | acct.contracts.update(name: name, code: decoded) 26 | } 27 | 28 | let linkPath = FlowManager.contractManagerPath 29 | let contractManager = getAccount(manager).capabilities.borrow<&FlowManager.Mapper>(linkPath)! 30 | 31 | let address = acct.address 32 | contractManager.setAddress(name, address: address) 33 | } 34 | } 35 | 36 | `; 37 | 38 | /** 39 | * Method to generate cadence code for updateContract transaction 40 | * @param {Object.} addressMap - contract name as a key and address where it's deployed as value 41 | */ 42 | export const updateContractTemplate = async (addressMap = {}) => { 43 | const envMap = await getEnvironment(); 44 | const fullMap = { 45 | ...envMap, 46 | ...addressMap, 47 | }; 48 | 49 | // If there are any missing imports in fullMap it will be reported via console 50 | reportMissingImports(CODE, fullMap, `updateContract =>`) 51 | 52 | return replaceImportAddresses(CODE, fullMap); 53 | }; 54 | 55 | 56 | /** 57 | * Sends updateContract transaction to the network 58 | * @param {Object.} props.addressMap - contract name as a key and address where it's deployed as value 59 | * @param Array<*> props.args - list of arguments 60 | * @param Array<*> props.signers - list of signers 61 | */ 62 | export const updateContract = async (props = {}) => { 63 | const { addressMap, args = [], signers = [] } = props; 64 | const code = await updateContractTemplate(addressMap); 65 | 66 | reportMissing("arguments", args.length, 3, `updateContract =>`); 67 | reportMissing("signers", signers.length, 1, `updateContract =>`); 68 | 69 | return sendTransaction({code, processed: true, ...props}) 70 | } 71 | -------------------------------------------------------------------------------- /src/imports.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {getContractAddress} from "./contract" 20 | import {defaultsByName} from "./file" 21 | import {replaceImportAddresses, extractImports} from "@onflow/flow-cadut" 22 | 23 | // TODO remove once flow-cadut is updated to support short-hand imports 24 | const importTokenPart = `(?:"[\\w\\d]+?")|(?:[\\w\\d]+)` 25 | const importRegex = new RegExp( 26 | `(^\\s*import\\s+)((?:${importTokenPart}[^\\n]*?,?[^\\n]?)+)[^\\n]*?(from)?[^\\n]*?$`, 27 | "gium" 28 | ) 29 | export const fixShorthandImports = code => { 30 | return code.replaceAll(importRegex, found => { 31 | if (found.indexOf(" from") !== -1) return found 32 | const whatMatch = found.matchAll(/"([^"\s]+)"\s*,?\s*?/giu) 33 | return [...whatMatch] 34 | .map(what => `import ${what[1]} from "./${what[1]}.cdc"`) 35 | .join("\n") 36 | }) 37 | } 38 | 39 | /** 40 | * Resolves import addresses defined in code template 41 | * @param {string} code - Cadence template code. 42 | * @returns {{string:string}} - name/address map 43 | */ 44 | export const resolveImports = async code => { 45 | const addressMap = {} 46 | const importList = extractImports(fixShorthandImports(code)) 47 | for (const key in importList) { 48 | if (defaultsByName[key]) { 49 | addressMap[key] = defaultsByName[key] 50 | } else { 51 | const address = await getContractAddress(key) 52 | addressMap[key] = address 53 | } 54 | } 55 | return addressMap 56 | } 57 | 58 | export {replaceImportAddresses, extractImports} 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export {init} from "./init" 20 | export {config} from "@onflow/fcl" 21 | export { 22 | getTemplate, 23 | getScriptCode, 24 | getContractCode, 25 | getTransactionCode, 26 | } from "./file" 27 | export {sendTransaction, executeScript} from "./interaction" 28 | export {getFlowBalance, mintFlow} from "./flow-token" 29 | export {deployContract, deployContractByName} from "./deploy-code" 30 | export {createAccount, getAccountAddress} from "./account" 31 | export { 32 | getBlockOffset, 33 | setBlockOffset, 34 | getTimestampOffset, 35 | setTimestampOffset, 36 | } from "./manager" 37 | export {getContractAddress} from "./contract" 38 | export {extractImports, replaceImportAddresses, resolveImports} from "./imports" 39 | export { 40 | promise, 41 | shallPass, 42 | shallResolve, 43 | shallRevert, 44 | shallThrow, 45 | } from "./jest-asserts" 46 | export {builtInMethods} from "./transformers" 47 | export { 48 | HashAlgorithm, 49 | SignatureAlgorithm, 50 | pubFlowKey, 51 | signUserMessage, 52 | verifyUserSignatures, 53 | } from "./crypto" 54 | export {isAddress, getServiceAddress} from "./utils" 55 | 56 | export {default as emulator} from "./emulator/emulator" 57 | -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {config} from "@onflow/fcl" 20 | import {flowConfig, getConfigPath} from "./flow-config" 21 | import path from "path" 22 | import fs from "fs" 23 | 24 | const DEFAULT_COMPUTE_LIMIT = 9999 25 | 26 | /** 27 | * Inits framework variables, storing private key of service account and base path 28 | * where Cadence files are stored. 29 | * @param {string} basePath - path to the folder with Cadence files to be tested. 30 | * @param {number} [props.port] - port to use for accessAPI 31 | * @param {number} [props.pkey] - private key to use for service account in case of collisions 32 | */ 33 | export const init = async (basePath, props = {}) => { 34 | const { 35 | pkey = "48a1f554aeebf6bf9fe0d7b5b79d080700b073ee77909973ea0b2f6fbc902", 36 | } = props 37 | 38 | const cfg = flowConfig() 39 | 40 | config().put("PRIVATE_KEY", getServiceKey(cfg) ?? pkey) 41 | config().put( 42 | "SERVICE_ADDRESS", 43 | cfg?.accounts?.["emulator-account"]?.address ?? "f8d6e0586b0a20c7" 44 | ) 45 | config().put("BASE_PATH", cfg?.testing?.paths ?? basePath) 46 | config().put("fcl.limit", DEFAULT_COMPUTE_LIMIT) 47 | } 48 | 49 | function getServiceKey(cfg) { 50 | const value = cfg?.accounts?.["emulator-account"]?.key 51 | if (value) { 52 | if (typeof value === "object") { 53 | switch (value.type) { 54 | case "hex": 55 | return value.privateKey 56 | case "file": { 57 | const configDir = path.dirname(getConfigPath()) 58 | const resovledPath = path.resolve(configDir, value.location) 59 | return fs.readFileSync(resovledPath, "utf8") 60 | } 61 | default: 62 | return null 63 | } 64 | } else if (typeof value === "string") { 65 | if (value.startsWith("$")) { 66 | return process.env[value.slice(1)] 67 | } else { 68 | return value 69 | } 70 | } else { 71 | return null 72 | } 73 | } 74 | 75 | return null 76 | } 77 | -------------------------------------------------------------------------------- /src/invariant.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | export const invariant = (fact, msg, ...rest) => { 20 | if (!fact) { 21 | const error = new Error(`INVARIANT ${msg}`) 22 | error.stack = error.stack 23 | .split("\n") 24 | .filter(d => !/at invariant/.test(d)) 25 | .join("\n") 26 | console.error("\n\n---\n\n", error, "\n\n", ...rest, "\n\n---\n\n") 27 | throw error 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/jest-asserts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {getPaths, getStorageValue} from "./storage" 20 | import {getValueByKey, parsePath} from "./utils" 21 | 22 | const {expect} = global 23 | 24 | /** 25 | * Return Promise from passed interaction 26 | * @param {function | Promise} ix - Promise or function to wrap 27 | * @returns Promise<*> 28 | * */ 29 | export const promise = async ix => { 30 | if (typeof ix === "function") { 31 | return await ix() 32 | } 33 | return await ix 34 | } 35 | 36 | /** 37 | * Ensure transaction did not throw and sealed. 38 | * @param {function | Promise} ix - Promise or function to wrap 39 | * @returns Promise<*> - transaction result 40 | * */ 41 | export const shallPass = async ix => { 42 | const wrappedInteraction = promise(ix) 43 | 44 | const response = await wrappedInteraction 45 | const [result, error] = response 46 | 47 | if (error) { 48 | throw error 49 | } 50 | 51 | let resolvedStatus 52 | let resolvedErrorMessage 53 | if (Array.isArray(result)) { 54 | const {status, errorMessage} = result 55 | resolvedStatus = status 56 | resolvedErrorMessage = errorMessage 57 | } else { 58 | const {status, errorMessage} = result 59 | resolvedStatus = status 60 | resolvedErrorMessage = errorMessage 61 | } 62 | 63 | await expect(resolvedStatus).toBe(4) 64 | await expect(resolvedErrorMessage).toBe("") 65 | 66 | return response 67 | } 68 | 69 | /** 70 | * Ensure interaction did not throw and return result of it 71 | * @param {function | Promise} ix - Promise or function to wrap 72 | * @returns Promise<*> - result of interaction 73 | * */ 74 | export const shallResolve = async ix => { 75 | const wrappedInteraction = promise(ix) 76 | const response = await wrappedInteraction 77 | const [, error] = response 78 | expect(error).toBe(null) 79 | 80 | return response 81 | } 82 | 83 | /** 84 | * Ensure interaction throws an error. 85 | * @param {function | Promise} ix - Promise or function to wrap 86 | * @param {string | RegExp} [message] - Expected error message provided as either a string equality or regular expression 87 | * @returns Promise<*> - result of interaction 88 | * */ 89 | export const shallRevert = async (ix, message) => { 90 | const wrappedInteraction = promise(ix) 91 | const response = await wrappedInteraction 92 | const [result, error] = response 93 | 94 | await expect(result).toBe(null) 95 | 96 | if (message) { 97 | const errorMessage = error 98 | .toString() 99 | .match(/^error: (panic)|(assertion failed): ([^\r\n]*)$/m) 100 | ?.at(3) 101 | if (message instanceof RegExp) { 102 | await expect(errorMessage).toMatch(message) 103 | } else { 104 | await expect(errorMessage).toBe(message) 105 | } 106 | } else { 107 | await expect(error).not.toBe(null) 108 | } 109 | 110 | return response 111 | } 112 | 113 | /** 114 | * Ensure interaction throws an error. 115 | * @param {function | Promise} ix - Promise or function to wrap 116 | * @returns Promise<*> - result of interaction 117 | * */ 118 | export const shallThrow = async ix => { 119 | const wrappedInteraction = promise(ix) 120 | const response = await wrappedInteraction 121 | 122 | const [result, error] = response 123 | await expect(result).toBe(null) 124 | await expect(error).not.toBe(null) 125 | 126 | return response 127 | } 128 | 129 | /** 130 | * Asserts that the given account has the given path enabled. 131 | * 132 | * @async 133 | * @param {string} account - The address or name of the account to check for the path. 134 | * @param {string} path - The path to check for. 135 | * @returns {Promise} A Promise that resolves when the assertion is complete, or rejects with an error if the assertion fails. 136 | */ 137 | export const shallHavePath = async (account, path) => { 138 | let parsedPath 139 | expect(() => { 140 | parsedPath = parsePath(path) 141 | }).not.toThrowError() 142 | 143 | const {domain, slot} = parsedPath 144 | const paths = await getPaths(account) 145 | const key = `${domain}Paths` 146 | const hasPathEnabled = paths[key].has(slot) 147 | 148 | await expect(hasPathEnabled).toBe(true) 149 | } 150 | 151 | /** 152 | * Asserts that the given account has the expected storage value at the given path. 153 | * 154 | * @async 155 | * @param {string} account - The address or name of the account to check for the storage value. 156 | * @param {{pathName: string, key?: string, expect: any}} params - An object containing the path name, optional key, and expected value. 157 | * @returns {Promise} A Promise that resolves when the assertion is complete, or rejects with an error if the assertion fails. 158 | */ 159 | export const shallHaveStorageValue = async (account, params) => { 160 | const {pathName, key} = params 161 | 162 | const storageValue = await getStorageValue(account, pathName) 163 | 164 | if (key) { 165 | const actualValue = getValueByKey(key, storageValue) 166 | expect(actualValue).toBe(params.expect) 167 | } else { 168 | expect(storageValue).toBe(params.expect) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/manager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {executeScript, sendTransaction} from "./interaction" 20 | import registry from "./generated" 21 | import {getServiceAddress} from "./utils" 22 | import {authorization} from "./crypto" 23 | 24 | export const initManager = async () => { 25 | await registry.contracts.deployFlowManager({ 26 | to: await authorization(), 27 | }) 28 | } 29 | 30 | export const getManagerAddress = async () => { 31 | const serviceAddress = await getServiceAddress() 32 | 33 | const addressMap = { 34 | FlowManager: serviceAddress, 35 | } 36 | 37 | const code = await registry.scripts.checkManagerTemplate(addressMap) 38 | 39 | let [result, e] = await executeScript({ 40 | code, 41 | service: true, 42 | }) 43 | if (e && result === null) { 44 | await initManager() 45 | } 46 | 47 | return getServiceAddress() 48 | } 49 | 50 | // TODO: replace method above after Cadence will allow to get contracts list on PublicAccount 51 | /* 52 | export const getManagerAddress = async () => { 53 | const serviceAddress = await getServiceAddress(); 54 | 55 | const code = ` 56 | access(all) fun main(address: Address):Bool { 57 | return getAccount(address).contracts.get("FlowManager") != null 58 | } 59 | `; 60 | const result = await executeScript({ code, args: [serviceAddress] }); 61 | 62 | if (!result) { 63 | await initManager(); 64 | } 65 | 66 | return serviceAddress; 67 | }; 68 | */ 69 | 70 | export const getBlockOffset = async () => { 71 | const FlowManager = await getManagerAddress() 72 | const code = await registry.scripts.getBlockOffsetTemplate({FlowManager}) 73 | return executeScript({code}) 74 | } 75 | 76 | export const setBlockOffset = async offset => { 77 | const FlowManager = await getManagerAddress() 78 | 79 | const args = [offset] 80 | const code = await registry.transactions.setBlockOffsetTemplate({FlowManager}) 81 | const payer = [FlowManager] 82 | 83 | return sendTransaction({code, args, payer}) 84 | } 85 | 86 | export const getTimestampOffset = async () => { 87 | const FlowManager = await getManagerAddress() 88 | const code = await registry.scripts.getTimestampOffsetTemplate({FlowManager}) 89 | return executeScript({code}) 90 | } 91 | 92 | export const setTimestampOffset = async offset => { 93 | const FlowManager = await getManagerAddress() 94 | 95 | const args = [offset] 96 | const code = await registry.transactions.setTimestampOffsetTemplate({ 97 | FlowManager, 98 | }) 99 | const payer = [FlowManager] 100 | 101 | return sendTransaction({code, args, payer}) 102 | } 103 | -------------------------------------------------------------------------------- /src/templates.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import registry from "./generated" 20 | import {defaultsByName} from "./file" 21 | 22 | const FlowTokenMap = { 23 | ExampleToken: defaultsByName.FlowToken, 24 | FungibleTokenMetadataViews: defaultsByName.FungibleTokenMetadataViews, 25 | } 26 | 27 | const lowerFirst = name => { 28 | return name[0].toLowerCase() + name.slice(1) 29 | } 30 | 31 | export const makeMintTransaction = async name => { 32 | let code = await registry.transactions.mintTokensTemplate(FlowTokenMap) 33 | const pattern = /(ExampleToken)/gi 34 | const aspPattern = /ExampleToken.AdminStoragePath/gi 35 | 36 | if (name === "FlowToken") { 37 | code = code.replace(aspPattern, "/storage/flowTokenAdmin") 38 | } 39 | 40 | return code.replace(pattern, match => { 41 | return match === "ExampleToken" ? name : lowerFirst(name) 42 | }) 43 | } 44 | 45 | export const makeGetBalance = async name => { 46 | const code = await registry.scripts.getBalanceTemplate(FlowTokenMap) 47 | const pattern = /(ExampleToken)/gi 48 | 49 | return code.replace(pattern, match => { 50 | return match === "ExampleToken" ? name : lowerFirst(name) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /src/transformers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {getManagerAddress} from "./manager" 20 | 21 | export const importManager = async () => { 22 | const managerAddress = await getManagerAddress() 23 | return `import FlowManager from ${managerAddress}` 24 | } 25 | 26 | export const importExists = (contractName, code) => { 27 | return new RegExp(`import\\s+${contractName}`).test(code) 28 | } 29 | 30 | export const builtInMethods = async code => { 31 | let replacedCode = code 32 | .replace(/getCurrentBlock\(\).height/g, `FlowManager.getBlockHeight()`) 33 | .replace( 34 | /getCurrentBlock\(\).timestamp/g, 35 | `FlowManager.getBlockTimestamp()` 36 | ) 37 | 38 | if (code === replacedCode) return code 39 | 40 | let injectedImports = replacedCode 41 | if (!importExists("FlowManager", replacedCode)) { 42 | const imports = await importManager() 43 | injectedImports = ` 44 | ${imports} 45 | ${replacedCode} 46 | ` 47 | } 48 | return injectedImports 49 | } 50 | 51 | export function builtInMethodsDeprecated(code) { 52 | console.warn(`The usage of the builtInMethods transformer for block & timestamp offset utilities is deprecated and unnecesary. 53 | It is applied by default to all cadence code within Flow JS Testing. 54 | Please remove this transformer from your code as this method will be removed in future versions of Flow JS Testing. 55 | See more here: https://github.com/onflow/flow-js-testing/blob/master/TRANSITIONS.md#0002-depreaction-of-builtinmethods-code-transformer`) 56 | return builtInMethods(code) 57 | } 58 | 59 | const addressToIndex = address => { 60 | return parseInt(address) - 1 61 | } 62 | 63 | const addressToAlias = accounts => address => accounts[addressToIndex(address)] 64 | 65 | export const playgroundImport = accounts => async code => { 66 | let injectedImports = code 67 | if (!importExists("FlowManager", code)) { 68 | const imports = await importManager() 69 | injectedImports = ` 70 | ${imports} 71 | ${code} 72 | ` 73 | } 74 | return injectedImports.replace(/(?:getAccount\()(.+)(?:\))/g, (match, g1) => { 75 | const alias = addressToAlias(accounts)(g1) 76 | if (!alias) { 77 | return `getAccount(FlowManager.resolveDefaultAccounts(${g1}))` 78 | } 79 | return `getAccount(FlowManager.getAccountAddress("${alias}"))` 80 | }) 81 | } 82 | 83 | export const applyTransformers = async (code, transformers) => 84 | transformers.reduce(async (acc, transformer) => transformer(await acc), code) 85 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Flow JS Testing 3 | * 4 | * Copyright 2020-2021 Dapper Labs, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import {config, withPrefix} from "@onflow/fcl" 20 | import {exec} from "child_process" 21 | import {createServer} from "net" 22 | import * as semver from "semver" 23 | 24 | export const isObject = arg => typeof arg === "object" && arg !== null 25 | export const isString = obj => typeof obj === "string" || obj instanceof String 26 | export const isAddress = address => /^0x[0-9a-f]{0,16}$/.test(address) 27 | 28 | export function getAvailablePorts(count = 1) { 29 | if (count === 0) return Promise.resolve([]) 30 | return new Promise((resolve, reject) => { 31 | const server = createServer() 32 | server.listen(0, () => { 33 | const port = server.address().port 34 | server.close(async err => { 35 | if (err) reject(err) 36 | resolve([...(await getAvailablePorts(count - 1)), port]) 37 | }) 38 | }) 39 | }) 40 | } 41 | 42 | /** 43 | * Get the Flow CLI version 44 | * @param {string} flowCommand - the Flow CLI command name 45 | * @returns {Promise} 46 | */ 47 | export async function getFlowVersion(flowCommand = "flow") { 48 | return new Promise((resolve, reject) => { 49 | exec(`${flowCommand} version --output=json`, (error, stdout) => { 50 | if (error) { 51 | reject( 52 | "Could not determine Flow CLI version, please make sure it is installed and available in your PATH" 53 | ) 54 | } else { 55 | let versionStr 56 | try { 57 | versionStr = JSON.parse(stdout).version 58 | } catch (error) { 59 | // fallback to regex for older versions of the CLI without JSON output 60 | const rxResult = /^Version: ([^\s]+)/m.exec(stdout) 61 | if (rxResult) { 62 | versionStr = rxResult[1] 63 | } 64 | } 65 | 66 | const version = versionStr ? semver.parse(versionStr) : undefined 67 | if (!version) { 68 | reject(`Invalid Flow CLI version string: ${versionStr}`) 69 | } 70 | 71 | resolve(version) 72 | } 73 | }) 74 | }) 75 | } 76 | 77 | export const getServiceAddress = async () => { 78 | return withPrefix(await config().get("SERVICE_ADDRESS")) 79 | } 80 | 81 | export function parsePath(path) { 82 | const rxResult = /(\w+)\/(\w+)/.exec(path) 83 | if (!rxResult) { 84 | throw Error(`${path} is not a correct path`) 85 | } 86 | 87 | return { 88 | domain: rxResult[1], 89 | slot: rxResult[2], 90 | } 91 | } 92 | export const getValueByKey = (keys, value) => { 93 | if (!value) { 94 | return null 95 | } 96 | 97 | if (keys.length > 0 && !isObject(value)) { 98 | return null 99 | } 100 | 101 | if (typeof keys === "string") { 102 | return getValueByKey(keys.split("."), value) 103 | } 104 | const [first, ...rest] = keys 105 | 106 | if (!first) { 107 | return value 108 | } 109 | 110 | if (!rest) { 111 | return value[first] 112 | } 113 | return getValueByKey(rest, value[first]) 114 | } 115 | -------------------------------------------------------------------------------- /test/basic/cadence.test.js: -------------------------------------------------------------------------------- 1 | import registry from "../../src/generated" 2 | const {FlowManagerTemplate} = registry.contracts 3 | 4 | describe("manager code", () => { 5 | test("FlowManager contract", async () => { 6 | const code = await FlowManagerTemplate() 7 | 8 | expect(code.includes("access(all) contract FlowManager")).toBe(true) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/basic/imports.test.js: -------------------------------------------------------------------------------- 1 | import {replaceImportAddresses} from "../../src" 2 | 3 | describe("replace", () => { 4 | test("deployed address", async () => { 5 | const Basic = "0x01" 6 | const code = `import Basic from 0xTEMPLATE_STRING` 7 | const result = replaceImportAddresses(code, { 8 | Basic, 9 | }) 10 | const reference = `import Basic from ${Basic}` 11 | expect(result).toEqual(reference) 12 | }) 13 | 14 | test("multiple deployed addresses", async () => { 15 | const Basic = "0x01" 16 | const Advanced = "0x02" 17 | 18 | const code = ` 19 | import Basic from 0xBASIC_TEMPLATE 20 | import Advanced from 0xADVANCED_TEMPLATE 21 | ` 22 | 23 | const result = replaceImportAddresses(code, { 24 | Basic, 25 | Advanced, 26 | }) 27 | 28 | const reference = ` 29 | import Basic from ${Basic} 30 | import Advanced from ${Advanced} 31 | ` 32 | 33 | expect(result).toEqual(reference) 34 | }) 35 | 36 | test("local imports", async () => { 37 | const Basic = "0x01" 38 | const code = `import Basic from "../cadence/script"` 39 | const result = replaceImportAddresses(code, { 40 | Basic, 41 | }) 42 | const reference = `import Basic from ${Basic}` 43 | expect(result).toEqual(reference) 44 | }) 45 | 46 | test("multiple local imports", async () => { 47 | const Basic = "0x01" 48 | const Advanced = "0x02" 49 | 50 | const code = ` 51 | import Basic from "../cadence/Basic.cdc" 52 | import Advanced from "../cadence/Advanced.cdc" 53 | ` 54 | 55 | const result = replaceImportAddresses(code, { 56 | Basic, 57 | Advanced, 58 | }) 59 | 60 | const reference = ` 61 | import Basic from ${Basic} 62 | import Advanced from ${Advanced} 63 | ` 64 | 65 | expect(result).toEqual(reference) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /test/basic/logger.test.js: -------------------------------------------------------------------------------- 1 | import {Logger, LOGGER_LEVELS} from "../../src/emulator/logger" 2 | 3 | describe("logger", () => { 4 | it("shall format logged message", () => { 5 | const input = { 6 | time: "2021-09-28T15:06:56+03:00", 7 | level: "info", 8 | msg: "🌱 Starting gRPC server on port 3569", 9 | port: 3659, 10 | } 11 | const msg = JSON.stringify(input) 12 | const [output] = new Logger().parseDataBuffer(msg) 13 | expect(output.level).toBe(LOGGER_LEVELS[input.level.toUpperCase()]) 14 | expect(output.msg).toBe(input.msg) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/cadence/contracts/HelloWorld.cdc: -------------------------------------------------------------------------------- 1 | access(all) contract HelloWorld{ 2 | access(all) let message: String 3 | 4 | init(){ 5 | log("contract added to account") 6 | self.message = "Hello, from Cadence" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/cadence/contracts/utility/Message.cdc: -------------------------------------------------------------------------------- 1 | access(all) contract Message{ 2 | access(all) let data: String 3 | 4 | init(){ 5 | self.data = "This is Message contract" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/cadence/scripts/get-message.cdc: -------------------------------------------------------------------------------- 1 | import HelloWorld from 0x01 2 | 3 | access(all) fun main():String{ 4 | return HelloWorld.message 5 | } 6 | -------------------------------------------------------------------------------- /test/cadence/scripts/log-message.cdc: -------------------------------------------------------------------------------- 1 | access(all) fun main(): Int{ 2 | log("hello from cadence") 3 | return 42 4 | } 5 | -------------------------------------------------------------------------------- /test/cadence/scripts/log-passed-message.cdc: -------------------------------------------------------------------------------- 1 | access(all) fun main(message: String):String{ 2 | log(message) 3 | return message 4 | } 5 | -------------------------------------------------------------------------------- /test/cadence/scripts/read-mocked-block-offset.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main(): UInt64 { 4 | let mockedHeight = FlowManager.getBlockHeight(); 5 | let realHeight = getCurrentBlock().height; 6 | log("Mocked Height: ".concat(mockedHeight.toString())) 7 | log("Real Height: ".concat(realHeight.toString())) 8 | 9 | return mockedHeight - realHeight; 10 | } 11 | -------------------------------------------------------------------------------- /test/cadence/scripts/read-mocked-timestamp-offset.cdc: -------------------------------------------------------------------------------- 1 | import FlowManager from 0x01 2 | 3 | access(all) fun main(): UFix64 { 4 | let mockedTimestamp = FlowManager.getBlockTimestamp(); 5 | let realTimestamp = getCurrentBlock().timestamp; 6 | log("Mocked Height: ".concat(mockedTimestamp.toString())) 7 | log("Real Height: ".concat(realTimestamp.toString())) 8 | 9 | return mockedTimestamp - realTimestamp; 10 | } 11 | -------------------------------------------------------------------------------- /test/cadence/transactions/log-message.cdc: -------------------------------------------------------------------------------- 1 | transaction(message:String){ 2 | prepare(signer: &Account){ 3 | log(message) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cadence/transactions/log-signer-address.cdc: -------------------------------------------------------------------------------- 1 | transaction{ 2 | prepare(signer: auth(BorrowValue, Capabilities) &Account){ 3 | log("Signer Address:".concat(signer.address.toString())) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "networks": { 3 | "emulator": "127.0.0.1:3569", 4 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 5 | "sandboxnet": "access.sandboxnet.nodes.onflow.org:9000", 6 | "testnet": "access.devnet.nodes.onflow.org:9000" 7 | }, 8 | "accounts": { 9 | "emulator-account": { 10 | "address": "f8d6e0586b0a20c7", 11 | "key": "992e51111af4107f1521afffa297788e4b7f83a2012811d6d1ba73d6296216de" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/integration/account.test.js: -------------------------------------------------------------------------------- 1 | import * as fcl from "@onflow/fcl" 2 | import path from "path" 3 | import { 4 | emulator, 5 | init, 6 | getAccountAddress, 7 | executeScript, 8 | createAccount, 9 | HashAlgorithm, 10 | SignatureAlgorithm, 11 | } from "../../src" 12 | import {playgroundImport} from "../../src/transformers" 13 | import {isAddress} from "../../src" 14 | import {validateKeyPair} from "../util/validate-key-pair" 15 | import {permute} from "../util/permute" 16 | import {DEFAULT_TEST_TIMEOUT} from "../util/timeout.const" 17 | 18 | jest.setTimeout(DEFAULT_TEST_TIMEOUT) 19 | 20 | beforeEach(async () => { 21 | const basePath = path.resolve(__dirname, "../cadence") 22 | await init(basePath) 23 | return emulator.start() 24 | }) 25 | 26 | afterEach(async () => { 27 | return emulator.stop() 28 | }) 29 | 30 | it("createAccount - should work with name and resolves to correct getAccountAddress", async () => { 31 | const addr1 = await createAccount({ 32 | name: "Billy", 33 | }) 34 | 35 | const addr2 = await getAccountAddress("Billy") 36 | 37 | expect(addr1).toBe(addr2) 38 | }) 39 | 40 | it("createAccount - should work without name and returns address", async () => { 41 | const Billy = await createAccount({ 42 | name: "Billy", 43 | }) 44 | 45 | expect(isAddress(Billy)).toBe(true) 46 | }) 47 | 48 | test.each(permute(Object.keys(HashAlgorithm), Object.keys(SignatureAlgorithm)))( 49 | "createAccount - should work with custom keys - hash algorithm %s - signing algorithm %s", 50 | async (hashAlgorithm, signatureAlgorithm) => { 51 | const privateKey = "1234" 52 | const Billy = await createAccount({ 53 | name: "Billy", 54 | keys: [ 55 | { 56 | privateKey, 57 | hashAlgorithm: HashAlgorithm[hashAlgorithm], 58 | signatureAlgorithm: SignatureAlgorithm[signatureAlgorithm], 59 | }, 60 | ], 61 | }) 62 | 63 | expect(isAddress(Billy)).toBe(true) 64 | 65 | const keys = await fcl.account(Billy).then(d => d.keys) 66 | 67 | expect(keys.length).toBe(1) 68 | expect(keys[0].hashAlgoString).toBe(hashAlgorithm) 69 | expect(keys[0].signAlgoString).toBe(signatureAlgorithm) 70 | expect(keys[0].weight).toBe(1000) 71 | expect( 72 | validateKeyPair(keys[0].publicKey, privateKey, signatureAlgorithm) 73 | ).toBe(true) 74 | } 75 | ) 76 | 77 | test("createAccount - should work with custom keys - defaults to SHA3_256, ECDSA_P256", async () => { 78 | const privateKey = "1234" 79 | const Billy = await createAccount({ 80 | name: "Billy", 81 | keys: [ 82 | { 83 | privateKey, 84 | }, 85 | ], 86 | }) 87 | 88 | expect(isAddress(Billy)).toBe(true) 89 | 90 | const keys = await fcl.account(Billy).then(d => d.keys) 91 | 92 | expect(keys.length).toBe(1) 93 | expect(keys[0].hashAlgoString).toBe("SHA3_256") 94 | expect(keys[0].signAlgoString).toBe("ECDSA_P256") 95 | expect(keys[0].weight).toBe(1000) 96 | expect( 97 | validateKeyPair( 98 | keys[0].publicKey, 99 | privateKey, 100 | SignatureAlgorithm.ECDSA_P256 101 | ) 102 | ).toBe(true) 103 | }) 104 | 105 | it("createAccount - should add universal private key to account by default", async () => { 106 | const Billy = await createAccount({ 107 | name: "Billy", 108 | }) 109 | 110 | expect(isAddress(Billy)).toBe(true) 111 | }) 112 | 113 | it("getAccountAddress - should return proper playground addresses", async () => { 114 | const accounts = ["Alice", "Bob", "Charlie", "Dave", "Eve"] 115 | for (const i in accounts) { 116 | await getAccountAddress(accounts[i]) 117 | } 118 | 119 | const code = ` 120 | access(all) fun main(address:Address):Address{ 121 | return getAccount(address).address 122 | } 123 | ` 124 | 125 | const playgroundAddresses = ["0x01", "0x02", "0x03", "0x04", "0x05"] 126 | for (const i in playgroundAddresses) { 127 | const [result] = await executeScript({ 128 | code, 129 | transformers: [playgroundImport(accounts)], 130 | args: [playgroundAddresses[i]], 131 | }) 132 | const account = await getAccountAddress(accounts[i]) 133 | expect(result).toBe(account) 134 | } 135 | }) 136 | 137 | it("getAccountAddress - should create an account if does not exist", async () => { 138 | const Billy = await getAccountAddress("Billy") 139 | 140 | expect(isAddress(Billy)).toBe(true) 141 | }) 142 | 143 | it("getAccountAddress - should resolve an already created account", async () => { 144 | const Billy1 = await getAccountAddress("Billy") 145 | const Billy2 = await getAccountAddress("Billy") 146 | 147 | expect(isAddress(Billy1)).toBe(true) 148 | expect(Billy1).toMatch(Billy2) 149 | }) 150 | -------------------------------------------------------------------------------- /test/integration/crypto.test.js: -------------------------------------------------------------------------------- 1 | import {config} from "@onflow/fcl" 2 | import { 3 | createAccount, 4 | emulator, 5 | getAccountAddress, 6 | getServiceAddress, 7 | init, 8 | signUserMessage, 9 | verifyUserSignatures, 10 | } from "../../src" 11 | import { 12 | prependDomainTag, 13 | resolveHashAlgoKey, 14 | resolveSignAlgoKey, 15 | } from "../../src/crypto" 16 | 17 | describe("cryptography tests", () => { 18 | beforeEach(async () => { 19 | await init() 20 | return emulator.start() 21 | }) 22 | 23 | afterEach(async () => { 24 | return emulator.stop() 25 | }) 26 | 27 | test("signUserMessage - sign with address", async () => { 28 | const Alice = await getAccountAddress("Alice") 29 | const msgHex = "a1b2c3" 30 | 31 | const signature = await signUserMessage(msgHex, Alice) 32 | 33 | expect(Object.keys(signature).length).toBe(3) 34 | expect(signature.addr).toBe(Alice) 35 | expect(signature.keyId).toBe(0) 36 | expect(await verifyUserSignatures(msgHex, [signature])).toBe(true) 37 | }) 38 | 39 | test("signUserMessage - sign with KeyObject", async () => { 40 | const hashAlgorithm = "SHA3_256" 41 | const signatureAlgorithm = "ECDSA_P256" 42 | 43 | const privateKey = "1234" 44 | const Adam = await createAccount({ 45 | name: "Adam", 46 | keys: [ 47 | { 48 | privateKey, 49 | hashAlgorithm, 50 | signatureAlgorithm, 51 | weight: 1000, 52 | }, 53 | ], 54 | }) 55 | 56 | const signer = { 57 | addr: Adam, 58 | keyId: 0, 59 | privateKey, 60 | hashAlgorithm, 61 | signatureAlgorithm, 62 | } 63 | 64 | const msgHex = "a1b2c3" 65 | const signature = await signUserMessage(msgHex, signer) 66 | 67 | expect(Object.keys(signature).length).toBe(3) 68 | expect(signature.addr).toBe(Adam) 69 | expect(signature.keyId).toBe(0) 70 | expect(await verifyUserSignatures(msgHex, [signature])).toBe(true) 71 | }) 72 | 73 | test("signUserMessage - sign with domain separation tag", async () => { 74 | const Alice = await getAccountAddress("Alice") 75 | const msgHex = "a1b2c3" 76 | 77 | const signature = await signUserMessage(msgHex, Alice, "foo") 78 | 79 | expect(Object.keys(signature).length).toBe(3) 80 | expect(signature.addr).toBe(Alice) 81 | expect(signature.keyId).toBe(0) 82 | expect(await verifyUserSignatures(msgHex, [signature], "foo")).toBe(true) 83 | }) 84 | 85 | test("signUserMessage - sign with service key", async () => { 86 | const Alice = await getServiceAddress() 87 | const msgHex = "a1b2c3" 88 | 89 | const signature = await signUserMessage(msgHex, Alice) 90 | 91 | expect(Object.keys(signature).length).toBe(3) 92 | expect(signature.addr).toBe(Alice) 93 | expect(signature.keyId).toBe(0) 94 | expect(await verifyUserSignatures(msgHex, [signature])).toBe(true) 95 | }) 96 | 97 | test("verifyUserSignature & signUserMessage - work with Buffer messageHex", async () => { 98 | const Alice = await getAccountAddress("Alice") 99 | const msgHex = Buffer.from([0xa1, 0xb2, 0xc3]) 100 | const signature = await signUserMessage(msgHex, Alice) 101 | 102 | expect(await verifyUserSignatures(msgHex, [signature])).toBe(true) 103 | }) 104 | 105 | test("verifyUserSignature - fails with bad signature", async () => { 106 | const Alice = await getAccountAddress("Alice") 107 | const msgHex = "a1b2c3" 108 | 109 | const badSignature = { 110 | addr: Alice, 111 | keyId: 0, 112 | signature: "a1b2c3", 113 | } 114 | 115 | expect(await verifyUserSignatures(msgHex, [badSignature])).toBe(false) 116 | }) 117 | 118 | test("verifyUserSignature - fails if weight < 1000", async () => { 119 | const Alice = await createAccount({ 120 | name: "Alice", 121 | keys: [ 122 | { 123 | privateKey: await config().get("PRIVATE_KEY"), 124 | weight: 123, 125 | }, 126 | ], 127 | }) 128 | const msgHex = "a1b2c3" 129 | const signature = await signUserMessage(msgHex, Alice) 130 | 131 | expect(await verifyUserSignatures(msgHex, [signature])).toBe(false) 132 | }) 133 | 134 | test("verifyUserSignatures - throws with null signature object", async () => { 135 | const msgHex = "a1b2c3" 136 | 137 | await expect(verifyUserSignatures(msgHex, null)).rejects.toThrow( 138 | "INVARIANT One or mores signatures must be provided" 139 | ) 140 | }) 141 | 142 | test("verifyUserSignatures - throws with no signatures in array", async () => { 143 | const msgHex = "a1b2c3" 144 | 145 | await expect(verifyUserSignatures(msgHex, [])).rejects.toThrow( 146 | "INVARIANT One or mores signatures must be provided" 147 | ) 148 | }) 149 | 150 | test("verifyUserSignatures - throws with different account signatures", async () => { 151 | const Alice = await getAccountAddress("Alice") 152 | const Bob = await getAccountAddress("Bob") 153 | const msgHex = "a1b2c3" 154 | 155 | const signatureAlice = await signUserMessage(msgHex, Alice) 156 | const signatureBob = await signUserMessage(msgHex, Bob) 157 | 158 | await expect( 159 | verifyUserSignatures(msgHex, [signatureAlice, signatureBob]) 160 | ).rejects.toThrow("INVARIANT Signatures must belong to the same address") 161 | }) 162 | 163 | test("verifyUserSignatures - throws with invalid signature format", async () => { 164 | const msgHex = "a1b2c3" 165 | const signature = { 166 | foo: "bar", 167 | } 168 | 169 | await expect(verifyUserSignatures(msgHex, [signature])).rejects.toThrow( 170 | "INVARIANT One or more signature is invalid. Valid signatures have the following keys: addr, keyId, siganture" 171 | ) 172 | }) 173 | 174 | test("verifyUserSignatures - throws with non-existant key", async () => { 175 | const Alice = await getAccountAddress("Alice") 176 | const msgHex = "a1b2c3" 177 | 178 | const signature = await signUserMessage(msgHex, Alice) 179 | signature.keyId = 42 180 | 181 | await expect(verifyUserSignatures(msgHex, [signature])).rejects.toThrow( 182 | `INVARIANT Key index ${signature.keyId} does not exist on account ${Alice}` 183 | ) 184 | }) 185 | 186 | test("prependDomainTag prepends a domain tag to a given msgHex", () => { 187 | const msgHex = "a1b2c3" 188 | const domainTag = "hello world" 189 | const paddedDomainTagHex = 190 | "00000000000000000000000000000000000000000068656c6c6f20776f726c64" 191 | 192 | const result = prependDomainTag(msgHex, domainTag) 193 | const expected = paddedDomainTagHex + msgHex 194 | 195 | expect(result).toEqual(expected) 196 | }) 197 | 198 | test("resolveHashAlgoKey - unsupported hash algorithm", () => { 199 | const hashAlgorithm = "ABC123" 200 | expect(() => resolveHashAlgoKey(hashAlgorithm)).toThrow( 201 | `Provided hash algorithm "${hashAlgorithm}" is not currently supported` 202 | ) 203 | }) 204 | 205 | test("resolveHashAlgoKey - unsupported signature algorithm", () => { 206 | const signatureAlgorithm = "ABC123" 207 | expect(() => resolveSignAlgoKey(signatureAlgorithm)).toThrow( 208 | `Provided signature algorithm "${signatureAlgorithm}" is not currently supported` 209 | ) 210 | }) 211 | }) 212 | -------------------------------------------------------------------------------- /test/integration/deploy.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | emulator, 4 | init, 5 | executeScript, 6 | deployContract, 7 | deployContractByName, 8 | getContractAddress, 9 | getAccountAddress, 10 | getServiceAddress, 11 | shallPass, 12 | shallResolve, 13 | } from "../../src" 14 | import {DEFAULT_TEST_TIMEOUT} from "../util/timeout.const" 15 | 16 | // We need to set timeout for a higher number, cause some transactions might take up some time 17 | jest.setTimeout(DEFAULT_TEST_TIMEOUT) 18 | 19 | describe("interactions - sendTransaction", () => { 20 | // Instantiate emulator and path to Cadence files 21 | beforeEach(async () => { 22 | const basePath = path.resolve(__dirname, "../cadence") 23 | await init(basePath) 24 | return emulator.start() 25 | }) 26 | 27 | // Stop emulator, so it could be restarted 28 | afterEach(async () => { 29 | return emulator.stop() 30 | }) 31 | 32 | test("deploy basic contract - to service account", async () => { 33 | const name = "HelloWorld" 34 | await deployContractByName({name}) 35 | const address = await getContractAddress(name) 36 | const serviceAccount = await getServiceAddress() 37 | expect(address).toBe(serviceAccount) 38 | }) 39 | 40 | test("deploy basic contract - captures logs", async () => { 41 | const name = "HelloWorld" 42 | const [, , logs] = await shallPass(deployContractByName({name})) 43 | expect(logs).toEqual(["contract added to account"]) 44 | }) 45 | 46 | test("deploy basic contract - to service account, short notation", async () => { 47 | const name = "HelloWorld" 48 | await deployContractByName(name) 49 | const address = await getContractAddress(name) 50 | const serviceAccount = await getServiceAddress() 51 | expect(address).toBe(serviceAccount) 52 | }) 53 | 54 | test("deploy basic contract - to Alice account", async () => { 55 | const Alice = await getAccountAddress("Alice") 56 | const name = "HelloWorld" 57 | await deployContractByName({name, to: Alice}) 58 | const address = await getContractAddress(name) 59 | expect(address).toBe(Alice) 60 | }) 61 | 62 | test("deploy basic contract - to Alice account, short notation", async () => { 63 | const name = "HelloWorld" 64 | const Alice = await getAccountAddress("Alice") 65 | await deployContractByName(name, Alice) 66 | const address = await getContractAddress(name) 67 | expect(address).toBe(Alice) 68 | }) 69 | 70 | test("deploy basic contract - check", async () => { 71 | const name = "HelloWorld" 72 | await deployContractByName(name) 73 | const [result, err] = await executeScript({ 74 | code: ` 75 | import HelloWorld from 0x1 76 | 77 | access(all) fun main():String{ 78 | return HelloWorld.message 79 | } 80 | `, 81 | }) 82 | expect(result).toBe("Hello, from Cadence") 83 | expect(err).toBe(null) 84 | }) 85 | 86 | test("deploy custom contract with arguments", async () => { 87 | const message = "Hello, Cadence" 88 | const number = "42" 89 | await shallPass( 90 | deployContract({ 91 | code: ` 92 | access(all) contract Basic{ 93 | access(all) let message: String 94 | access(all) let number: Int 95 | init(message: String, number: Int){ 96 | self.message = message 97 | self.number = number 98 | } 99 | } 100 | `, 101 | args: [message, number], 102 | }) 103 | ) 104 | 105 | // Read message 106 | const [messageResult, messageErr] = await shallResolve( 107 | executeScript({ 108 | code: ` 109 | import Basic from 0x1 110 | 111 | access(all) fun main():String{ 112 | return Basic.message 113 | } 114 | `, 115 | }) 116 | ) 117 | expect(messageResult).toBe(message) 118 | expect(messageErr).toBe(null) 119 | 120 | // Read number 121 | const [numberResult, numberErr] = await shallResolve( 122 | executeScript({ 123 | code: ` 124 | import Basic from 0x1 125 | 126 | access(all) fun main():Int{ 127 | return Basic.number 128 | } 129 | `, 130 | }) 131 | ) 132 | expect(numberResult).toBe(number) 133 | expect(numberErr).toBe(null) 134 | }) 135 | }) 136 | -------------------------------------------------------------------------------- /test/integration/imports.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | emulator, 4 | init, 5 | deployContract, 6 | resolveImports, 7 | getServiceAddress, 8 | } from "../../src" 9 | import {defaultsByName} from "../../src/file" 10 | import {DEFAULT_TEST_TIMEOUT} from "../util/timeout.const" 11 | import {fixShorthandImports} from "../../src/imports" 12 | 13 | jest.setTimeout(DEFAULT_TEST_TIMEOUT) 14 | 15 | const emptyContract = name => 16 | `access(all) contract ${name}{ 17 | init(){} 18 | } 19 | ` 20 | 21 | describe("import resolver", () => { 22 | // Instantiate emulator and path to Cadence files 23 | beforeEach(async () => { 24 | const basePath = path.resolve(__dirname, "../cadence") 25 | await init(basePath) 26 | return emulator.start() 27 | }) 28 | 29 | // Stop emulator, so it could be restarted 30 | afterEach(async () => { 31 | return emulator.stop() 32 | }) 33 | 34 | test("use imports", async () => { 35 | await deployContract({code: emptyContract("First"), name: "First"}) 36 | await deployContract({code: emptyContract("Second"), name: "Second"}) 37 | await deployContract({code: emptyContract("Third"), name: "Third"}) 38 | await deployContract({code: emptyContract("A"), name: "A"}) 39 | await deployContract({code: emptyContract("B"), name: "B"}) 40 | 41 | const code = ` 42 | import First from 0xFIRST 43 | import Second from 0xSECOND 44 | import "Third" 45 | import "A", "B" 46 | 47 | import FungibleToken from 0xFUNGIBLETOKEN 48 | import FlowToken from 0xFLOWTOKEN 49 | 50 | access(all) fun main(){} 51 | ` 52 | 53 | const testFixed = fixShorthandImports(code) 54 | expect(testFixed.includes("import.cdc")).toBe(false) 55 | 56 | const addressMap = await resolveImports(code) 57 | const Registry = await getServiceAddress() 58 | 59 | const {First, Second, Third, A, B, FungibleToken, FlowToken} = addressMap 60 | expect(First).toBe(Registry) 61 | expect(Second).toBe(Registry) 62 | expect(Third).toBe(Registry) 63 | expect(A).toBe(Registry) 64 | expect(B).toBe(Registry) 65 | expect(FungibleToken).toBe(defaultsByName.FungibleToken) 66 | expect(FlowToken).toBe(defaultsByName.FlowToken) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/integration/metadata.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import {init, emulator, executeScript, shallResolve} from "../../src" 3 | import {DEFAULT_TEST_TIMEOUT} from "../util/timeout.const" 4 | 5 | jest.setTimeout(DEFAULT_TEST_TIMEOUT) 6 | 7 | describe("metadata examples", () => { 8 | beforeEach(async () => { 9 | const basePath = path.resolve("./cadence") 10 | await init(basePath) 11 | return emulator.start() 12 | }) 13 | 14 | afterEach(async () => { 15 | return emulator.stop() 16 | }) 17 | 18 | test("simple dictionary - {String: String}", async () => { 19 | const code = ` 20 | access(all) fun main(metadata: {String: String}): String{ 21 | return metadata["name"]! 22 | } 23 | ` 24 | const name = "Cadence" 25 | const args = [{name}] 26 | const [result] = await shallResolve(executeScript({code, args})) 27 | expect(result).toBe(name) 28 | }) 29 | 30 | test("simple dictionary - {String: Int}", async () => { 31 | const code = ` 32 | access(all) fun main(metadata: {String: Int}): Int{ 33 | return metadata["answer"]! 34 | } 35 | ` 36 | const answer = "42" 37 | const args = [{answer}] 38 | const [result] = await shallResolve(executeScript({code, args})) 39 | expect(result).toBe(answer) 40 | }) 41 | 42 | test("simple array - [String]", async () => { 43 | const code = ` 44 | access(all) fun main(list: [String]): String{ 45 | return list[0] 46 | } 47 | ` 48 | const value = "test" 49 | const args = [[value]] 50 | const [result] = await shallResolve(executeScript({code, args})) 51 | expect(result).toBe(value) 52 | }) 53 | 54 | test("nested arrays - [[Int]]", async () => { 55 | const code = ` 56 | access(all) fun main(list: [[Int]], index: Int): Int { 57 | log("this is log message we want to output") 58 | log(list[0][0]) 59 | log(list[0][1]) 60 | log(list[0][2]) 61 | return list[0][index] 62 | } 63 | ` 64 | const value = ["1", "3", "3", "7"] 65 | const index = "3" 66 | const args = [[value], index] 67 | 68 | const [result, err] = await executeScript({code, args}) 69 | expect(result).toBe(value[index]) 70 | expect(err).toBe(null) 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /test/integration/storage.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | emulator, 4 | init, 5 | getAccountAddress, 6 | shallPass, 7 | sendTransaction, 8 | } from "../../src" 9 | import { 10 | getPaths, 11 | getPathsWithType, 12 | getStorageStats, 13 | getStorageValue, 14 | } from "../../src/storage" 15 | import {shallHavePath, shallHaveStorageValue} from "../../src/jest-asserts" 16 | 17 | // We need to set timeout for a higher number, because some transactions might take up some time 18 | jest.setTimeout(50000) 19 | 20 | beforeEach(async () => { 21 | const basePath = path.resolve(__dirname, "../cadence") 22 | await init(basePath) 23 | return emulator.start({ 24 | flags: "--contracts", 25 | }) 26 | }) 27 | 28 | afterEach(async () => { 29 | await emulator.stop() 30 | }) 31 | 32 | describe("Storage Inspection", () => { 33 | test("Paths inspection", async () => { 34 | const Alice = await getAccountAddress("Alice") 35 | const {publicPaths, storagePaths} = await getPaths(Alice) 36 | 37 | // Newly created account shall have 2 public and 1 storage slot occupied for FLOW Vault 38 | expect(publicPaths.size).toBe(2) 39 | expect(publicPaths.has("flowTokenBalance")).toBe(true) 40 | expect(publicPaths.has("flowTokenReceiver")).toBe(true) 41 | 42 | expect(storagePaths.size).toBe(1) 43 | expect(storagePaths.has("flowTokenVault")).toBe(true) 44 | }) 45 | test("Reading storage values", async () => { 46 | const Alice = await getAccountAddress("Alice") 47 | await shallPass( 48 | sendTransaction({ 49 | code: ` 50 | transaction{ 51 | prepare(signer: auth(SaveValue) &Account){ 52 | signer.storage.save(42, to: /storage/answer) 53 | } 54 | } 55 | `, 56 | signers: [Alice], 57 | }) 58 | ) 59 | const {storagePaths} = await getPaths(Alice) 60 | const vault = await getStorageValue(Alice, "flowTokenVault") 61 | const answer = await getStorageValue("Alice", "answer") 62 | const empty = await getStorageValue(Alice, "empty") 63 | 64 | expect(storagePaths.has("answer")).toBe(true) 65 | expect(vault.balance).toBe("0.00100000") 66 | expect(answer).toBe("42") 67 | expect(answer).not.toBe(1337) 68 | expect(empty).toBe(null) 69 | }) 70 | test("Reading types", async () => { 71 | const {publicPaths} = await getPathsWithType("Alice") 72 | const refTokenBalance = publicPaths.flowTokenBalance 73 | 74 | expect(refTokenBalance).not.toBe(undefined) 75 | }) 76 | test("Read storage stats", async () => { 77 | const {capacity, used} = await getStorageStats("Alice") 78 | 79 | expect(capacity).toBe(100000) 80 | expect(used > 0).toBe(true) 81 | }) 82 | test("Jest helper - shallHavePath - pass name", async () => { 83 | await shallHavePath("Alice", "/storage/flowTokenVault") 84 | }) 85 | test("Jest helper - shallHavePath - pass address", async () => { 86 | const Alice = await getAccountAddress("Alice") 87 | await shallHavePath(Alice, "/storage/flowTokenVault") 88 | }) 89 | test("Jest helper - shallHaveStorageValue - check simple storage value", async () => { 90 | const expectedValue = 42 91 | const pathName = "answer" 92 | 93 | const Alice = await getAccountAddress("Alice") 94 | await shallPass( 95 | sendTransaction({ 96 | code: ` 97 | transaction{ 98 | prepare(signer: auth(SaveValue) &Account){ 99 | signer.storage.save(${expectedValue}, to: /storage/${pathName}) 100 | } 101 | } 102 | `, 103 | signers: [Alice], 104 | }) 105 | ) 106 | 107 | await shallHaveStorageValue(Alice, { 108 | pathName, 109 | expect: expectedValue.toString(), 110 | }) 111 | }) 112 | test("Jest helper - shallHaveStorageValue - check complex storage value", async () => { 113 | const Alice = await getAccountAddress("Alice") 114 | await shallHaveStorageValue(Alice, { 115 | pathName: "flowTokenVault", 116 | key: "balance", 117 | expect: "0.00100000", 118 | }) 119 | }) 120 | }) 121 | -------------------------------------------------------------------------------- /test/integration/transformers.test.js: -------------------------------------------------------------------------------- 1 | import {builtInMethods, emulator, init} from "../../src" 2 | import {importManager} from "../../src/transformers" 3 | import path from "path" 4 | 5 | describe("transformers", () => { 6 | beforeEach(async () => { 7 | const basePath = path.resolve(__dirname, "../cadence") 8 | await init(basePath) 9 | return emulator.start() 10 | }) 11 | 12 | // Stop emulator, so it could be restarted 13 | afterEach(async () => { 14 | return emulator.stop() 15 | }) 16 | 17 | it("should inject contract for built-in methods", async () => { 18 | const code = ` 19 | access(all) fun main() : UInt64 { 20 | return getCurrentBlock().height 21 | } 22 | ` 23 | 24 | const transformed = await builtInMethods(code) 25 | const importStatement = await importManager() 26 | expect(transformed.includes(importStatement)).toBe(true) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/integration/usage.test.js: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import { 3 | emulator, 4 | init, 5 | getAccountAddress, 6 | getContractAddress, 7 | deployContractByName, 8 | getScriptCode, 9 | executeScript, 10 | sendTransaction, 11 | mintFlow, 12 | getFlowBalance, 13 | shallRevert, 14 | shallResolve, 15 | shallPass, 16 | shallThrow, 17 | getServiceAddress, 18 | } from "../../src" 19 | import {DEFAULT_TEST_TIMEOUT} from "../util/timeout.const" 20 | 21 | // We need to set timeout for a higher number, because some transactions might take up some time 22 | jest.setTimeout(DEFAULT_TEST_TIMEOUT) 23 | 24 | describe("Basic Usage test", () => { 25 | // Instantiate emulator and path to Cadence files 26 | beforeEach(async () => { 27 | const basePath = path.resolve(__dirname, "../cadence") 28 | await init(basePath) 29 | return emulator.start() 30 | }) 31 | 32 | // Stop emulator, so it could be restarted 33 | afterEach(async () => { 34 | return emulator.stop() 35 | }) 36 | 37 | test("create Accounts", async () => { 38 | const Alice = await getAccountAddress("Alice") 39 | 40 | await deployContractByName({name: "HelloWorld", to: Alice}) 41 | 42 | const addressMap = {HelloWorld: Alice} 43 | const code = await getScriptCode({name: "get-message", addressMap}) 44 | const [message] = await shallResolve( 45 | executeScript({ 46 | code, 47 | }) 48 | ) 49 | expect(message).toBe("Hello, from Cadence") 50 | 51 | await shallPass(mintFlow(Alice, "13.37")) 52 | const [balance] = await shallResolve(getFlowBalance(Alice)) 53 | expect(balance).toBe("13.37100000") 54 | }) 55 | 56 | test("deploy nested contract", async () => { 57 | await deployContractByName({ 58 | name: "utility/Message", 59 | }) 60 | const address = await getContractAddress("Message") 61 | const service = await getServiceAddress() 62 | expect(address).toBe(service) 63 | 64 | const [message] = await shallResolve( 65 | executeScript({ 66 | code: ` 67 | import Message from 0x01 68 | 69 | access(all) fun main():String{ 70 | return Message.data 71 | } 72 | `, 73 | }) 74 | ) 75 | expect(message).toBe("This is Message contract") 76 | }) 77 | }) 78 | 79 | describe("jest methods", () => { 80 | beforeEach(async () => { 81 | const basePath = path.resolve(__dirname, "../cadence") 82 | await init(basePath) 83 | return emulator.start() 84 | }) 85 | 86 | // Stop emulator, so it could be restarted 87 | afterEach(async () => { 88 | return emulator.stop() 89 | }) 90 | 91 | test("shall throw and revert properly", async () => { 92 | await shallRevert( 93 | sendTransaction({ 94 | code: ` 95 | transaction{ 96 | prepare(signer: &Account){ 97 | panic("not on my watch!") 98 | } 99 | } 100 | `, 101 | }) 102 | ) 103 | }) 104 | 105 | test("shall resolve properly", async () => { 106 | const [result, err] = await shallResolve( 107 | executeScript({ 108 | code: ` 109 | access(all) fun main(): Int{ 110 | return 42 111 | } 112 | `, 113 | }) 114 | ) 115 | expect(result).toBe("42") 116 | expect(err).toBe(null) 117 | }) 118 | 119 | test("shall pass tx", async () => { 120 | await shallPass( 121 | sendTransaction({ 122 | code: ` 123 | transaction{ 124 | prepare(signer: &Account){} 125 | } 126 | `, 127 | }) 128 | ) 129 | }) 130 | 131 | test("shall throw error", async () => { 132 | await shallThrow( 133 | executeScript({ 134 | code: ` 135 | access(all) fun main(){ 136 | panic("exit here") 137 | } 138 | `, 139 | }) 140 | ) 141 | }) 142 | }) 143 | 144 | describe("Path arguments", () => { 145 | beforeEach(async () => { 146 | const basePath = path.resolve(__dirname, "../cadence") 147 | await init(basePath) 148 | 149 | return emulator.start() 150 | }) 151 | 152 | // Stop emulator, so it could be restarted 153 | afterEach(async () => { 154 | return emulator.stop() 155 | }) 156 | 157 | it("shall work with Path variables in code", async () => { 158 | const [result] = await shallResolve( 159 | executeScript({ 160 | code: ` 161 | access(all) fun main(): Bool{ 162 | let path = StoragePath(identifier: "foo") 163 | log(path) 164 | 165 | return true 166 | } 167 | `, 168 | }) 169 | ) 170 | expect(result).toBe(true) 171 | }) 172 | 173 | it("shall be possible to pass Path argument", async () => { 174 | const [result] = await shallResolve( 175 | executeScript({ 176 | code: ` 177 | access(all) fun main(path: Path): Bool{ 178 | log("this is awesome") 179 | log(path) 180 | 181 | return true 182 | } 183 | `, 184 | args: ["/storage/foo"], 185 | }) 186 | ) 187 | expect(result).toBe(true) 188 | }) 189 | 190 | it("shall show debug logs", async () => { 191 | const [result] = await shallResolve( 192 | executeScript({ 193 | code: ` 194 | access(all) fun main(): Bool{ 195 | return true 196 | } 197 | `, 198 | }) 199 | ) 200 | expect(result).toBe(true) 201 | }) 202 | }) 203 | -------------------------------------------------------------------------------- /test/util/index.test.js: -------------------------------------------------------------------------------- 1 | import {getValueByKey} from "../../src/utils" 2 | 3 | describe("testing utilities", function () { 4 | test("extract object value - simple key", () => { 5 | const expected = 42 6 | 7 | const key = "answer" 8 | const obj = { 9 | [key]: expected, 10 | } 11 | const actual = getValueByKey(key, obj) 12 | expect(actual).toBe(expected) 13 | }) 14 | test("extract object value - nested key", () => { 15 | const expected = 42 16 | 17 | const key = "some.nested.value" 18 | const obj = { 19 | some: { 20 | nested: { 21 | value: expected, 22 | }, 23 | }, 24 | } 25 | const actual = getValueByKey(key, obj) 26 | expect(actual).toBe(expected) 27 | }) 28 | test("extract object value - not an object", () => { 29 | const expected = null 30 | 31 | const key = "some.nested.value" 32 | const obj = "not an object" 33 | const actual = getValueByKey(key, obj) 34 | expect(actual).toBe(expected) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/util/permute.js: -------------------------------------------------------------------------------- 1 | export const permute = (...values) => 2 | values.length > 1 3 | ? permute( 4 | values[0].reduce((acc, i) => { 5 | return [...acc, ...values[1].map(j => [].concat(i).concat(j))] 6 | }, []) 7 | ) 8 | : values[0] 9 | -------------------------------------------------------------------------------- /test/util/timeout.const.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_TEST_TIMEOUT = 10000 2 | -------------------------------------------------------------------------------- /test/util/validate-key-pair.js: -------------------------------------------------------------------------------- 1 | import {resolveSignAlgoKey, ec, SignatureAlgorithm} from "../../src/crypto" 2 | import {isString} from "../../src/utils" 3 | 4 | export function validateKeyPair( 5 | publicKey, 6 | privateKey, 7 | signatureAlgorithm = SignatureAlgorithm.P256 8 | ) { 9 | const signAlgoKey = resolveSignAlgoKey(signatureAlgorithm) 10 | 11 | const prepareKey = key => { 12 | if (isString(key)) key = Buffer.from(key, "hex") 13 | if (key.at(0) !== 0x04) key = Buffer.concat([Buffer.from([0x04]), key]) 14 | return key 15 | } 16 | 17 | publicKey = prepareKey(publicKey) 18 | privateKey = prepareKey(privateKey) 19 | 20 | const pair = ec[signAlgoKey].keyPair({ 21 | pub: publicKey, 22 | priv: privateKey, 23 | }) 24 | 25 | return pair.validate()?.result ?? false 26 | } 27 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const CopyPlugin = require("copy-webpack-plugin") 3 | 4 | module.exports = { 5 | target: "node", 6 | mode: "production", 7 | entry: { 8 | index: path.resolve(__dirname, "src", "index.js"), 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, "dist"), 12 | }, 13 | 14 | optimization: { 15 | splitChunks: {chunks: "all"}, 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js/, 22 | exclude: /(node_modules)/, 23 | use: ["babel-loader"], 24 | }, 25 | ], 26 | }, 27 | resolve: { 28 | extensions: [".js"], 29 | }, 30 | plugins: [ 31 | new CopyPlugin({ 32 | patterns: [ 33 | { 34 | from: "src/**/*.cdc", 35 | to(props) { 36 | const {context, absoluteFilename} = props 37 | console.log(props) 38 | const suffix = context + "/src/" 39 | const result = absoluteFilename.slice(suffix.length) 40 | // eslint-disable-next-line no-undef 41 | return Promise.resolve(result) 42 | }, 43 | }, 44 | ], 45 | }), 46 | ], 47 | } 48 | --------------------------------------------------------------------------------