├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── ci.yaml ├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── account_test.go ├── address.go ├── address_test.go ├── blocks_test.go ├── cadence └── scripts │ └── block.cdc ├── contracts ├── Debug.cdc ├── FlowToken.cdc ├── FungibleToken.cdc ├── MetadataViews.cdc ├── NonFungibleToken.cdc └── ViewResolver.cdc ├── doc_test.go ├── event.go ├── event_filter_test.go ├── example ├── example_test.go ├── flow.json └── setup_test.go ├── flix.go ├── flow.json ├── generate.go ├── generate_test.go ├── go.mod ├── go.sum ├── identifier_integration_test.go ├── interaction_builder.go ├── log.go ├── metadata.go ├── meter.go ├── mocks └── OverflowClient.go ├── npm_module.go ├── parse_integration_test.go ├── print.go ├── result.go ├── rollback_test.go ├── script.go ├── script_integration_test.go ├── script_v3_test.go ├── scripts ├── aScript.cdc ├── block.cdc ├── emulatorFoo.cdc ├── mainnetFoo.cdc ├── mainnetaScript.cdc ├── mainnetzScript.cdc ├── test.cdc ├── testnetFoo.cdc ├── type.cdc └── zScript.cdc ├── setup.go ├── setup_integration_test.go ├── setup_test.go ├── sign.go ├── sign_integration_test.go ├── state.go ├── stop_on_failure_test.go ├── templates.go ├── testdata ├── TestArguments │ ├── AccountArray.golden │ ├── Address.golden │ ├── Paths.golden │ ├── Raw_address.golden │ ├── ScalarMap.golden │ ├── ScalarMapArray.golden │ ├── StringArray.golden │ ├── StringMap.golden │ ├── StringMapArray.golden │ ├── UFix64Array.golden │ ├── Uint64Array.golden │ └── Uint8Array.golden ├── TestCheckContractUpdate │ ├── Should_return_the_updatable_contracts.golden │ └── Should_return_the_updatable_contracts_(updatable).golden ├── TestParseConfig │ └── parse_and_filter.golden ├── contracts │ ├── Debug.cdc │ ├── Debug2.cdc │ ├── FlowToken.cdc │ ├── FungibleToken.cdc │ ├── MetadataViews.cdc │ ├── NonFungibleToken.cdc │ └── ViewResolver.cdc ├── flow-with-multiple-deployments.json ├── graffle-event.golden ├── invalid_account_in_deployment.json ├── invalid_env_flow.json ├── non_existing_contract.json ├── pig.png └── testFile.txt ├── testing.go ├── transaction.go ├── transaction_integration_test.go ├── transaction_test.go ├── transactions ├── aTransaction.cdc ├── arguments.cdc ├── argumentsWithAccount.cdc ├── create_nft_collection.cdc ├── emulatorFoo.cdc ├── mainnetFoo.cdc ├── mainnetaTransaction.cdc ├── mainnetzTransaction.cdc ├── mint_tokens.cdc ├── sendFlow.cdc ├── signWithMultipleAccounts.cdc ├── testnetFoo.cdc └── zTransaction.cdc ├── tx ├── create_nft_collection.cdc └── mint_tokens.cdc ├── upload_integration_test.go └── utils.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Closes #??? 2 | 3 | ## Description 4 | 5 | 9 | 10 | ______ 11 | 12 | For contributor use: 13 | 14 | - [ ] Targeted PR against `main` branch 15 | - [ ] Code follows the [standards mentioned here](https://github.com/bjartek/overflow/blob/main/CONTRIBUTING.md#styleguides) 16 | - [ ] Updated relevant documentation 17 | - [ ] Re-reviewed `Files changed` in the Github PR explorer 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | name: ci 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: "1.22.*" 19 | - name: golangci-lint 20 | uses: golangci/golangci-lint-action@v6 21 | with: 22 | args: --timeout=3m 23 | 24 | tidy: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v4 29 | - uses: actions/setup-go@v5 30 | with: 31 | go-version: "1.22.*" 32 | - uses: zencargo/github-action-go-mod-tidy@v1 33 | with: 34 | go-version: "1.22" 35 | 36 | test: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout code 40 | uses: actions/checkout@v4 41 | - uses: actions/setup-go@v5 42 | with: 43 | go-version: "1.22" 44 | - run: make test-report 45 | - name: Publish Unit Test Results 46 | uses: EnricoMi/publish-unit-test-result-action@v1 47 | if: always() 48 | with: 49 | files: test-result.xml 50 | 51 | coverage: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - name: Checkout code 55 | uses: actions/checkout@v4 56 | - uses: actions/setup-go@v5 57 | with: 58 | go-version: "1.22" 59 | - run: make coveralls 60 | - uses: shogo82148/actions-goveralls@v1 61 | if: always() 62 | with: 63 | path-to-profile: profile.cov 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | vendor 4 | .idea 5 | profile.cov 6 | test-result.xml 7 | coverage-report.json 8 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | errcheck: 3 | check-type-assertions: true 4 | goconst: 5 | min-len: 2 6 | min-occurrences: 3 7 | gocritic: 8 | enabled-tags: 9 | - diagnostic 10 | - experimental 11 | - opinionated 12 | - performance 13 | - style 14 | govet: 15 | check-shadowing: true 16 | nolintlint: 17 | require-explanation: true 18 | require-specific: true 19 | funlen: 20 | lines: 100 21 | statements: 40 22 | 23 | linters: 24 | disable-all: true 25 | enable: 26 | - bodyclose 27 | - deadcode 28 | - depguard 29 | - dogsled 30 | #- dupl 31 | - errcheck 32 | - exportloopref 33 | - exhaustive 34 | - goconst 35 | # - gocritic 36 | - gofmt 37 | - goimports 38 | # - gomnd 39 | - gocyclo 40 | # - gosec 41 | - gosimple 42 | - govet 43 | - ineffassign 44 | - misspell 45 | - nolintlint 46 | - nakedret 47 | # - prealloc 48 | - predeclared 49 | # - revive 50 | - staticcheck 51 | - structcheck 52 | # - stylecheck 53 | - thelper 54 | - typecheck 55 | - unconvert 56 | - unparam 57 | - varcheck 58 | # - whitespace 59 | # - wsl 60 | 61 | run: 62 | issues-exit-code: 0 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - OverflowState.Account is gone use AccountE and handle the error: (might consider adding this back again and deprecating it) 2 | - Script with inline -> InlineScript 3 | - the type of the state changed from Overflow -> OverflowState 4 | - parsing of events into interface{}/json is more typesafe so it changes from not only beeing strings! 5 | - remove discordgo 6 | - changed the folder structure so you do not have to double import overflow and make it build better on godoc 7 | 8 | TODO: 9 | - document what not to do when composing. 10 | 11 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @bjartek 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 | Discord bjartek#3655. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Flow CLI 2 | 3 | The following is a set of guidelines for contributing to Overflow 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 | These guidelines are loosely based on the guidelines found in flow-cli 7 | 8 | ## Table Of Contents 9 | 10 | [Getting Started](#project-overview) 11 | 12 | [How Can I Contribute?](#how-can-i-contribute) 13 | 14 | - [Reporting Bugs](#reporting-bugs) 15 | - [Suggesting Enhancements](#suggesting-enhancements) 16 | - [Pull Requests](#pull-requests) 17 | 18 | [Styleguides](#styleguides) 19 | 20 | - [Git Commit Messages](#git-commit-messages) 21 | - [Go Styleguide](#go-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 of the CLI 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 overflow users. 78 | 79 | 80 | ### Pull Requests 81 | 82 | The process described here has several goals: 83 | 84 | - Maintain code quality 85 | - Fix problems that are important to users 86 | - Engage the community in working toward the best possible UX 87 | - Enable a sustainable system for the maintainers to review contributions 88 | 89 | Please follow the [styleguides](#styleguides) to have your contribution considered by the maintainers. 90 | Reviewer(s) may ask you to complete additional design work, tests, 91 | or other changes before your pull request can be ultimately accepted. 92 | 93 | ## Styleguides 94 | 95 | Before contributing, make sure to examine the project to get familiar with the patterns and style already being used. 96 | 97 | ### Git Commit Messages 98 | 99 | - Use the present tense ("Add feature" not "Added feature") 100 | - Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 101 | - Limit the first line to 72 characters or less 102 | - Reference issues and pull requests liberally after the first line 103 | 104 | ### Go Styleguide 105 | 106 | The majority of this project is written Go. 107 | 108 | We try to follow the coding guidelines from the Go community. 109 | 110 | - Code should be formatted using `gofmt` 111 | - Code should pass the linter: `make lint` 112 | - Code should follow the guidelines covered in 113 | [Effective Go](http://golang.org/doc/effective_go.html) 114 | and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 115 | - Code should be commented 116 | - Code should pass all tests: `make test` 117 | 118 | ## Inspiration and Reference 119 | https://clig.dev/ 120 | 121 | https://blog.developer.atlassian.com/10-design-principles-for-delightful-clis/ 122 | 123 | https://devcenter.heroku.com/articles/cli-style-guide 124 | 125 | https://eng.localytics.com/exploring-cli-best-practices/ 126 | 127 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2020 Bjarte Stien Karlsen, Eric Breuers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: coveralls cover install-gotestsum test-report 2 | 3 | coveralls: 4 | go test --timeout 120s -coverprofile=profile.cov -covermode=atomic -coverpkg=github.com/bjartek/overflow/v2 -v ./... 5 | 6 | cover: coveralls 7 | go tool cover -html=profile.cov 8 | 9 | install-gotestsum: 10 | go install gotest.tools/gotestsum@latest 11 | 12 | test-report: install-gotestsum 13 | gotestsum -f testname --no-color --hide-summary failed --junitfile test-result.xml 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Coverage Status](https://coveralls.io/repos/github/bjartek/overflow/badge.svg?branch=main)](https://coveralls.io/github/bjartek/overflow?branch=main) [![ci](https://github.com/bjartek/overflow/actions/workflows/ci.yaml/badge.svg)](https://github.com/bjartek/overflow/actions/workflows/ci.yaml) 2 | 3 | # Overflow 4 | 5 | This is the v2 version of overflow to be used with cadence 1.0, for the v1 version that targets 0.x look in the v1 branch. 6 | 7 | A DSL written in golang to be used in tests or to run a `story` of interactions against either an local emulator, testnet, mainnet or an in memory instance of the flow-emulator. 8 | 9 | Use case scenarios include: 10 | - demo purposes 11 | - integration testing of combinations of scripts/transactions 12 | - batch jobs. 13 | 14 | For a standalone example on how overflow can be used look at https://github.com/bjartek/flow-nft-overflow it has both an interactive demo and unit tests for an example NFT contract. 15 | 16 | ## Main features 17 | 18 | - Uses a shared golang builder pattern for almost all interactions. 19 | - Well documented source code 20 | - supports all variants of multi-sign, Two authorizers, proposer and payer can be different. 21 | - when refering to an account/address you can use the logical name for that stakeholder defined in flow json. the same stakeholder IE admin can have different addresses on each network 22 | - can be run in embedded in memory mode that will start emulator, deploy contracts, create stakeholders and run interactions (scripts/transactions) against this embedded system and then stop it when it ends. 23 | - has a DSL to fetch Events and optionally store progress in a file. This can be chained into indexers/crawlers/notification services. 24 | - all interactions can be specified inline as well as from files 25 | - transform all interactions into a NPM module that can be published for the frontend to use. this json file that is generate has the option to filter out certain interactions and to strip away network suffixes if you have multiple local interactions that should map to the same logical name in the client for each network 26 | - the interaction (script/tx) dsl has a rich set of assertions 27 | - arguments to interactions are all _named_ that is the same name in that is in the argument must be used with the `Arg("name", "value")` builder. The `value` in this example can be either a primitive go value or a `cadence.Value`. 28 | - supports shared instance in test to collect coverage report and rollback after/before each test. See `example` folder. 29 | 30 | ## Gotchas 31 | 32 | - When specifying extra accounts that are created on emulator they are created in alphabetical order, the addresses the emulator assign is always fixed. 33 | - tldr; Name your stakeholder accounts in alphabetical order, we suggest admin, bob, charlie, demi, eddie 34 | - When writing integration tests, tests must be in the same folder as flow.json 35 | with contracts and transactions/scripts in subdirectories in order for the path resolver 36 | to work correctly 37 | 38 | ## Resources 39 | 40 | - Check [other codebases](https://github.com/bjartek/overflow/network/dependents) that use this project 41 | - Feel free to ask questions to @bjartek in the Overflow Discord. https://discord.gg/t6GEtHnWFh 42 | 43 | ## Usage 44 | 45 | First create a project directory, initialize the go module and install `overflow`: 46 | 47 | ```sh 48 | mkdir test-overflow && cd test-overflow 49 | flow init 50 | go mod init example.com/test-overflow 51 | go get github.com/bjartek/overflow/v2 52 | ``` 53 | 54 | Then create a task file: 55 | 56 | ```sh 57 | touch tasks/main.go 58 | ``` 59 | 60 | In that task file, you can then import `overflow` and use it to your convenience, for example: 61 | 62 | ```go 63 | package main 64 | 65 | import ( 66 | "fmt" 67 | 68 | //if you imports this with . you do not have to repeat overflow everywhere 69 | . "github.com/bjartek/overflow/v2" 70 | ) 71 | 72 | func main() { 73 | 74 | //start an in memory emulator by default 75 | // there are two interfaces published for Overflow, `OverflowClient` and `OverrflowBetaClient` that has unstable api, I urge you to store this as the client and not the impl. Currenly the Overflow method returns the impl so you can choose. 76 | o := Overflow() 77 | 78 | //the Tx DSL runs an transaction 79 | o.Tx("name_of_transaction", SignProposeAndPayAs("bob"), Arg("name", "bob")).Print() 80 | 81 | //Run a script/get interaction against the same in memory chain 82 | o.Script("name_of_script", Arg("name", "bob")).Print() 83 | } 84 | ``` 85 | 86 | Then you can run 87 | 88 | ```sh 89 | go run ./tasks/main.go 90 | ``` 91 | 92 | This is a minimal example that run a transction and a script, but from there you can branch out. 93 | 94 | The following env vars are supported 95 | - OVERFLOW_ENV : set the environment to run against "emulator|embedded|testnet|mainnet|testing" (embedded is standard) 96 | - OVEFFLOW_CONTINUE: if you do not want overflow to deploy contracts and accounts on emulator you can set this to true 97 | - OVERFLOW_LOGGING: Set this to 0-4 to get increasing log 98 | 99 | ## Usage flix 100 | ```package main 101 | 102 | import ( 103 | . "github.com/bjartek/overflow/v2" 104 | ) 105 | 106 | func main() { 107 | o := Overflow(WithNetwork("mainnet")) 108 | 109 | o.FlixTx("transfer-flow", 110 | WithSigner(""), 111 | WithArg("amount", 0.42), 112 | WithArg("to", "0x886f3aeaf848c535"), 113 | ).Print() 114 | }``` 115 | 116 | This will send 0.42 flow to me on mainnet. You need a go file with that content and a valid flow.json that is it 117 | 118 | 119 | 120 | ## Migrating from v1 api 121 | 122 | You need to change your imports to be v2 and not v1 123 | 124 | ## Credits 125 | 126 | This project is the successor of https://github.com/bjartek/go-with-the-flow 127 | The v0 version of the code with a set of apis that is now deprecated is in https://github.com/bjartek/overflow/tree/v0 128 | The v1 version of the code with a set of apis that is now deprecated is in https://github.com/bjartek/overflow/tree/v1 129 | -------------------------------------------------------------------------------- /account_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestErrorsInAccountCreation(t *testing.T) { 12 | 13 | t.Run("Should deploy contracts to multiple accounts", func(t *testing.T) { 14 | _, err := OverflowTesting(WithFlowConfig("testdata/flow-with-multiple-deployments.json"), WithLogFull(), WithFlowForNewUsers(100.0)) 15 | assert.NoError(t, err) 16 | }) 17 | 18 | t.Run("Should give error on wrong contract name", func(t *testing.T) { 19 | _, err := OverflowTesting(WithFlowConfig("testdata/non_existing_contract.json")) 20 | assert.ErrorContains(t, err, "deployment contains nonexisting contract Debug2") 21 | }) 22 | 23 | t.Run("Should give error on invalid env var in flow.json", func(t *testing.T) { 24 | _, err := OverflowTesting(WithFlowConfig("testdata/invalid_env_flow.json")) 25 | require.Error(t, err) 26 | assert.Contains(t, err.Error(), "required environment variable INVALID_ENV_VAR not set") 27 | }) 28 | 29 | t.Run("Should give error on wrong account name", func(t *testing.T) { 30 | _, err := OverflowTesting(WithFlowConfig("testdata/invalid_account_in_deployment.json")) 31 | require.Error(t, err) 32 | assert.Contains(t, err.Error(), "deployment contains nonexisting account emulator-firs") 33 | }) 34 | 35 | } 36 | 37 | func TestGetAccount(t *testing.T) { 38 | 39 | t.Run("Should return the account", func(t *testing.T) { 40 | g, err := OverflowTesting() 41 | require.NoError(t, err) 42 | require.NotNil(t, g) 43 | assert.NotNil(t, g) 44 | account, err := g.GetAccount(context.Background(), "account") 45 | require.NoError(t, err) 46 | assert.Equal(t, "f8d6e0586b0a20c7", account.Address.String()) 47 | }) 48 | 49 | t.Run("Should return an error if account doesn't exist", func(t *testing.T) { 50 | g, err := OverflowTesting() 51 | require.NotNil(t, g) 52 | require.NoError(t, err) 53 | _, err = g.GetAccount(context.Background(), "doesnotexist") 54 | assert.ErrorContains(t, err, "could not find account with name emulator-doesnotexist in the configuration") 55 | 56 | }) 57 | 58 | t.Run("Should return an error if sa does not exist", func(t *testing.T) { 59 | _, err := OverflowTesting(WithServiceAccountSuffix("dummy")) 60 | 61 | assert.ErrorContains(t, err, "could not find account with name emulator-dummy in the configuration") 62 | 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /address.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | linearCodeN = 64 10 | 11 | parityCheckMatrixColumns = []*big.Int{ 12 | big.NewInt(0x00001), big.NewInt(0x00002), big.NewInt(0x00004), big.NewInt(0x00008), 13 | big.NewInt(0x00010), big.NewInt(0x00020), big.NewInt(0x00040), big.NewInt(0x00080), 14 | big.NewInt(0x00100), big.NewInt(0x00200), big.NewInt(0x00400), big.NewInt(0x00800), 15 | big.NewInt(0x01000), big.NewInt(0x02000), big.NewInt(0x04000), big.NewInt(0x08000), 16 | big.NewInt(0x10000), big.NewInt(0x20000), big.NewInt(0x40000), big.NewInt(0x7328d), 17 | big.NewInt(0x6689a), big.NewInt(0x6112f), big.NewInt(0x6084b), big.NewInt(0x433fd), 18 | big.NewInt(0x42aab), big.NewInt(0x41951), big.NewInt(0x233ce), big.NewInt(0x22a81), 19 | big.NewInt(0x21948), big.NewInt(0x1ef60), big.NewInt(0x1deca), big.NewInt(0x1c639), 20 | big.NewInt(0x1bdd8), big.NewInt(0x1a535), big.NewInt(0x194ac), big.NewInt(0x18c46), 21 | big.NewInt(0x1632b), big.NewInt(0x1529b), big.NewInt(0x14a43), big.NewInt(0x13184), 22 | big.NewInt(0x12942), big.NewInt(0x118c1), big.NewInt(0x0f812), big.NewInt(0x0e027), 23 | big.NewInt(0x0d00e), big.NewInt(0x0c83c), big.NewInt(0x0b01d), big.NewInt(0x0a831), 24 | big.NewInt(0x982b), big.NewInt(0x07034), big.NewInt(0x0682a), big.NewInt(0x05819), 25 | big.NewInt(0x03807), big.NewInt(0x007d2), big.NewInt(0x00727), big.NewInt(0x0068e), 26 | big.NewInt(0x0067c), big.NewInt(0x0059d), big.NewInt(0x004eb), big.NewInt(0x003b4), 27 | big.NewInt(0x0036a), big.NewInt(0x002d9), big.NewInt(0x001c7), big.NewInt(0x0003f), 28 | } 29 | ) 30 | 31 | func GetNetworkFromAddress(input string) string { 32 | address := strings.TrimPrefix(input, "0x") 33 | testnet, _ := new(big.Int).SetString("6834ba37b3980209", 16) 34 | emulator, _ := new(big.Int).SetString("1cb159857af02018", 16) 35 | 36 | networkCodewords := map[string]*big.Int{ 37 | "mainnet": big.NewInt(0), 38 | "testnet": testnet, 39 | "emulator": emulator, 40 | } 41 | 42 | for network, codeWord := range networkCodewords { 43 | if IsValidAddressForNetwork(address, network, codeWord) { 44 | return network 45 | } 46 | } 47 | return "" 48 | } 49 | 50 | func IsValidAddressForNetwork(address, network string, codeWord *big.Int) bool { 51 | flowAddress, ok := new(big.Int).SetString(address, 16) 52 | if !ok { 53 | panic("not valid address") 54 | } 55 | codeWord.Xor(codeWord, flowAddress) 56 | 57 | if codeWord.Cmp(big.NewInt(0)) == 0 { 58 | return false 59 | } 60 | 61 | parity := big.NewInt(0) 62 | for i := 0; i < linearCodeN; i++ { 63 | if codeWord.Bit(0) == 1 { 64 | parity.Xor(parity, parityCheckMatrixColumns[i]) 65 | } 66 | codeWord.Rsh(codeWord, 1) 67 | } 68 | 69 | return parity.Cmp(big.NewInt(0)) == 0 && codeWord.Cmp(big.NewInt(0)) == 0 70 | } 71 | -------------------------------------------------------------------------------- /address_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestGetAddress(t *testing.T) { 11 | o, err := OverflowTesting() 12 | require.NoError(t, err) 13 | 14 | testCases := map[string]string{ 15 | "first": "0x179b6b1cb6755e31", 16 | "FlowToken": "0x0ae53cb6e3f42a79", 17 | "Debug": "0xf8d6e0586b0a20c7", 18 | } 19 | 20 | for name, result := range testCases { 21 | t.Run(name, func(t *testing.T) { 22 | assert.EqualValues(t, result, o.Address(name)) 23 | }) 24 | } 25 | } 26 | 27 | func TestAddressNetworks(t *testing.T) { 28 | t.Run("emulator with prefix", func(t *testing.T) { 29 | assert.Equal(t, "emulator", GetNetworkFromAddress("0xf8d6e0586b0a20c7")) 30 | }) 31 | t.Run("emulator", func(t *testing.T) { 32 | assert.Equal(t, "emulator", GetNetworkFromAddress("f8d6e0586b0a20c7")) 33 | }) 34 | t.Run("testnet", func(t *testing.T) { 35 | assert.Equal(t, "testnet", GetNetworkFromAddress("9a0766d93b6608b7")) 36 | }) 37 | t.Run("mainnet", func(t *testing.T) { 38 | assert.Equal(t, "mainnet", GetNetworkFromAddress("f233dcee88fe0abe")) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /blocks_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestGetBlock(t *testing.T) { 12 | 13 | t.Run("Should get latest block", func(t *testing.T) { 14 | g, err := OverflowTesting() 15 | require.NoError(t, err) 16 | require.NotNil(t, g) 17 | block, err := g.GetLatestBlock(context.Background()) 18 | 19 | assert.Nil(t, err) 20 | assert.GreaterOrEqual(t, block.Height, uint64(0)) 21 | }) 22 | 23 | t.Run("Should get block by height", func(t *testing.T) { 24 | g, err := OverflowTesting() 25 | require.NoError(t, err) 26 | block, err := g.GetBlockAtHeight(context.Background(), 0) 27 | 28 | assert.Nil(t, err) 29 | assert.Equal(t, uint64(0), block.Height) 30 | }) 31 | 32 | t.Run("Should get block by ID", func(t *testing.T) { 33 | g, err := OverflowTesting() 34 | require.NoError(t, err) 35 | block, err := g.GetBlockAtHeight(context.Background(), 0) 36 | assert.Nil(t, err) 37 | block, err = g.GetBlockById(context.Background(), block.ID.String()) 38 | assert.Nil(t, err) 39 | assert.NotNil(t, block) 40 | }) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /cadence/scripts/block.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | access(all) fun main(): UInt64 { 3 | let height = getCurrentBlock().height 4 | log(height) 5 | return height 6 | } 7 | -------------------------------------------------------------------------------- /contracts/Debug.cdc: -------------------------------------------------------------------------------- 1 | import "NonFungibleToken" 2 | 3 | access(all) contract Debug { 4 | 5 | access(all) struct FooListBar { 6 | access(all) let foo:[Foo2] 7 | access(all) let bar:String 8 | 9 | init(foo:[Foo2], bar:String) { 10 | self.foo=foo 11 | self.bar=bar 12 | } 13 | } 14 | access(all) struct FooBar { 15 | access(all) let foo:Foo 16 | access(all) let bar:String 17 | 18 | init(foo:Foo, bar:String) { 19 | self.foo=foo 20 | self.bar=bar 21 | } 22 | } 23 | 24 | 25 | access(all) struct Foo2{ 26 | access(all) let bar: Address 27 | 28 | init(bar: Address) { 29 | self.bar=bar 30 | } 31 | } 32 | 33 | access(all) struct Foo{ 34 | access(all) let bar: String 35 | 36 | init(bar: String) { 37 | self.bar=bar 38 | } 39 | } 40 | 41 | access(all) event Log(msg: String) 42 | access(all) event LogNum(id: UInt64) 43 | 44 | access(all) fun id(_ id:UInt64) { 45 | emit LogNum(id:id) 46 | } 47 | 48 | access(all) fun log(_ msg: String) : String { 49 | emit Log(msg: msg) 50 | return msg 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /contracts/FungibleToken.cdc: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | # The Flow Fungible Token standard 4 | 5 | ## `FungibleToken` contract interface 6 | 7 | The interface that all Fungible Token contracts would have to conform to. 8 | If a users wants to deploy a new token contract, their contract 9 | would need to implement the FungibleToken interface. 10 | 11 | Their contract would have to follow all the rules and naming 12 | that the interface specifies. 13 | 14 | ## `Vault` resource 15 | 16 | Each account that owns tokens would need to have an instance 17 | of the Vault resource stored in their account storage. 18 | 19 | The Vault resource has methods that the owner and other users can call. 20 | 21 | ## `Provider`, `Receiver`, and `Balance` resource interfaces 22 | 23 | These interfaces declare pre-conditions and post-conditions that restrict 24 | the execution of the functions in the Vault. 25 | 26 | They are separate because it gives the user the ability to share 27 | a reference to their Vault that only exposes the fields functions 28 | in one or more of the interfaces. 29 | 30 | It also gives users the ability to make custom resources that implement 31 | these interfaces to do various things with the tokens. 32 | For example, a faucet can be implemented by conforming 33 | to the Provider interface. 34 | 35 | By using resources and interfaces, users of Fungible Token contracts 36 | can send and receive tokens peer-to-peer, without having to interact 37 | with a central ledger smart contract. To send tokens to another user, 38 | a user would simply withdraw the tokens from their Vault, then call 39 | the deposit function on another user's Vault to complete the transfer. 40 | 41 | */ 42 | 43 | /// The interface that Fungible Token contracts implement. 44 | /// 45 | access(all) contract interface FungibleToken { 46 | 47 | // An entitlement for allowing the withdrawal of tokens from a Vault 48 | access(all) entitlement Withdrawable 49 | 50 | /// The total number of tokens in existence. 51 | /// It is up to the implementer to ensure that the total supply 52 | /// stays accurate and up to date 53 | access(all) var totalSupply: UFix64 54 | 55 | /// The event that is emitted when the contract is created 56 | access(all) event TokensInitialized(initialSupply: UFix64) 57 | 58 | /// The event that is emitted when tokens are withdrawn from a Vault 59 | access(all) event TokensWithdrawn(amount: UFix64, from: Address?) 60 | 61 | /// The event that is emitted when tokens are deposited into a Vault 62 | access(all) event TokensDeposited(amount: UFix64, to: Address?) 63 | 64 | /// The interface that enforces the requirements for withdrawing 65 | /// tokens from the implementing type. 66 | /// 67 | /// It does not enforce requirements on `balance` here, 68 | /// because it leaves open the possibility of creating custom providers 69 | /// that do not necessarily need their own balance. 70 | /// 71 | access(all) resource interface Provider { 72 | 73 | /// Subtracts tokens from the owner's Vault 74 | /// and returns a Vault with the removed tokens. 75 | /// 76 | /// The function's access level is public, but this is not a problem 77 | /// because only the owner storing the resource in their account 78 | /// can initially call this function. 79 | /// 80 | /// The owner may grant other accounts access by creating a private 81 | /// capability that allows specific other users to access 82 | /// the provider resource through a reference. 83 | /// 84 | /// The owner may also grant all accounts access by creating a public 85 | /// capability that allows all users to access the provider 86 | /// resource through a reference. 87 | /// 88 | /// @param amount: The amount of tokens to be withdrawn from the vault 89 | /// @return The Vault resource containing the withdrawn funds 90 | /// 91 | access(Withdrawable) fun withdraw(amount: UFix64): @Vault { 92 | post { 93 | // `result` refers to the return value 94 | result.balance == amount: 95 | "Withdrawal amount must be the same as the balance of the withdrawn Vault" 96 | } 97 | } 98 | } 99 | 100 | /// The interface that enforces the requirements for depositing 101 | /// tokens into the implementing type. 102 | /// 103 | /// We do not include a condition that checks the balance because 104 | /// we want to give users the ability to make custom receivers that 105 | /// can do custom things with the tokens, like split them up and 106 | /// send them to different places. 107 | /// 108 | access(all) resource interface Receiver { 109 | 110 | /// Takes a Vault and deposits it into the implementing resource type 111 | /// 112 | /// @param from: The Vault resource containing the funds that will be deposited 113 | /// 114 | access(all) fun deposit(from: @Vault) 115 | 116 | /// Below is referenced from the FLIP #69 https://github.com/onflow/flips/blob/main/flips/20230206-fungible-token-vault-type-discovery.md 117 | /// 118 | /// Returns the dictionary of Vault types that the the receiver is able to accept in its `deposit` method 119 | /// this then it would return `{Type<@FlowToken.Vault>(): true}` and if any custom receiver 120 | /// uses the default implementation then it would return empty dictionary as its parent 121 | /// resource doesn't conform with the `FungibleToken.Vault` resource. 122 | /// 123 | /// Custom receiver implementations are expected to upgrade their contracts to add an implementation 124 | /// that supports this method because it is very valuable for various applications to have. 125 | /// 126 | /// @return dictionary of supported deposit vault types by the implementing resource. 127 | /// 128 | access(all) view fun getSupportedVaultTypes(): {Type: Bool} { 129 | // Below check is implemented to make sure that run-time type would 130 | // only get returned when the parent resource conforms with `FungibleToken.Vault`. 131 | if self.getType().isSubtype(of: Type<@FungibleToken.Vault>()) { 132 | return {self.getType(): true} 133 | } else { 134 | // Return an empty dictionary as the default value for resource who don't 135 | // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc. 136 | return {} 137 | } 138 | } 139 | } 140 | 141 | /// The interface that contains the `balance` field of the Vault 142 | /// and enforces that when new Vaults are created, the balance 143 | /// is initialized correctly. 144 | /// 145 | access(all) resource interface Balance { 146 | 147 | /// The total balance of a vault 148 | /// 149 | access(all) var balance: UFix64 150 | 151 | init(balance: UFix64) { 152 | post { 153 | self.balance == balance: 154 | "Balance must be initialized to the initial balance" 155 | } 156 | } 157 | 158 | /// Function that returns all the Metadata Views implemented by a Fungible Token 159 | /// 160 | /// @return An array of Types defining the implemented views. This value will be used by 161 | /// developers to know which parameter to pass to the resolveView() method. 162 | /// 163 | access(all) view fun getViews(): [Type] { 164 | return [] 165 | } 166 | 167 | /// Function that resolves a metadata view for this fungible token by type. 168 | /// 169 | /// @param view: The Type of the desired view. 170 | /// @return A structure representing the requested view. 171 | /// 172 | access(all) fun resolveView(_ view: Type): AnyStruct? { 173 | return nil 174 | } 175 | } 176 | 177 | /// The resource that contains the functions to send and receive tokens. 178 | /// The declaration of a concrete type in a contract interface means that 179 | /// every Fungible Token contract that implements the FungibleToken interface 180 | /// must define a concrete `Vault` resource that conforms to the `Provider`, `Receiver`, 181 | /// and `Balance` interfaces, and declares their required fields and functions 182 | /// 183 | access(all) resource Vault: Provider, Receiver, Balance { 184 | 185 | /// The total balance of the vault 186 | access(all) var balance: UFix64 187 | 188 | // The conforming type must declare an initializer 189 | // that allows providing the initial balance of the Vault 190 | // 191 | init(balance: UFix64) 192 | 193 | /// Subtracts `amount` from the Vault's balance 194 | /// and returns a new Vault with the subtracted balance 195 | /// 196 | /// @param amount: The amount of tokens to be withdrawn from the vault 197 | /// @return The Vault resource containing the withdrawn funds 198 | /// 199 | access(Withdrawable) fun withdraw(amount: UFix64): @Vault { 200 | pre { 201 | self.balance >= amount: 202 | "Amount withdrawn must be less than or equal than the balance of the Vault" 203 | } 204 | post { 205 | // use the special function `before` to get the value of the `balance` field 206 | // at the beginning of the function execution 207 | // 208 | self.balance == before(self.balance) - amount: 209 | "New Vault balance must be the difference of the previous balance and the withdrawn Vault" 210 | } 211 | } 212 | 213 | /// Takes a Vault and deposits it into the implementing resource type 214 | /// 215 | /// @param from: The Vault resource containing the funds that will be deposited 216 | /// 217 | access(all) fun deposit(from: @Vault) { 218 | // Assert that the concrete type of the deposited vault is the same 219 | // as the vault that is accepting the deposit 220 | pre { 221 | from.isInstance(self.getType()): 222 | "Cannot deposit an incompatible token type" 223 | } 224 | post { 225 | self.balance == before(self.balance) + before(from.balance): 226 | "New Vault balance must be the sum of the previous balance and the deposited Vault" 227 | } 228 | } 229 | } 230 | 231 | /// Allows any user to create a new Vault that has a zero balance 232 | /// 233 | /// @return The new Vault resource 234 | /// 235 | access(all) fun createEmptyVault(): @Vault { 236 | post { 237 | result.balance == 0.0: "The newly created Vault must have zero balance" 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /contracts/NonFungibleToken.cdc: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | ## The Flow Non-Fungible Token standard 4 | 5 | ## `NonFungibleToken` contract interface 6 | 7 | The interface that all Non-Fungible Token contracts could conform to. 8 | If a user wants to deploy a new NFT contract, their contract would need 9 | to implement the NonFungibleToken interface. 10 | 11 | Their contract would have to follow all the rules and naming 12 | that the interface specifies. 13 | 14 | ## `NFT` resource 15 | 16 | The core resource type that represents an NFT in the smart contract. 17 | 18 | ## `Collection` Resource 19 | 20 | The resource that stores a user's NFT collection. 21 | It includes a few functions to allow the owner to easily 22 | move tokens in and out of the collection. 23 | 24 | ## `Provider` and `Receiver` resource interfaces 25 | 26 | These interfaces declare functions with some pre and post conditions 27 | that require the Collection to follow certain naming and behavior standards. 28 | 29 | They are separate because it gives the user the ability to share a reference 30 | to their Collection that only exposes the fields and functions in one or more 31 | of the interfaces. It also gives users the ability to make custom resources 32 | that implement these interfaces to do various things with the tokens. 33 | 34 | By using resources and interfaces, users of NFT smart contracts can send 35 | and receive tokens peer-to-peer, without having to interact with a central ledger 36 | smart contract. 37 | 38 | To send an NFT to another user, a user would simply withdraw the NFT 39 | from their Collection, then call the deposit function on another user's 40 | Collection to complete the transfer. 41 | 42 | */ 43 | 44 | /// The main NFT contract interface. Other NFT contracts will 45 | /// import and implement this interface 46 | /// 47 | access(all) contract interface NonFungibleToken { 48 | 49 | // An entitlement for allowing the withdrawal of tokens from a Vault 50 | access(all) entitlement Withdrawable 51 | 52 | /// The total number of tokens of this type in existence 53 | access(all) var totalSupply: UInt64 54 | 55 | /// Event that emitted when the NFT contract is initialized 56 | /// 57 | access(all) event ContractInitialized() 58 | 59 | /// Event that is emitted when a token is withdrawn, 60 | /// indicating the owner of the collection that it was withdrawn from. 61 | /// 62 | /// If the collection is not in an account's storage, `from` will be `nil`. 63 | /// 64 | access(all) event Withdraw(id: UInt64, from: Address?) 65 | 66 | /// Event that emitted when a token is deposited to a collection. 67 | /// 68 | /// It indicates the owner of the collection that it was deposited to. 69 | /// 70 | access(all) event Deposit(id: UInt64, to: Address?) 71 | 72 | /// Interface that the NFTs have to conform to 73 | /// The metadata views methods are included here temporarily 74 | /// because enforcing the metadata interfaces in the standard 75 | /// would break many contracts in an upgrade. Those breaking changes 76 | /// are being saved for the stable cadence milestone 77 | /// 78 | access(all) resource interface INFT { 79 | /// The unique ID that each NFT has 80 | access(all) let id: UInt64 81 | 82 | /// Function that returns all the Metadata Views implemented by a Non Fungible Token 83 | /// 84 | /// @return An array of Types defining the implemented views. This value will be used by 85 | /// developers to know which parameter to pass to the resolveView() method. 86 | /// 87 | access(all) view fun getViews(): [Type] { 88 | return [] 89 | } 90 | 91 | /// Function that resolves a metadata view for this token. 92 | /// 93 | /// @param view: The Type of the desired view. 94 | /// @return A structure representing the requested view. 95 | /// 96 | access(all) fun resolveView(_ view: Type): AnyStruct? { 97 | return nil 98 | } 99 | } 100 | 101 | /// Requirement that all conforming NFT smart contracts have 102 | /// to define a resource called NFT that conforms to INFT 103 | /// 104 | access(all) resource NFT: INFT { 105 | access(all) let id: UInt64 106 | } 107 | 108 | /// Interface to mediate withdraws from the Collection 109 | /// 110 | access(all) resource interface Provider { 111 | /// Removes an NFT from the resource implementing it and moves it to the caller 112 | /// 113 | /// @param withdrawID: The ID of the NFT that will be removed 114 | /// @return The NFT resource removed from the implementing resource 115 | /// 116 | access(Withdrawable) fun withdraw(withdrawID: UInt64): @NFT { 117 | post { 118 | result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" 119 | } 120 | } 121 | } 122 | 123 | /// Interface to mediate deposits to the Collection 124 | /// 125 | access(all) resource interface Receiver { 126 | 127 | /// Adds an NFT to the resource implementing it 128 | /// 129 | /// @param token: The NFT resource that will be deposited 130 | /// 131 | access(all) fun deposit(token: @NFT) 132 | } 133 | 134 | /// Interface that an account would commonly 135 | /// publish for their collection 136 | /// 137 | access(all) resource interface CollectionPublic { 138 | access(all) fun deposit(token: @NFT) 139 | access(all) view fun getIDs(): [UInt64] 140 | access(all) view fun borrowNFT(id: UInt64): &NFT 141 | /// Safe way to borrow a reference to an NFT that does not panic 142 | /// 143 | /// @param id: The ID of the NFT that want to be borrowed 144 | /// @return An optional reference to the desired NFT, will be nil if the passed id does not exist 145 | /// 146 | access(all) view fun borrowNFTSafe(id: UInt64): &NFT? { 147 | post { 148 | result == nil || result!.id == id: "The returned reference's ID does not match the requested ID" 149 | } 150 | return nil 151 | } 152 | } 153 | 154 | /// Requirement for the concrete resource type 155 | /// to be declared in the implementing contract 156 | /// 157 | access(all) resource Collection: Provider, Receiver, CollectionPublic { 158 | 159 | /// Dictionary to hold the NFTs in the Collection 160 | access(all) var ownedNFTs: @{UInt64: NFT} 161 | 162 | /// Removes an NFT from the collection and moves it to the caller 163 | /// 164 | /// @param withdrawID: The ID of the NFT that will be withdrawn 165 | /// @return The resource containing the desired NFT 166 | /// 167 | access(Withdrawable) fun withdraw(withdrawID: UInt64): @NFT 168 | 169 | /// Takes a NFT and adds it to the collections dictionary 170 | /// and adds the ID to the ID array 171 | /// 172 | /// @param token: An NFT resource 173 | /// 174 | access(all) fun deposit(token: @NFT) 175 | 176 | /// Returns an array of the IDs that are in the collection 177 | /// 178 | /// @return An array containing all the IDs on the collection 179 | /// 180 | access(all) view fun getIDs(): [UInt64] 181 | 182 | /// Returns a borrowed reference to an NFT in the collection 183 | /// so that the caller can read data and call methods from it 184 | /// 185 | /// @param id: The ID of the NFT that want to be borrowed 186 | /// @return A reference to the NFT 187 | /// 188 | access(all) view fun borrowNFT(id: UInt64): &NFT { 189 | pre { 190 | self.ownedNFTs[id] != nil: "NFT does not exist in the collection!" 191 | } 192 | } 193 | } 194 | 195 | /// Creates an empty Collection and returns it to the caller so that they can own NFTs 196 | /// 197 | /// @return A new Collection resource 198 | /// 199 | access(all) fun createEmptyCollection(): @Collection { 200 | post { 201 | result.getIDs().length == 0: "The created collection must be empty!" 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /contracts/ViewResolver.cdc: -------------------------------------------------------------------------------- 1 | // Taken from the NFT Metadata standard, this contract exposes an interface to let 2 | // anyone borrow a contract and resolve views on it. 3 | // 4 | // This will allow you to obtain information about a contract without necessarily knowing anything about it. 5 | // All you need is its address and name and you're good to go! 6 | access(all) contract interface ViewResolver { 7 | /// Function that returns all the Metadata Views implemented by the resolving contract 8 | /// 9 | /// @return An array of Types defining the implemented views. This value will be used by 10 | /// developers to know which parameter to pass to the resolveView() method. 11 | /// 12 | access(all) view fun getViews(): [Type] { 13 | return [] 14 | } 15 | 16 | /// Function that resolves a metadata view for this token. 17 | /// 18 | /// @param view: The Type of the desired view. 19 | /// @return A structure representing the requested view. 20 | /// 21 | access(all) fun resolveView(_ view: Type): AnyStruct? { 22 | return nil 23 | } 24 | 25 | /// Provides access to a set of metadata views. A struct or 26 | /// resource (e.g. an NFT) can implement this interface to provide access to 27 | /// the views that it supports. 28 | /// 29 | access(all) resource interface Resolver { 30 | access(all) view fun getViews(): [Type] { 31 | return [] 32 | } 33 | access(all) fun resolveView(_ view: Type): AnyStruct? { 34 | return nil 35 | } 36 | } 37 | 38 | /// A group of view resolvers indexed by ID. 39 | /// 40 | access(all) resource interface ResolverCollection { 41 | access(all) view fun borrowViewResolver(id: UInt64): &{Resolver}? { 42 | pre { true: "dummy" } 43 | } 44 | access(all) view fun getIDs(): [UInt64] { 45 | pre { true: "dummy" } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /doc_test.go: -------------------------------------------------------------------------------- 1 | package overflow_test 2 | 3 | // importing overflow using "." will yield a cleaner DSL 4 | import ( 5 | . "github.com/bjartek/overflow/v2" 6 | ) 7 | 8 | var docOptions = WithGlobalPrintOptions(WithoutId()) 9 | 10 | func Example() { 11 | // in order to start overflow use the Overflow function 12 | // it can be customized with lots of OverflowOption 13 | Overflow() 14 | //Output: 15 | //🧑 Created account: emulator-first with address: 179b6b1cb6755e31 with flow: 10.00 16 | //🧑 Created account: emulator-second with address: f3fcd2c1a78f5eee with flow: 10.00 17 | //📜 deploy contracts Debug 18 | } 19 | 20 | func ExampleOverflowState_Tx() { 21 | o := Overflow(docOptions) 22 | 23 | // start the Tx DSL with the name of the transactions file, by default this 24 | // is in the `transactions` folder in your root dit 25 | o.Tx("arguments", 26 | // Customize the Transaction by sending in more InteractionOptions, 27 | // at minimum you need to set Signer and Args if any 28 | WithSigner("first"), 29 | // Arguments are always passed by name in the DSL builder, order does not matter 30 | WithArg("test", "overflow ftw!"), 31 | ) 32 | //Output: 33 | //🧑 Created account: emulator-first with address: 179b6b1cb6755e31 with flow: 10.00 34 | //🧑 Created account: emulator-second with address: f3fcd2c1a78f5eee with flow: 10.00 35 | //📜 deploy contracts Debug 36 | //👌 Tx:arguments fee:0.00001000 gas:8 37 | // 38 | } 39 | 40 | func ExampleOverflowState_Tx_inline() { 41 | o := Overflow(docOptions) 42 | 43 | // The Tx dsl can also contain an inline transaction 44 | o.Tx(` 45 | import Debug from "../contracts/Debug.cdc" 46 | transaction(message:String) { 47 | prepare(acct: &Account) { 48 | Debug.log(message) 49 | } 50 | }`, 51 | WithSigner("first"), 52 | WithArg("message", "overflow ftw!"), 53 | ) 54 | //Output: 55 | //🧑 Created account: emulator-first with address: 179b6b1cb6755e31 with flow: 10.00 56 | //🧑 Created account: emulator-second with address: f3fcd2c1a78f5eee with flow: 10.00 57 | //📜 deploy contracts Debug 58 | //👌 Tx: fee:0.00001000 gas:14 59 | //=== Events === 60 | //A.f8d6e0586b0a20c7.Debug.Log 61 | // msg -> overflow ftw! 62 | } 63 | 64 | func ExampleOverflowState_Tx_multisign() { 65 | o := Overflow(docOptions) 66 | 67 | // The Tx dsl supports multiple signers, note that the mainSigner is the last account 68 | o.Tx(` 69 | import Debug from "../contracts/Debug.cdc" 70 | transaction { 71 | prepare(acct: &Account, acct2: &Account) { 72 | Debug.log("acct:".concat(acct.address.toString())) 73 | Debug.log("acct2:".concat(acct2.address.toString())) 74 | } 75 | }`, 76 | WithSigner("first"), 77 | WithPayloadSigner("second"), 78 | ) 79 | 80 | //Output: 81 | //🧑 Created account: emulator-first with address: 179b6b1cb6755e31 with flow: 10.00 82 | //🧑 Created account: emulator-second with address: f3fcd2c1a78f5eee with flow: 10.00 83 | //📜 deploy contracts Debug 84 | //👌 Tx: fee:0.00001000 gas:14 85 | //=== Events === 86 | //A.f8d6e0586b0a20c7.Debug.Log 87 | // msg -> acct:0xf3fcd2c1a78f5eee 88 | //A.f8d6e0586b0a20c7.Debug.Log 89 | // msg -> acct2:0x179b6b1cb6755e31 90 | } 91 | 92 | func ExampleOverflowState_Script() { 93 | o := Overflow(docOptions) 94 | 95 | // the other major interaction you can run on Flow is a script, it uses the script DSL. 96 | // Start it by specifying the script name from `scripts` folder 97 | o.Script("test", 98 | // the test script requires an address as arguments, Overflow is smart enough that it 99 | // sees this and knows that there is an account for the emulator network called 100 | // `emulator-first` so it will insert that address as the argument. 101 | // If you change the network to testnet/mainnet later and name your stakholders 102 | // accordingly it will just work 103 | WithArg("account", "first"), 104 | ) 105 | //Output: 106 | // 107 | //🧑 Created account: emulator-first with address: 179b6b1cb6755e31 with flow: 10.00 108 | //🧑 Created account: emulator-second with address: f3fcd2c1a78f5eee with flow: 10.00 109 | //📜 deploy contracts Debug 110 | //⭐ Script test run result:"0x179b6b1cb6755e31" 111 | } 112 | 113 | func ExampleOverflowState_Script_inline() { 114 | o := Overflow(docOptions) 115 | 116 | // Script can be run inline 117 | o.Script(` 118 | access(all) fun main(account: Address): String { 119 | return getAccount(account).address.toString() 120 | }`, 121 | WithArg("account", "first"), 122 | WithName("get_address"), 123 | ) 124 | //Output: 125 | //🧑 Created account: emulator-first with address: 179b6b1cb6755e31 with flow: 10.00 126 | //🧑 Created account: emulator-second with address: f3fcd2c1a78f5eee with flow: 10.00 127 | //📜 deploy contracts Debug 128 | //⭐ Script get_address run result:"0x179b6b1cb6755e31" 129 | } 130 | -------------------------------------------------------------------------------- /event_filter_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hexops/autogold" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFilterOverflowEvents(t *testing.T) { 11 | events := OverflowEvents{ 12 | "A.123.Test.Deposit": []OverflowEvent{ 13 | {Fields: map[string]interface{}{ 14 | "id": 1, 15 | "string": "string", 16 | }}, 17 | }, 18 | } 19 | 20 | t.Run("Filter out all events should yield empty", func(t *testing.T) { 21 | filter := OverflowEventFilter{ 22 | "Deposit": []string{"id", "string"}, 23 | } 24 | filtered := events.FilterEvents(filter) 25 | assert.Empty(t, filtered) 26 | }) 27 | t.Run("Filter out single field", func(t *testing.T) { 28 | filter := OverflowEventFilter{ 29 | "Deposit": []string{"id"}, 30 | } 31 | filtered := events.FilterEvents(filter) 32 | want := autogold.Want("string", OverflowEvents{"A.123.Test.Deposit": []OverflowEvent{{Fields: map[string]interface{}{ 33 | "string": "string", 34 | }}}}) 35 | want.Equal(t, filtered) 36 | }) 37 | 38 | t.Run("Filter fees", func(t *testing.T) { 39 | eventsWithFees := OverflowEvents{ 40 | "A.f919ee77447b7497.FlowFees.FeesDeducted": []OverflowEvent{ 41 | {Fields: map[string]interface{}{ 42 | "amount": 0.00000918, 43 | "inclusionEffort": 1.00000000, 44 | "executionEffort": 0.00000164, 45 | }}, 46 | }, 47 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{ 48 | {Fields: map[string]interface{}{ 49 | "amount": 0.00000918, 50 | "from": "0x55ad22f01ef568a1", 51 | }}, 52 | }, 53 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{ 54 | {Fields: map[string]interface{}{ 55 | "amount": 0.00000918, 56 | "to": "0xf919ee77447b7497", 57 | }}, {Fields: map[string]interface{}{ 58 | "amount": 1.00000000, 59 | "to": "0xf919ee77447b7497", 60 | }}, 61 | }, 62 | } 63 | filtered, _ := eventsWithFees.FilterFees(0.00000918, "0x55ad22f01ef568a1") 64 | want := autogold.Want("fees filtered", OverflowEvents{"A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{ 65 | {Fields: map[string]interface{}{ 66 | "amount": 1, 67 | "to": "0xf919ee77447b7497", 68 | }}, 69 | }}) 70 | want.Equal(t, filtered) 71 | }) 72 | 73 | t.Run("Filter fees with other transfer", func(t *testing.T) { 74 | eventsWithFees := OverflowEvents{ 75 | "A.f919ee77447b7497.FlowFees.FeesDeducted": []OverflowEvent{{Fields: map[string]interface{}{ 76 | "amount": 0.00000918, 77 | "inclusionEffort": 1.00000000, 78 | "executionEffort": 0.00000164, 79 | }}}, 80 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{{Fields: map[string]interface{}{ 81 | "amount": 0.00000918, 82 | "from": "0x55ad22f01ef568a1", 83 | }}, {Fields: map[string]interface{}{ 84 | "amount": 1.00000000, 85 | "from": "0x55ad22f01ef568a1", 86 | }}}, 87 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{{Fields: map[string]interface{}{ 88 | "amount": 0.00000918, 89 | "to": "0xf919ee77447b7497", 90 | }}, {Fields: map[string]interface{}{ 91 | "amount": 1.00000000, 92 | "to": "0xf919ee77447b7497", 93 | }}}, 94 | } 95 | filtered, _ := eventsWithFees.FilterFees(0.00000918, "0x55ad22f01ef568a1") 96 | want := autogold.Want("fees filtered with transfer", OverflowEvents{ 97 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{ 98 | {Fields: map[string]interface{}{ 99 | "amount": 1, 100 | "to": "0xf919ee77447b7497", 101 | }}, 102 | }, 103 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{{Fields: map[string]interface{}{ 104 | "amount": 1, 105 | "from": "0x55ad22f01ef568a1", 106 | }}}, 107 | }) 108 | want.Equal(t, filtered) 109 | }) 110 | 111 | t.Run("Filter empty deposit withdraw", func(t *testing.T) { 112 | eventsWithFees := OverflowEvents{ 113 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{{Fields: map[string]interface{}{ 114 | "amount": 0.00000918, 115 | "from": nil, 116 | }}}, 117 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{{Fields: map[string]interface{}{ 118 | "amount": 0.00000918, 119 | "to": nil, 120 | }}, {Fields: map[string]interface{}{ 121 | "amount": 1.00000000, 122 | "to": "0xf919ee77447b7497", 123 | }}}, 124 | } 125 | filtered := eventsWithFees.FilterTempWithdrawDeposit() 126 | want := autogold.Want("fees empty deposit withdraw", OverflowEvents{"A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{ 127 | {Fields: map[string]interface{}{ 128 | "amount": 1, 129 | "to": "0xf919ee77447b7497", 130 | }}, 131 | }}) 132 | want.Equal(t, filtered) 133 | }) 134 | 135 | t.Run("Filter non-empty deposit withdraw", func(t *testing.T) { 136 | eventsWithFees := OverflowEvents{ 137 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{{Fields: map[string]interface{}{ 138 | "amount": 0.00000918, 139 | "from": "0x01", 140 | }}}, 141 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{{Fields: map[string]interface{}{ 142 | "amount": 0.00000918, 143 | "to": "0x02", 144 | }}, {Fields: map[string]interface{}{ 145 | "amount": 1.00000000, 146 | "to": "0xf919ee77447b7497", 147 | }}}, 148 | } 149 | filtered := eventsWithFees.FilterTempWithdrawDeposit() 150 | want := autogold.Want("fees non-empty deposit withdraw", OverflowEvents{ 151 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{ 152 | {Fields: map[string]interface{}{ 153 | "amount": 9.18e-06, 154 | "to": "0x02", 155 | }}, 156 | {Fields: map[string]interface{}{ 157 | "amount": 1, 158 | "to": "0xf919ee77447b7497", 159 | }}, 160 | }, 161 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{{Fields: map[string]interface{}{ 162 | "amount": 9.18e-06, 163 | "from": "0x01", 164 | }}}, 165 | }) 166 | want.Equal(t, filtered) 167 | }) 168 | 169 | t.Run("Filter all empty deposit withdraw", func(t *testing.T) { 170 | eventsWithFees := OverflowEvents{ 171 | "A.1654653399040a61.FlowToken.TokensWithdrawn": []OverflowEvent{{Fields: map[string]interface{}{ 172 | "amount": 0.00000918, 173 | "from": nil, 174 | }}}, 175 | "A.1654653399040a61.FlowToken.TokensDeposited": []OverflowEvent{{Fields: map[string]interface{}{ 176 | "amount": 0.00000918, 177 | "to": nil, 178 | }}}, 179 | } 180 | filtered := eventsWithFees.FilterTempWithdrawDeposit() 181 | want := autogold.Want("filter all empty deposit withdraw", OverflowEvents{}) 182 | want.Equal(t, filtered) 183 | }) 184 | } 185 | -------------------------------------------------------------------------------- /example/example_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestExample(t *testing.T) { 12 | // in order to run a test that will reset to known state in setup_test use the `ot.Run(t,...)``method instead of `t.Run(...)` 13 | ot.Run(t, "Example test", func(t *testing.T) { 14 | block, err := ot.O.GetLatestBlock(context.Background()) 15 | require.NoError(t, err) 16 | assert.Equal(t, 4, int(block.Height)) 17 | 18 | ot.O.MintFlowTokens("first", 1000.0) 19 | require.NoError(t, ot.O.Error) 20 | 21 | block, err = ot.O.GetLatestBlock(context.Background()) 22 | require.NoError(t, err) 23 | assert.Equal(t, 5, int(block.Height)) 24 | }) 25 | 26 | ot.Run(t, "Example test 2", func(t *testing.T) { 27 | block, err := ot.O.GetLatestBlock(context.Background()) 28 | require.NoError(t, err) 29 | assert.Equal(t, 4, int(block.Height)) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /example/flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "default": { 4 | "port": 3569, 5 | "serviceAccount": "emulator-account" 6 | } 7 | }, 8 | "contracts": { 9 | "Debug": "./../contracts/Debug.cdc", 10 | "FlowToken": { 11 | "source": "./../contracts/FlowToken.cdc", 12 | "aliases": { 13 | "emulator": "0x0ae53cb6e3f42a79", 14 | "testnet": "0x7e60df042a9c0868", 15 | "mainnet": "0x1654653399040a61" 16 | } 17 | }, 18 | "FungibleToken": { 19 | "source": "./../contracts/FungibleToken.cdc", 20 | "aliases": { 21 | "emulator": "0xee82856bf20e2aa6", 22 | "testnet": "0x9a0766d93b6608b7", 23 | "mainnet": "0xf233dcee88fe0abe" 24 | } 25 | }, 26 | "NonFungibleToken": { 27 | "source": "./../contracts/NonFungibleToken.cdc", 28 | "aliases": { 29 | "testnet": "0x631e88ae7f1d7c20", 30 | "mainnet": "0x1d7e57aa55817448", 31 | "emulator": "0xf8d6e0586b0a20c7" 32 | } 33 | }, 34 | "MetadataViews": { 35 | "source": "./../contracts/MetadataViews.cdc", 36 | "aliases": { 37 | "testnet": "0x631e88ae7f1d7c20", 38 | "mainnet": "0x1d7e57aa55817448", 39 | "emulator": "0xf8d6e0586b0a20c7" 40 | } 41 | }, 42 | "ViewResolver": { 43 | "source": "./contracts/ViewResolver.cdc", 44 | "aliases": { 45 | "testnet": "0x631e88ae7f1d7c20", 46 | "mainnet": "0x1d7e57aa55817448", 47 | "emulator": "0xf8d6e0586b0a20c7" 48 | } 49 | } 50 | }, 51 | "networks": { 52 | "emulator": "127.0.0.1:3569", 53 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 54 | "testnet": "access.devnet.nodes.onflow.org:9000" 55 | }, 56 | "accounts": { 57 | "emulator-account": { 58 | "address": "f8d6e0586b0a20c7", 59 | "key": "dc0097a6b58533e56af78c955e7b0c0f386b5f44f22b75c390beab7fcb1af13f" 60 | }, 61 | "emulator-first": { 62 | "address": "01cf0e2f2f715450", 63 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 64 | }, 65 | "emulator-second": { 66 | "address": "179b6b1cb6755e31", 67 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 68 | }, 69 | "emulator-3": { 70 | "address": "f3fcd2c1a78f5eee", 71 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 72 | }, 73 | "emulator-4": { 74 | "address": "e03daebed8ca0615", 75 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 76 | }, 77 | "emulator-5": { 78 | "address": "045a1763c93006ca", 79 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 80 | } 81 | }, 82 | "deployments": { 83 | "emulator": { 84 | "emulator-account": [ 85 | "Debug" 86 | ], 87 | "emulator-first": [], 88 | "emulator-second": [] 89 | }, 90 | "testnet": { 91 | "emulator-first": [ 92 | "Debug" 93 | ] 94 | }, 95 | "mainnet": { 96 | "emulator-second": [ 97 | "Debug" 98 | ] 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /example/setup_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/bjartek/overflow/v2" 8 | ) 9 | 10 | // we set the shared overflow test struct that will reset to known setup state after each test 11 | var ot *overflow.OverflowTest 12 | 13 | func TestMain(m *testing.M) { 14 | var err error 15 | ot, err = overflow.SetupTest([]overflow.OverflowOption{overflow.WithCoverageReport()}, func(o *overflow.OverflowState) error { 16 | o.MintFlowTokens("first", 1000.0) 17 | return nil 18 | }) 19 | if err != nil { 20 | panic(err) 21 | } 22 | code := m.Run() 23 | ot.Teardown() 24 | os.Exit(code) 25 | } 26 | -------------------------------------------------------------------------------- /flix.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | // run a script with the given code/filanem an options 4 | func (o *OverflowState) FlixScript(filename string, opts ...OverflowInteractionOption) *OverflowScriptResult { 5 | interaction := o.BuildInteraction(filename, "flix", opts...) 6 | 7 | result := interaction.runScript() 8 | 9 | if interaction.PrintOptions != nil && !interaction.NoLog { 10 | result.Print() 11 | } 12 | if o.StopOnError && result.Err != nil { 13 | result.PrintArguments(nil) 14 | panic(result.Err) 15 | } 16 | return result 17 | } 18 | 19 | // compose interactionOptions into a new Script function 20 | func (o *OverflowState) FlixScriptFN(outerOpts ...OverflowInteractionOption) OverflowScriptFunction { 21 | return func(filename string, opts ...OverflowInteractionOption) *OverflowScriptResult { 22 | outerOpts = append(outerOpts, opts...) 23 | return o.FlixScript(filename, outerOpts...) 24 | } 25 | } 26 | 27 | // compose fileName and interactionOptions into a new Script function 28 | func (o *OverflowState) FlixScriptFileNameFN(filename string, outerOpts ...OverflowInteractionOption) OverflowScriptOptsFunction { 29 | return func(opts ...OverflowInteractionOption) *OverflowScriptResult { 30 | outerOpts = append(outerOpts, opts...) 31 | return o.FlixScript(filename, outerOpts...) 32 | } 33 | } 34 | 35 | // If you store this in a struct and add arguments to it it will not reset between calls 36 | func (o *OverflowState) FlixTxFN(outerOpts ...OverflowInteractionOption) OverflowTransactionFunction { 37 | return func(filename string, opts ...OverflowInteractionOption) *OverflowResult { 38 | // outer has to be first since we need to be able to overwrite 39 | opts = append(outerOpts, opts...) 40 | return o.FlixTx(filename, opts...) 41 | } 42 | } 43 | 44 | func (o *OverflowState) FlixTxFileNameFN(filename string, outerOpts ...OverflowInteractionOption) OverflowTransactionOptsFunction { 45 | return func(opts ...OverflowInteractionOption) *OverflowResult { 46 | // outer has to be first since we need to be able to overwrite 47 | opts = append(outerOpts, opts...) 48 | return o.FlixTx(filename, opts...) 49 | } 50 | } 51 | 52 | // run a flix transaction with the given code/filanem an options 53 | func (o *OverflowState) FlixTx(filename string, opts ...OverflowInteractionOption) *OverflowResult { 54 | interaction := o.BuildInteraction(filename, "flix", opts...) 55 | 56 | return o.sendTx(interaction) 57 | } 58 | -------------------------------------------------------------------------------- /flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "default": { 4 | "port": 3569, 5 | "serviceAccount": "emulator-account" 6 | } 7 | }, 8 | "contracts": { 9 | "Debug": "./contracts/Debug.cdc", 10 | "FlowToken": { 11 | "source": "./contracts/FlowToken.cdc", 12 | "aliases": { 13 | "emulator": "0x0ae53cb6e3f42a79", 14 | "testnet": "0x7e60df042a9c0868", 15 | "mainnet": "0x1654653399040a61" 16 | } 17 | }, 18 | "FungibleToken": { 19 | "source": "./contracts/FungibleToken.cdc", 20 | "aliases": { 21 | "emulator": "0xee82856bf20e2aa6", 22 | "testnet": "0x9a0766d93b6608b7", 23 | "mainnet": "0xf233dcee88fe0abe" 24 | } 25 | }, 26 | "NonFungibleToken": { 27 | "source": "./contracts/NonFungibleToken.cdc", 28 | "aliases": { 29 | "testnet": "0x631e88ae7f1d7c20", 30 | "mainnet": "0x1d7e57aa55817448", 31 | "emulator": "0xf8d6e0586b0a20c7" 32 | } 33 | }, 34 | "MetadataViews": { 35 | "source": "./contracts/MetadataViews.cdc", 36 | "aliases": { 37 | "testnet": "0x631e88ae7f1d7c20", 38 | "mainnet": "0x1d7e57aa55817448", 39 | "emulator": "0xf8d6e0586b0a20c7" 40 | } 41 | }, 42 | "ViewResolver": { 43 | "source": "./contracts/ViewResolver.cdc", 44 | "aliases": { 45 | "testnet": "0x631e88ae7f1d7c20", 46 | "mainnet": "0x1d7e57aa55817448", 47 | "emulator": "0xf8d6e0586b0a20c7" 48 | } 49 | } 50 | }, 51 | "networks": { 52 | "emulator": "127.0.0.1:3569", 53 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 54 | "testnet": "access.devnet.nodes.onflow.org:9000" 55 | }, 56 | "accounts": { 57 | "emulator-account": { 58 | "address": "f8d6e0586b0a20c7", 59 | "key": "dc0097a6b58533e56af78c955e7b0c0f386b5f44f22b75c390beab7fcb1af13f" 60 | }, 61 | "emulator-first": { 62 | "address": "0x179b6b1cb6755e31", 63 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 64 | }, 65 | "emulator-second": { 66 | "address": "0xf3fcd2c1a78f5eee", 67 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 68 | }, 69 | "emulator-3": { 70 | "address": "0xe03daebed8ca0615", 71 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 72 | }, 73 | "emulator-4": { 74 | "address": "0x045a1763c93006ca", 75 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 76 | } 77 | }, 78 | "deployments": { 79 | "emulator": { 80 | "emulator-account": [ 81 | "Debug" 82 | ], 83 | "emulator-first": [], 84 | "emulator-second": [] 85 | }, 86 | "testnet": { 87 | "emulator-first": [ 88 | "Debug" 89 | ] 90 | }, 91 | "mainnet": { 92 | "emulator-second": [ 93 | "Debug" 94 | ] 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | func (o *OverflowState) GenerateStub(network, filePath string, standalone bool) (string, error) { 10 | solution, err := o.ParseAll() 11 | if err != nil { 12 | return "", err 13 | } 14 | 15 | interactionName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)) 16 | var interaction *OverflowDeclarationInfo 17 | var commandName string 18 | if strings.HasPrefix(filePath, o.TransactionBasePath) || strings.HasPrefix("./"+filePath, o.TransactionBasePath) { 19 | interaction = solution.Transactions[interactionName] 20 | commandName = "Tx" 21 | } else { 22 | interaction = solution.Scripts[interactionName] 23 | commandName = "Script" 24 | } 25 | if interaction == nil { 26 | return "", fmt.Errorf("could not find interaction of type %s with name %s", commandName, interaction) 27 | } 28 | lines := []string{ 29 | fmt.Sprintf(` o.%s("%s",`, commandName, interactionName), 30 | } 31 | 32 | if commandName == "Tx" { 33 | lines = append(lines, " WithSigner(\"<>\"),") 34 | } 35 | for name, value := range interaction.Parameters { 36 | lines = append(lines, fmt.Sprintf(" WithArg(\"%s\", <>), //%s", name, value)) 37 | } 38 | var stub string 39 | if len(lines) > 1 { 40 | lines = append(lines, " )") 41 | stub = strings.Join(lines, "\n") 42 | } else { 43 | stub = strings.ReplaceAll(lines[0], ",", ")") 44 | } 45 | 46 | if !standalone { 47 | return stub, nil 48 | } 49 | 50 | return fmt.Sprintf(`package main 51 | 52 | import ( 53 | . "github.com/bjartek/overflow/v2" 54 | ) 55 | 56 | func main() { 57 | o := Overflow(WithNetwork("%s"), WithPrintResults()) 58 | %s 59 | }`, network, stub), nil 60 | } 61 | -------------------------------------------------------------------------------- /generate_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | /* 11 | Tests must be in the same folder as flow.json with contracts and transactions/scripts in subdirectories in order for the path resolver to work correctly 12 | */ 13 | func TestGenerate(t *testing.T) { 14 | o, err := OverflowTesting() 15 | require.NoError(t, err) 16 | t.Run("script", func(t *testing.T) { 17 | stub, err := o.GenerateStub("emulator", "scripts/test.cdc", false) 18 | assert.NoError(t, err) 19 | assert.Equal(t, " o.Script(\"test\",\n WithArg(\"account\", <>), //Address\n )", stub) 20 | }) 21 | t.Run("script with no arg", func(t *testing.T) { 22 | stub, err := o.GenerateStub("emulator", "scripts/type.cdc", false) 23 | assert.NoError(t, err) 24 | assert.Equal(t, " o.Script(\"type\")", stub) 25 | }) 26 | 27 | t.Run("transaction", func(t *testing.T) { 28 | stub, err := o.GenerateStub("emulator", "transactions/arguments.cdc", false) 29 | assert.NoError(t, err) 30 | assert.Equal(t, " o.Tx(\"arguments\",\n WithSigner(\"<>\"),\n WithArg(\"test\", <>), //String\n )", stub) 31 | }) 32 | 33 | t.Run("transaction with no args", func(t *testing.T) { 34 | stub, err := o.GenerateStub("emulator", "transactions/create_nft_collection.cdc", false) 35 | assert.NoError(t, err) 36 | assert.Equal(t, " o.Tx(\"create_nft_collection\",\n WithSigner(\"<>\"),\n )", stub) 37 | }) 38 | 39 | t.Run("transaction standalone", func(t *testing.T) { 40 | stub, err := o.GenerateStub("emulator", "transactions/arguments.cdc", true) 41 | assert.NoError(t, err) 42 | assert.Equal(t, `package main 43 | 44 | import ( 45 | . "github.com/bjartek/overflow/v2" 46 | ) 47 | 48 | func main() { 49 | o := Overflow(WithNetwork("emulator"), WithPrintResults()) 50 | o.Tx("arguments", 51 | WithSigner("<>"), 52 | WithArg("test", <>), //String 53 | ) 54 | }`, stub) 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /identifier_integration_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestIdentifierIntegration(t *testing.T) { 11 | o, err := OverflowTesting() 12 | require.NoError(t, err) 13 | 14 | result, err := o.QualifiedIdentifier("MetadataViews", "Display") 15 | assert.NoError(t, err) 16 | assert.Equal(t, "A.f8d6e0586b0a20c7.MetadataViews.Display", result) 17 | } 18 | 19 | func TestIdentifierTestnet(t *testing.T) { 20 | o := Overflow(WithNetwork("testnet")) 21 | require.NoError(t, o.Error) 22 | 23 | result, err := o.QualifiedIdentifier("MetadataViews", "Display") 24 | assert.NoError(t, err) 25 | assert.Equal(t, "A.631e88ae7f1d7c20.MetadataViews.Display", result) 26 | } 27 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // OverflowEmulatorLogMessage a log message from the logrus implementation used in the flow emulator 8 | type OverflowEmulatorLogMessage struct { 9 | Fields map[string]interface{} 10 | Level string 11 | Msg string 12 | ComputationUsed int 13 | } 14 | 15 | func (me OverflowEmulatorLogMessage) String() string { 16 | fields := "" 17 | if len(me.Fields) > 0 { 18 | for key, value := range me.Fields { 19 | fields = fmt.Sprintf("%s %s=%v", fields, key, value) 20 | } 21 | } 22 | 23 | return fmt.Sprintf("%s - %s%s", me.Level, me.Msg, fields) 24 | } 25 | -------------------------------------------------------------------------------- /metadata.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | type MetadataViews_HTTPFile struct { 4 | Url string 5 | } 6 | 7 | type MetadataViews_IPFSFile struct { 8 | Path *string 9 | Cid string 10 | } 11 | 12 | type MetadataViews_Display_IPFS struct { 13 | Thumbnail MetadataViews_IPFSFile 14 | Name string 15 | Description string 16 | } 17 | type MetadataViews_Display_Http struct { 18 | Name string 19 | Description string 20 | Thumbnail MetadataViews_HTTPFile 21 | } 22 | 23 | type MetadataViews_Edition struct { 24 | Name *string 25 | Max *uint64 26 | Number uint64 27 | } 28 | 29 | type MetadataViews_Editions struct { 30 | Editions []MetadataViews_Edition `cadence:"infoList"` 31 | } 32 | 33 | type MetadataViews_Serial struct { 34 | Number uint64 35 | } 36 | 37 | type MetadataViews_Media_IPFS struct { 38 | File MetadataViews_IPFSFile 39 | MediaType string `cadence:"mediaType"` 40 | } 41 | 42 | type MetadataViews_Media_HTTP struct { 43 | File MetadataViews_HTTPFile 44 | MediaType string `cadence:"mediaType"` 45 | } 46 | type MetadtaViews_Licensce struct { 47 | Spdx string `cadence:"spdxIdentifier"` 48 | } 49 | 50 | type MetadataViews_ExternalURL struct { 51 | Url string 52 | } 53 | 54 | type MetadataViews_Rarity struct { 55 | Score *string 56 | Max *uint64 57 | Description *string 58 | } 59 | 60 | type MetadataViews_Trait struct { 61 | Value interface{} 62 | Rarity *MetadataViews_Rarity 63 | Name string 64 | DisplayType string `cadence:"displayType"` 65 | } 66 | 67 | type MetadataViews_Traits struct { 68 | Traits []MetadataViews_Trait 69 | } 70 | -------------------------------------------------------------------------------- /meter.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import "github.com/onflow/cadence/common" 4 | 5 | // a type representing a meter that contains information about the inner workings of an interaction, only available on local emulator 6 | type OverflowMeter struct { 7 | ComputationIntensities OverflowMeteredComputationIntensities `json:"computationIntensities"` 8 | MemoryIntensities OverflowMeteredMemoryIntensities `json:"memoryIntensities"` 9 | LedgerInteractionUsed int `json:"ledgerInteractionUsed"` 10 | ComputationUsed int `json:"computationUsed"` 11 | MemoryUsed int `json:"memoryUsed"` 12 | } 13 | 14 | // get the number of functions invocations 15 | func (m OverflowMeter) FunctionInvocations() int { 16 | return int(m.ComputationIntensities[common.ComputationKindFunctionInvocation]) 17 | } 18 | 19 | // get the number of loops 20 | func (m OverflowMeter) Loops() int { 21 | return int(m.ComputationIntensities[common.ComputationKindLoop]) 22 | } 23 | 24 | // get the number of statements 25 | func (m OverflowMeter) Statements() int { 26 | return int(m.ComputationIntensities[common.ComputationKindStatement]) 27 | } 28 | 29 | // type collecting computatationIntensities 30 | type OverflowMeteredComputationIntensities map[common.ComputationKind]uint 31 | 32 | // type collecting memoryIntensities 33 | type OverflowMeteredMemoryIntensities map[common.MemoryKind]uint 34 | -------------------------------------------------------------------------------- /npm_module.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/onflow/cadence/ast" 8 | "github.com/onflow/cadence/parser" 9 | "github.com/onflow/cadence/sema" 10 | ) 11 | 12 | // NPM Module 13 | // 14 | // Overflow has support for generating an NPM module from a set of interactions 15 | 16 | // a type representing the raw solutions that contains all transactions, scripts, networks and warnings of any 17 | type OverflowSolution struct { 18 | // all transactions with name and what paremters they have 19 | Transactions map[string]*OverflowDeclarationInfo `json:"transactions"` 20 | 21 | // all scripts with name and parameter they have 22 | Scripts map[string]*OverflowDeclarationInfo `json:"scripts"` 23 | 24 | // all networks with associated scripts/tranasctions/contracts preresolved 25 | Networks map[string]*OverflowSolutionNetwork `json:"networks"` 26 | 27 | // warnings accumulated during parsing 28 | Warnings []string `json:"warnings"` 29 | } 30 | 31 | type OverflowAuthorizers []OverflowAuthorizer 32 | 33 | type OverflowAuthorizer struct { 34 | Name string 35 | Entitlements []string 36 | } 37 | 38 | // a type containing information about parameter types and orders 39 | type OverflowDeclarationInfo struct { 40 | Parameters map[string]string `json:"parameters"` 41 | Authorizers OverflowAuthorizers `json:"-"` 42 | ParameterOrder []string `json:"order"` 43 | } 44 | 45 | // a type representing one network in a solution, so mainnet/testnet/emulator 46 | type OverflowSolutionNetwork struct { 47 | Scripts map[string]string `json:"scripts"` 48 | Transactions map[string]string `json:"transactions,omitempty"` 49 | Contracts *map[string]string `json:"contracts,omitempty"` 50 | } 51 | 52 | // a type representing a merged solution that will be serialized as the json file for the npm module 53 | type OverflowSolutionMerged struct { 54 | Networks map[string]OverflowSolutionMergedNetwork `json:"networks"` 55 | } 56 | 57 | // a network in the merged solution 58 | type OverflowSolutionMergedNetwork struct { 59 | Scripts map[string]OverflowCodeWithSpec `json:"scripts"` 60 | Transactions map[string]OverflowCodeWithSpec `json:"transactions,omitempty"` 61 | Contracts *map[string]string `json:"contracts,omitempty"` 62 | } 63 | 64 | // representing code with specification if parameters 65 | type OverflowCodeWithSpec struct { 66 | Spec *OverflowDeclarationInfo `json:"spec"` 67 | Code string `json:"code"` 68 | } 69 | 70 | // merge the given Solution into a MergedSolution that is suited for exposing as an NPM module 71 | func (s *OverflowSolution) MergeSpecAndCode() *OverflowSolutionMerged { 72 | networks := map[string]OverflowSolutionMergedNetwork{} 73 | 74 | networkNames := []string{} 75 | for name := range s.Networks { 76 | networkNames = append(networkNames, name) 77 | } 78 | 79 | for name, network := range s.Networks { 80 | 81 | scripts := map[string]OverflowCodeWithSpec{} 82 | for rawScriptName, code := range network.Scripts { 83 | 84 | scriptName := rawScriptName 85 | 86 | valid := true 87 | for _, networkName := range networkNames { 88 | overwriteNetworkScriptName := fmt.Sprintf("%s%s", networkName, scriptName) 89 | _, ok := s.Scripts[overwriteNetworkScriptName] 90 | if ok { 91 | if networkName == name { 92 | valid = false 93 | break 94 | } 95 | } 96 | if strings.HasPrefix(scriptName, networkName) { 97 | if networkName == name { 98 | scriptName = strings.TrimPrefix(scriptName, networkName) 99 | valid = true 100 | break 101 | } else { 102 | valid = false 103 | break 104 | } 105 | } 106 | } 107 | if valid { 108 | scripts[scriptName] = OverflowCodeWithSpec{ 109 | Code: formatCode(code), 110 | Spec: s.Scripts[rawScriptName], 111 | } 112 | } 113 | } 114 | 115 | transactions := map[string]OverflowCodeWithSpec{} 116 | for rawTxName, code := range network.Transactions { 117 | 118 | txName := rawTxName 119 | txValid := true 120 | for _, networkName := range networkNames { 121 | overwriteNetworkTxName := fmt.Sprintf("%s%s", networkName, txName) 122 | _, ok := s.Transactions[overwriteNetworkTxName] 123 | if ok { 124 | if networkName == name { 125 | txValid = false 126 | break 127 | } 128 | } 129 | if strings.HasPrefix(txName, networkName) { 130 | if networkName == name { 131 | txName = strings.TrimPrefix(txName, networkName) 132 | txValid = true 133 | break 134 | } else { 135 | txValid = false 136 | break 137 | } 138 | } 139 | } 140 | if txValid { 141 | transactions[txName] = OverflowCodeWithSpec{ 142 | Code: formatCode(code), 143 | Spec: s.Transactions[rawTxName], 144 | } 145 | } 146 | } 147 | 148 | networks[name] = OverflowSolutionMergedNetwork{ 149 | Contracts: network.Contracts, 150 | Scripts: scripts, 151 | Transactions: transactions, 152 | } 153 | 154 | } 155 | return &OverflowSolutionMerged{Networks: networks} 156 | } 157 | 158 | func declarationInfo(code []byte) *OverflowDeclarationInfo { 159 | params, authorizerTypes := paramsAndAuthorizers(code) 160 | if params == nil { 161 | return &OverflowDeclarationInfo{ 162 | ParameterOrder: []string{}, 163 | Parameters: map[string]string{}, 164 | Authorizers: authorizerTypes, 165 | } 166 | } 167 | parametersMap := make(map[string]string, len(params.Parameters)) 168 | var parameterList []string 169 | for _, parameter := range params.Parameters { 170 | parametersMap[parameter.Identifier.Identifier] = parameter.TypeAnnotation.Type.String() 171 | parameterList = append(parameterList, parameter.Identifier.Identifier) 172 | } 173 | if len(parameterList) == 0 { 174 | return &OverflowDeclarationInfo{ 175 | ParameterOrder: []string{}, 176 | Parameters: map[string]string{}, 177 | Authorizers: authorizerTypes, 178 | } 179 | } 180 | return &OverflowDeclarationInfo{ 181 | ParameterOrder: parameterList, 182 | Parameters: parametersMap, 183 | Authorizers: authorizerTypes, 184 | } 185 | } 186 | 187 | func paramsAndAuthorizers(code []byte) (*ast.ParameterList, OverflowAuthorizers) { 188 | program, err := parser.ParseProgram(nil, code, parser.Config{}) 189 | if err != nil { 190 | return nil, nil 191 | } 192 | 193 | authorizers := OverflowAuthorizers{} 194 | // if we have any transtion declaration then return it 195 | for _, txd := range program.TransactionDeclarations() { 196 | if txd.Prepare != nil { 197 | prepareParams := txd.Prepare.FunctionDeclaration.ParameterList 198 | if prepareParams != nil { 199 | for _, parg := range txd.Prepare.FunctionDeclaration.ParameterList.ParametersByIdentifier() { 200 | name := parg.Identifier.Identifier 201 | ta := parg.TypeAnnotation 202 | if ta != nil { 203 | rt, ok := ta.Type.(*ast.ReferenceType) 204 | if ok { 205 | 206 | entitlements := []string{} 207 | switch authorization := rt.Authorization.(type) { 208 | case ast.EntitlementSet: 209 | for _, entitlement := range authorization.Entitlements() { 210 | entitlements = append(entitlements, entitlement.Identifier.Identifier) 211 | } 212 | } 213 | authorizers = append(authorizers, OverflowAuthorizer{ 214 | Name: name, 215 | Entitlements: entitlements, 216 | }) 217 | } else { 218 | authorizers = append(authorizers, OverflowAuthorizer{Name: name}) 219 | } 220 | } 221 | } 222 | } 223 | } 224 | return txd.ParameterList, authorizers 225 | } 226 | 227 | functionDeclaration := sema.FunctionEntryPointDeclaration(program) 228 | if functionDeclaration != nil { 229 | if functionDeclaration.ParameterList != nil { 230 | return functionDeclaration.ParameterList, nil 231 | } 232 | } 233 | 234 | return nil, nil 235 | } 236 | 237 | func formatCode(input string) string { 238 | return strings.ReplaceAll(strings.TrimSpace(input), "\t", " ") 239 | } 240 | -------------------------------------------------------------------------------- /parse_integration_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hexops/autogold" 8 | "github.com/sanity-io/litter" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestParseConfig(t *testing.T) { 14 | g, err2 := OverflowTesting() 15 | require.NoError(t, err2) 16 | require.NotNil(t, g) 17 | 18 | t.Run("parse", func(t *testing.T) { 19 | result, err := g.ParseAll() 20 | assert.NoError(t, err) 21 | 22 | assert.NotNil(t, result) 23 | // TODO; do not know 24 | // TODO; do not know why equal does not work here 25 | // autogold.Equal(t, result) 26 | }) 27 | 28 | t.Run("parse and merge", func(t *testing.T) { 29 | result, err := g.ParseAll() 30 | assert.NoError(t, err) 31 | 32 | merged := result.MergeSpecAndCode() 33 | // TODO; do not know why equal does not work here 34 | assert.NotNil(t, merged) 35 | }) 36 | 37 | t.Run("parse and filter", func(t *testing.T) { 38 | result, err := g.ParseAllWithConfig(true, []string{"arguments"}, []string{"test"}) 39 | assert.NoError(t, err) 40 | autogold.Equal(t, result) 41 | }) 42 | 43 | t.Run("parse and merge strip network prefix scripts", func(t *testing.T) { 44 | result, err := g.ParseAll() 45 | assert.NoError(t, err) 46 | 47 | merged := result.MergeSpecAndCode() 48 | emulator := merged.Networks["emulator"] 49 | _, ok := emulator.Scripts["Foo"] 50 | assert.True(t, ok, litter.Sdump(emulator.Scripts)) 51 | mainnet := merged.Networks["mainnet"] 52 | _, mainnetOk := mainnet.Scripts["Foo"] 53 | assert.True(t, mainnetOk, litter.Sdump(mainnet.Scripts)) 54 | }) 55 | 56 | t.Run("parse and merge strip network prefix transaction", func(t *testing.T) { 57 | result, err := g.ParseAll() 58 | assert.NoError(t, err) 59 | merged := result.MergeSpecAndCode() 60 | emulator := merged.Networks["emulator"] 61 | _, ok := emulator.Transactions["Foo"] 62 | assert.True(t, ok, litter.Sdump(emulator.Transactions)) 63 | mainnet := merged.Networks["mainnet"] 64 | _, mainnetOk := mainnet.Transactions["Foo"] 65 | assert.True(t, mainnetOk, litter.Sdump(mainnet.Transactions)) 66 | }) 67 | 68 | tx := `import FungibleToken from %s 69 | 70 | transaction() { 71 | // This is a %s transaction 72 | }` 73 | 74 | script := `import FungibleToken from %s 75 | // This is a %s script 76 | 77 | access(all) fun main(account: Address): String { 78 | return getAccount(account).address.toString() 79 | }` 80 | 81 | result, err := g.ParseAll() 82 | assert.NoError(t, err) 83 | merged := result.MergeSpecAndCode() 84 | 85 | type ParseIntegrationTestCase struct { 86 | Network string 87 | ScriptName string 88 | ScriptType string 89 | ContractAddress string 90 | Expected string 91 | } 92 | 93 | tcs := []ParseIntegrationTestCase{ 94 | { 95 | Network: "mainnet", 96 | ScriptType: "Transaction", 97 | ScriptName: "aTransaction", 98 | ContractAddress: "0xf233dcee88fe0abe", 99 | Expected: fmt.Sprintf(tx, "0xf233dcee88fe0abe", "mainnet specific"), 100 | }, 101 | { 102 | Network: "mainnet", 103 | ScriptType: "Transaction", 104 | ScriptName: "zTransaction", 105 | ContractAddress: "0xf233dcee88fe0abe", 106 | Expected: fmt.Sprintf(tx, "0xf233dcee88fe0abe", "mainnet specific"), 107 | }, 108 | { 109 | Network: "emulator", 110 | ScriptType: "Transaction", 111 | ScriptName: "aTransaction", 112 | ContractAddress: "0xee82856bf20e2aa6", 113 | Expected: fmt.Sprintf(tx, "0xee82856bf20e2aa6", "generic"), 114 | }, 115 | { 116 | Network: "emulator", 117 | ScriptType: "Transaction", 118 | ScriptName: "zTransaction", 119 | ContractAddress: "0xf233dcee88fe0abe", 120 | Expected: fmt.Sprintf(tx, "0xee82856bf20e2aa6", "generic"), 121 | }, 122 | { 123 | Network: "mainnet", 124 | ScriptType: "Script", 125 | ScriptName: "aScript", 126 | ContractAddress: "0xf233dcee88fe0abe", 127 | Expected: fmt.Sprintf(script, "0xf233dcee88fe0abe", "mainnet specific"), 128 | }, 129 | { 130 | Network: "mainnet", 131 | ScriptType: "Script", 132 | ScriptName: "zScript", 133 | ContractAddress: "0xf233dcee88fe0abe", 134 | Expected: fmt.Sprintf(script, "0xf233dcee88fe0abe", "mainnet specific"), 135 | }, 136 | { 137 | Network: "emulator", 138 | ScriptType: "Script", 139 | ScriptName: "aScript", 140 | ContractAddress: "0xee82856bf20e2aa6", 141 | Expected: fmt.Sprintf(script, "0xee82856bf20e2aa6", "generic"), 142 | }, 143 | { 144 | Network: "emulator", 145 | ScriptType: "Script", 146 | ScriptName: "zScript", 147 | ContractAddress: "0xf233dcee88fe0abe", 148 | Expected: fmt.Sprintf(script, "0xee82856bf20e2aa6", "generic"), 149 | }, 150 | } 151 | 152 | for _, tc := range tcs { 153 | t.Run(fmt.Sprintf("parse and overwrite %s with %s network prefix script : %s", tc.ScriptType, tc.Network, tc.ScriptName), func(t *testing.T) { 154 | network := merged.Networks[tc.Network] 155 | if tc.ScriptType == "Script" { 156 | script, ok := network.Scripts[tc.ScriptName] 157 | assert.True(t, ok, litter.Sdump(network.Scripts)) 158 | assert.Equal(t, tc.Expected, script.Code) 159 | return 160 | } 161 | tx, ok := network.Transactions[tc.ScriptName] 162 | assert.True(t, ok, litter.Sdump(network.Transactions)) 163 | assert.Equal(t, tc.Expected, tx.Code) 164 | }) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /print.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/enescakir/emoji" 8 | "github.com/fatih/color" 9 | ) 10 | 11 | // a type represneting seting an obtion in the printer builder 12 | type OverflowPrinterOption func(*OverflowPrinterBuilder) 13 | 14 | // a type representing the accuumlated state in the builder 15 | // 16 | // the default setting is to print one line for each transaction with meter and all events 17 | type OverflowPrinterBuilder struct { 18 | // filter out some events 19 | EventFilter OverflowEventFilter 20 | 21 | // 0 to print no meter, 1 to print some, 2 to pritn all NB verbose 22 | Meter int 23 | 24 | // set to false to disable all events 25 | Events bool 26 | 27 | // print the emulator log, NB! Verbose 28 | EmulatorLog bool 29 | 30 | // print transaction id, useful to disable in tests 31 | Id bool 32 | 33 | Arguments bool 34 | TransactionUrl bool 35 | } 36 | 37 | // print full meter verbose mode 38 | func WithFullMeter() OverflowPrinterOption { 39 | return func(opb *OverflowPrinterBuilder) { 40 | opb.Meter = 2 41 | } 42 | } 43 | 44 | // print meters as part of the transaction output line 45 | func WithMeter() OverflowPrinterOption { 46 | return func(opb *OverflowPrinterBuilder) { 47 | opb.Meter = 1 48 | } 49 | } 50 | 51 | func WithTransactionUrl() OverflowPrinterOption { 52 | return func(opb *OverflowPrinterBuilder) { 53 | opb.TransactionUrl = true 54 | } 55 | } 56 | 57 | // do not print meter 58 | func WithoutMeter(value int) OverflowPrinterOption { 59 | return func(opb *OverflowPrinterBuilder) { 60 | opb.Meter = 0 61 | } 62 | } 63 | 64 | // print the emulator log. NB! Verbose 65 | func WithEmulatorLog() OverflowPrinterOption { 66 | return func(opb *OverflowPrinterBuilder) { 67 | opb.EmulatorLog = true 68 | } 69 | } 70 | 71 | // filter out events that are printed 72 | func WithEventFilter(filter OverflowEventFilter) OverflowPrinterOption { 73 | return func(opb *OverflowPrinterBuilder) { 74 | opb.EventFilter = filter 75 | } 76 | } 77 | 78 | // do not print events 79 | func WithoutEvents() OverflowPrinterOption { 80 | return func(opb *OverflowPrinterBuilder) { 81 | opb.Events = false 82 | } 83 | } 84 | 85 | func WithoutId() OverflowPrinterOption { 86 | return func(opb *OverflowPrinterBuilder) { 87 | opb.Id = false 88 | } 89 | } 90 | 91 | func WithArguments() OverflowPrinterOption { 92 | return func(opb *OverflowPrinterBuilder) { 93 | opb.Arguments = true 94 | } 95 | } 96 | 97 | // print out an result 98 | func (o OverflowResult) Print(opbs ...OverflowPrinterOption) OverflowResult { 99 | printOpts := &OverflowPrinterBuilder{ 100 | Events: true, 101 | EventFilter: OverflowEventFilter{}, 102 | Meter: 1, 103 | EmulatorLog: false, 104 | Id: true, 105 | Arguments: false, 106 | TransactionUrl: false, 107 | } 108 | 109 | for _, opb := range opbs { 110 | opb(printOpts) 111 | } 112 | 113 | messages := []string{} 114 | 115 | nameMessage := fmt.Sprintf("Tx:%s", o.Name) 116 | if o.Name == "inline" { 117 | nameMessage = "Inline TX" 118 | } 119 | messages = append(messages, nameMessage) 120 | 121 | if len(o.Fee) != 0 { 122 | messages = append(messages, fmt.Sprintf("fee:%.8f gas:%d", o.Fee["amount"], o.FeeGas)) 123 | } else { 124 | if o.ComputationUsed != 0 { 125 | messages = append(messages, fmt.Sprintf("gas:%d", o.ComputationUsed)) 126 | } 127 | } 128 | 129 | if printOpts.Id { 130 | messages = append(messages, fmt.Sprintf("id:%s", o.Id.String())) 131 | } 132 | 133 | icon := emoji.OkHand.String() 134 | if o.Err != nil { 135 | color.Red("%v Error executing transaction: %s error:%v", emoji.PileOfPoo, o.Name, o.Err) 136 | icon = emoji.PileOfPoo.String() 137 | } 138 | 139 | fmt.Printf("%v %s\n", icon, strings.Join(messages, " ")) 140 | 141 | if printOpts.TransactionUrl { 142 | fmt.Printf("https://flowscan.org/transaction/%s\n", o.Id) 143 | } 144 | 145 | if printOpts.Arguments { 146 | o.PrintArguments(nil) 147 | } 148 | 149 | if printOpts.Events { 150 | events := o.Events 151 | if len(printOpts.EventFilter) != 0 { 152 | events = events.FilterEvents(printOpts.EventFilter) 153 | } 154 | if len(events) != 0 { 155 | events.Print(nil) 156 | } 157 | } 158 | 159 | if printOpts.EmulatorLog && len(o.RawLog) > 0 { 160 | fmt.Println("=== LOG ===") 161 | for _, msg := range o.RawLog { 162 | fmt.Println(msg.Msg) 163 | } 164 | } 165 | /* 166 | //TODO: print how a meter is computed 167 | if printOpts.Meter == 1 && o.Meter != nil { 168 | messages = append(messages, fmt.Sprintf("loops:%d", o.Meter.Loops())) 169 | messages = append(messages, fmt.Sprintf("statements:%d", o.Meter.Statements())) 170 | messages = append(messages, fmt.Sprintf("invocations:%d", o.Meter.FunctionInvocations())) 171 | } 172 | */ 173 | 174 | if printOpts.Meter != 0 && o.Meter != nil { 175 | if printOpts.Meter == 2 { 176 | fmt.Println("=== METER ===") 177 | fmt.Printf("LedgerInteractionUsed: %d\n", o.Meter.LedgerInteractionUsed) 178 | if o.Meter.MemoryUsed != 0 { 179 | fmt.Printf("Memory: %d\n", o.Meter.MemoryUsed) 180 | memories := strings.ReplaceAll(strings.Trim(fmt.Sprintf("%+v", o.Meter.MemoryIntensities), "map[]"), " ", "\n ") 181 | 182 | fmt.Println("Memory Intensities") 183 | fmt.Printf(" %s\n", memories) 184 | } 185 | fmt.Printf("Computation: %d\n", o.Meter.ComputationUsed) 186 | intensities := strings.ReplaceAll(strings.Trim(fmt.Sprintf("%+v", o.Meter.ComputationIntensities), "map[]"), " ", "\n ") 187 | 188 | fmt.Println("Computation Intensities:") 189 | fmt.Printf(" %s\n", intensities) 190 | } 191 | } 192 | return o 193 | } 194 | -------------------------------------------------------------------------------- /result.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/bjartek/underflow" 9 | "github.com/onflow/cadence" 10 | "github.com/onflow/flow-go-sdk" 11 | "github.com/sanity-io/litter" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | type CadenceArguments map[string]cadence.Value 17 | 18 | // a type to define a function used to compose Transaction interactions 19 | type OverflowTransactionFunction func(filename string, opts ...OverflowInteractionOption) *OverflowResult 20 | 21 | // a type to define a function used to compose Transaction interactions 22 | type OverflowTransactionOptsFunction func(opts ...OverflowInteractionOption) *OverflowResult 23 | 24 | // OverflowResult represents the state after running an transaction 25 | type OverflowResult struct { 26 | StopOnError bool 27 | // The error if any 28 | Err error 29 | 30 | // The id of the transaction 31 | Id flow.Identifier 32 | 33 | // If running on an emulator 34 | // the meter that contains useful debug information on memory and interactions 35 | Meter *OverflowMeter 36 | // The Raw log from the emulator 37 | RawLog []OverflowEmulatorLogMessage 38 | // The log from the emulator 39 | EmulatorLog []string 40 | 41 | // The computation used 42 | ComputationUsed int 43 | 44 | // The raw unfiltered events 45 | RawEvents []flow.Event 46 | 47 | // Events that are filtered and parsed into a terse format 48 | Events OverflowEvents 49 | 50 | // The underlying transaction if we need to look into that 51 | Transaction *flow.Transaction 52 | 53 | // The transaction result if we need to look into that 54 | TransactionResult *flow.TransactionResult 55 | 56 | // TODO: consider marshalling this as a struct for convenience 57 | // The fee event if any 58 | Fee map[string]interface{} 59 | FeeGas int 60 | 61 | Balance FeeBalance 62 | // The name of the Transaction 63 | Name string 64 | 65 | Arguments CadenceArguments 66 | UnderflowOptions underflow.Options 67 | DeclarationInfo OverflowDeclarationInfo 68 | } 69 | 70 | func (o OverflowResult) PrintArguments(t *testing.T) { 71 | printOrLog(t, "=== Arguments ===") 72 | maxLength := 0 73 | for name := range o.Arguments { 74 | if len(name) > maxLength { 75 | maxLength = len(name) 76 | } 77 | } 78 | 79 | format := fmt.Sprintf("%%%ds -> %%v", maxLength) 80 | 81 | for name, arg := range o.Arguments { 82 | value, err := underflow.CadenceValueToJsonStringWithOption(arg, o.UnderflowOptions) 83 | if err != nil { 84 | panic(err) 85 | } 86 | printOrLog(t, fmt.Sprintf(format, name, value)) 87 | } 88 | } 89 | 90 | // Get a uint64 field with the given fieldname(most often an id) from an event with a given suffix 91 | func (o OverflowResult) GetIdFromEvent(eventName string, fieldName string) (uint64, error) { 92 | for name, event := range o.Events { 93 | if strings.HasSuffix(name, eventName) { 94 | return event[0].Fields[fieldName].(uint64), nil 95 | } 96 | } 97 | err := fmt.Errorf("could not find id field %s in event with suffix %s", fieldName, eventName) 98 | if o.StopOnError { 99 | panic(err) 100 | } 101 | return 0, err 102 | } 103 | 104 | // Get a byteArray field with the given fieldname from an event with a given suffix 105 | func (o OverflowResult) GetByteArrayFromEvent(eventName string, fieldName string) ([]byte, error) { 106 | for name, event := range o.Events { 107 | if strings.HasSuffix(name, eventName) { 108 | return getByteArray(event[0].Fields[fieldName]) 109 | } 110 | } 111 | err := fmt.Errorf("could not find field %s in event with suffix %s", fieldName, eventName) 112 | if o.StopOnError { 113 | panic(err) 114 | } 115 | return nil, err 116 | } 117 | 118 | func (o OverflowResult) GetIdsFromEvent(eventName string, fieldName string) []uint64 { 119 | var ids []uint64 120 | for name, events := range o.Events { 121 | if strings.HasSuffix(name, eventName) { 122 | for _, event := range events { 123 | ids = append(ids, event.Fields[fieldName].(uint64)) 124 | } 125 | } 126 | } 127 | return ids 128 | } 129 | 130 | // Get all events that end with the given suffix 131 | func (o OverflowResult) GetEventsWithName(eventName string) []OverflowEvent { 132 | for name, event := range o.Events { 133 | if strings.HasSuffix(name, eventName) { 134 | return event 135 | } 136 | } 137 | return []OverflowEvent{} 138 | } 139 | 140 | // Get all events that end with the given suffix 141 | func (o OverflowResult) MarshalEventsWithName(eventName string, result interface{}) error { 142 | for name, event := range o.Events { 143 | if strings.HasSuffix(name, eventName) { 144 | err := event.MarshalAs(result) 145 | return err 146 | } 147 | } 148 | return nil 149 | } 150 | 151 | // Assert that this particular transaction was a failure that has a message that contains the sendt in assertion 152 | func (o OverflowResult) RequireFailure(t *testing.T, msg string) OverflowResult { 153 | t.Helper() 154 | 155 | require.Error(t, o.Err) 156 | if o.Err != nil { 157 | require.Contains(t, o.Err.Error(), msg) 158 | } 159 | return o 160 | } 161 | 162 | // Assert that this particular transaction was a failure that has a message that contains the sendt in assertion 163 | func (o OverflowResult) AssertFailure(t *testing.T, msg string) OverflowResult { 164 | t.Helper() 165 | 166 | assert.Error(t, o.Err) 167 | if o.Err != nil { 168 | assert.Contains(t, o.Err.Error(), msg) 169 | } 170 | return o 171 | } 172 | 173 | // Require that this transaction was an success 174 | func (o OverflowResult) RequireSuccess(t *testing.T) OverflowResult { 175 | t.Helper() 176 | require.NoError(t, o.Err) 177 | return o 178 | } 179 | 180 | // Assert that this transaction was an success 181 | func (o OverflowResult) AssertSuccess(t *testing.T) OverflowResult { 182 | t.Helper() 183 | assert.NoError(t, o.Err) 184 | return o 185 | } 186 | 187 | // Assert that the event with the given name suffix and fields are present 188 | func (o OverflowResult) AssertEvent(t *testing.T, name string, fields map[string]interface{}) OverflowResult { 189 | t.Helper() 190 | newFields := OverflowEvent{Fields: map[string]interface{}{}} 191 | for key, value := range fields { 192 | if value != nil { 193 | newFields.Fields[key] = value 194 | } 195 | } 196 | hit := false 197 | for eventName, events := range o.Events { 198 | if strings.HasSuffix(eventName, name) { 199 | hit = true 200 | newEvents := []OverflowEvent{} 201 | for _, event := range events { 202 | oe := OverflowEvent{Fields: map[string]interface{}{}} 203 | valid := false 204 | for key, value := range event.Fields { 205 | _, exist := newFields.Fields[key] 206 | if exist { 207 | oe.Fields[key] = value 208 | valid = true 209 | } 210 | } 211 | if valid { 212 | newEvents = append(newEvents, oe) 213 | } 214 | } 215 | 216 | if !newFields.ExistIn(newEvents) { 217 | assert.Fail(t, fmt.Sprintf("transaction %s missing event %s with fields %s", o.Name, name, litter.Sdump(newFields.Fields))) 218 | newEventsMap := OverflowEvents{eventName: newEvents} 219 | newEventsMap.Print(t) 220 | } 221 | } 222 | } 223 | if !hit { 224 | assert.Fail(t, fmt.Sprintf("event not found %s, %s", name, litter.Sdump(newFields))) 225 | o.Events.Print(t) 226 | } 227 | return o 228 | } 229 | 230 | // Require that the event with the given name suffix and fields are present 231 | func (o OverflowResult) RequireEvent(t *testing.T, name string, fields map[string]interface{}) OverflowResult { 232 | t.Helper() 233 | newFields := OverflowEvent{Fields: map[string]interface{}{}} 234 | for key, value := range fields { 235 | if value != nil { 236 | newFields.Fields[key] = value 237 | } 238 | } 239 | hit := false 240 | for eventName, events := range o.Events { 241 | if strings.HasSuffix(eventName, name) { 242 | hit = true 243 | newEvents := []OverflowEvent{} 244 | for _, event := range events { 245 | oe := OverflowEvent{Fields: map[string]interface{}{}} 246 | valid := false 247 | for key, value := range event.Fields { 248 | _, exist := newFields.Fields[key] 249 | if exist { 250 | oe.Fields[key] = value 251 | valid = true 252 | } 253 | } 254 | if valid { 255 | newEvents = append(newEvents, oe) 256 | } 257 | } 258 | 259 | if !newFields.ExistIn(newEvents) { 260 | require.Fail(t, fmt.Sprintf("transaction %s missing event %s with fields %s", o.Name, name, litter.Sdump(newFields.Fields))) 261 | newEventsMap := OverflowEvents{eventName: newEvents} 262 | newEventsMap.Print(t) 263 | } 264 | } 265 | } 266 | if !hit { 267 | require.Fail(t, fmt.Sprintf("event not found %s, %s", name, litter.Sdump(newFields))) 268 | o.Events.Print(t) 269 | } 270 | return o 271 | } 272 | 273 | // Assert that the transaction result contains the amount of events 274 | func (o OverflowResult) AssertEventCount(t *testing.T, number int) OverflowResult { 275 | t.Helper() 276 | num := 0 277 | for _, ev := range o.Events { 278 | num = num + len(ev) 279 | } 280 | assert.Equal(t, number, num) 281 | 282 | o.Events.Print(t) 283 | return o 284 | } 285 | 286 | // Assert that this transaction emitted no events 287 | func (o OverflowResult) AssertNoEvents(t *testing.T) OverflowResult { 288 | t.Helper() 289 | res := assert.Empty(t, o.Events) 290 | if !res { 291 | o.Events.Print(t) 292 | } 293 | return o 294 | } 295 | 296 | // Assert that events with the given suffixes are present 297 | func (o OverflowResult) AssertEmitEventName(t *testing.T, event ...string) OverflowResult { 298 | t.Helper() 299 | 300 | eventNames := []string{} 301 | for name := range o.Events { 302 | eventNames = append(eventNames, name) 303 | } 304 | 305 | for _, ev := range event { 306 | valid := false 307 | for _, name := range eventNames { 308 | if strings.HasSuffix(name, ev) { 309 | valid = true 310 | } 311 | } 312 | 313 | if !valid { 314 | assert.Fail(t, fmt.Sprintf("event with suffix %s not present in %v", ev, eventNames)) 315 | } 316 | } 317 | 318 | return o 319 | } 320 | 321 | // Assert that the internal log of the emulator contains the given message 322 | func (o OverflowResult) AssertEmulatorLog(t *testing.T, message string) OverflowResult { 323 | t.Helper() 324 | 325 | for _, log := range o.EmulatorLog { 326 | if strings.Contains(log, message) { 327 | return o 328 | } 329 | } 330 | 331 | assert.Fail(t, "No emulator log contain message "+message, o.EmulatorLog) 332 | 333 | return o 334 | } 335 | 336 | // Assert that this transaction did not use more then the given amount of computation 337 | func (o OverflowResult) AssertComputationLessThenOrEqual(t *testing.T, computation int) OverflowResult { 338 | t.Helper() 339 | 340 | assert.LessOrEqual(t, o.ComputationUsed, computation) 341 | if o.FeeGas != 0 { 342 | // TODO: add back in again once fixed 343 | assert.Equal(t, o.ComputationUsed, o.FeeGas) 344 | } 345 | 346 | return o 347 | } 348 | 349 | // Assert that the transaction uses exactly the given computation amount 350 | func (o OverflowResult) AssertComputationUsed(t *testing.T, computation int) OverflowResult { 351 | t.Helper() 352 | assert.Equal(t, computation, o.ComputationUsed) 353 | if o.FeeGas != 0 { 354 | // TODO: add back in again once fixed 355 | assert.Equal(t, o.ComputationUsed, o.FeeGas) 356 | } 357 | return o 358 | } 359 | 360 | // Assert that a Debug.Log event was emitted that contains the given messages 361 | func (o OverflowResult) AssertDebugLog(t *testing.T, message ...string) OverflowResult { 362 | t.Helper() 363 | var logMessages []interface{} 364 | for name, fe := range o.Events { 365 | if strings.HasSuffix(name, "Debug.Log") { 366 | for _, ev := range fe { 367 | logMessages = append(logMessages, ev.Fields["msg"]) 368 | } 369 | } 370 | } 371 | for _, ev := range message { 372 | assert.Contains(t, logMessages, ev) 373 | } 374 | return o 375 | } 376 | 377 | func getByteArray(data interface{}) ([]byte, error) { 378 | slice, ok := data.([]interface{}) 379 | if !ok { 380 | return nil, fmt.Errorf("expected a slice of interfaces") 381 | } 382 | byteSlice := make([]byte, len(slice)) 383 | for i, val := range slice { 384 | b, ok := val.(uint8) 385 | if !ok { 386 | return nil, fmt.Errorf("unexpected type at index %d", i) 387 | } 388 | byteSlice[i] = b 389 | } 390 | return byteSlice, nil 391 | } 392 | -------------------------------------------------------------------------------- /rollback_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestRollback(t *testing.T) { 12 | 13 | o, err := OverflowTesting(WithCoverageReport()) 14 | require.NoError(t, err) 15 | require.NotNil(t, o) 16 | 17 | block, err := o.GetLatestBlock(context.Background()) 18 | require.NoError(t, err) 19 | assert.Equal(t, uint64(5), block.Height) 20 | o.Tx("mint_tokens", WithSignerServiceAccount(), WithArg("recipient", "first"), WithArg("amount", 1.0)).AssertSuccess(t) 21 | 22 | block, err = o.GetLatestBlock(context.Background()) 23 | require.NoError(t, err) 24 | 25 | require.NoError(t, err) 26 | assert.Equal(t, uint64(6), block.Height) 27 | 28 | err = o.RollbackToBlockHeight(4) 29 | require.NoError(t, err) 30 | 31 | block, err = o.GetLatestBlock(context.Background()) 32 | require.NoError(t, err) 33 | assert.Equal(t, uint64(4), block.Height) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /script.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "testing" 8 | 9 | "github.com/bjartek/underflow" 10 | "github.com/enescakir/emoji" 11 | "github.com/fatih/color" 12 | "github.com/hexops/autogold" 13 | "github.com/onflow/cadence" 14 | "github.com/onflow/flowkit/v2" 15 | "github.com/pkg/errors" 16 | "github.com/sanity-io/litter" 17 | "github.com/stretchr/testify/assert" 18 | "github.com/stretchr/testify/require" 19 | "github.com/xeipuuv/gojsonpointer" 20 | ) 21 | 22 | // Scripts 23 | // 24 | // A read only interaction against the flow blockchain 25 | 26 | // a type used for composing scripts 27 | type OverflowScriptFunction func(filename string, opts ...OverflowInteractionOption) *OverflowScriptResult 28 | 29 | // a type used for composing scripts 30 | type OverflowScriptOptsFunction func(opts ...OverflowInteractionOption) *OverflowScriptResult 31 | 32 | // compose interactionOptions into a new Script function 33 | func (o *OverflowState) ScriptFN(outerOpts ...OverflowInteractionOption) OverflowScriptFunction { 34 | return func(filename string, opts ...OverflowInteractionOption) *OverflowScriptResult { 35 | outerOpts = append(outerOpts, opts...) 36 | return o.Script(filename, outerOpts...) 37 | } 38 | } 39 | 40 | // compose fileName and interactionOptions into a new Script function 41 | func (o *OverflowState) ScriptFileNameFN(filename string, outerOpts ...OverflowInteractionOption) OverflowScriptOptsFunction { 42 | return func(opts ...OverflowInteractionOption) *OverflowScriptResult { 43 | outerOpts = append(outerOpts, opts...) 44 | return o.Script(filename, outerOpts...) 45 | } 46 | } 47 | 48 | // run a script with the given code/filanem an options 49 | func (o *OverflowState) Script(filename string, opts ...OverflowInteractionOption) *OverflowScriptResult { 50 | interaction := o.BuildInteraction(filename, "script", opts...) 51 | 52 | result := interaction.runScript() 53 | 54 | if interaction.PrintOptions != nil && !interaction.NoLog { 55 | result.Print() 56 | } 57 | if o.StopOnError && result.Err != nil { 58 | result.PrintArguments(nil) 59 | panic(result.Err) 60 | } 61 | return result 62 | } 63 | 64 | func (fbi *OverflowInteractionBuilder) runScript() *OverflowScriptResult { 65 | o := fbi.Overflow 66 | osc := &OverflowScriptResult{Input: fbi} 67 | if fbi.Error != nil { 68 | osc.Err = fbi.Error 69 | return osc 70 | } 71 | 72 | filePath := fmt.Sprintf("%s/%s.cdc", fbi.BasePath, fbi.FileName) 73 | 74 | o.Log.Reset() 75 | 76 | script := flowkit.Script{ 77 | Code: fbi.TransactionCode, 78 | Args: fbi.Arguments, 79 | Location: filePath, 80 | } 81 | sc := fbi.ScriptQuery 82 | if fbi.ScriptQuery == nil { 83 | sc = &flowkit.ScriptQuery{ 84 | Latest: true, 85 | } 86 | } 87 | result, err := o.Flowkit.ExecuteScript(fbi.Ctx, script, *sc) 88 | osc.Result = result 89 | osc.Output = underflow.CadenceValueToInterfaceWithOption(result, fbi.Overflow.UnderflowOptions) 90 | if err != nil { 91 | osc.Err = errors.Wrapf(err, "scriptFileName:%s", fbi.FileName) 92 | } 93 | //} 94 | 95 | var logMessage []OverflowEmulatorLogMessage 96 | dec := json.NewDecoder(o.Log) 97 | for { 98 | var doc OverflowEmulatorLogMessage 99 | 100 | err := dec.Decode(&doc) 101 | if err == io.EOF { 102 | // all done 103 | break 104 | } 105 | if err != nil { 106 | osc.Err = err 107 | } 108 | 109 | logMessage = append(logMessage, doc) 110 | } 111 | 112 | o.Log.Reset() 113 | 114 | osc.Log = logMessage 115 | 116 | return osc 117 | } 118 | 119 | // result after running a script 120 | type OverflowScriptResult struct { 121 | Err error 122 | Result cadence.Value 123 | Output interface{} 124 | Input *OverflowInteractionBuilder 125 | Log []OverflowEmulatorLogMessage 126 | } 127 | 128 | func (osr *OverflowScriptResult) PrintArguments(t *testing.T) { 129 | args := osr.Input.NamedCadenceArguments 130 | maxLength := 0 131 | for name := range args { 132 | if len(name) > maxLength { 133 | maxLength = len(name) 134 | } 135 | } 136 | 137 | format := fmt.Sprintf("%%%ds -> %%v", maxLength) 138 | 139 | for name, arg := range args { 140 | value, err := underflow.CadenceValueToJsonStringWithOption(arg, osr.Input.Overflow.UnderflowOptions) 141 | if err != nil { 142 | panic(err) 143 | } 144 | printOrLog(t, fmt.Sprintf(format, name, value)) 145 | } 146 | } 147 | 148 | // get the script as json 149 | func (osr *OverflowScriptResult) GetAsJson() (string, error) { 150 | if osr.Err != nil { 151 | return "", errors.Wrapf(osr.Err, "script: %s", osr.Input.FileName) 152 | } 153 | j, err := json.MarshalIndent(osr.Output, "", " ") 154 | if err != nil { 155 | return "", errors.Wrapf(err, "script: %s", osr.Input.FileName) 156 | } 157 | 158 | return string(j), nil 159 | } 160 | 161 | // get the script as interface{} 162 | func (osr *OverflowScriptResult) GetAsInterface() (interface{}, error) { 163 | if osr.Err != nil { 164 | return nil, errors.Wrapf(osr.Err, "script: %s", osr.Input.FileName) 165 | } 166 | return osr.Output, nil 167 | } 168 | 169 | // Assert that a jsonPointer into the result is an error 170 | func (osr *OverflowScriptResult) AssertWithPointerError(t *testing.T, pointer string, message string) *OverflowScriptResult { 171 | t.Helper() 172 | _, err := osr.GetWithPointer(pointer) 173 | assert.Error(t, err) 174 | assert.ErrorContains(t, err, message, "output", litter.Sdump(osr.Output)) 175 | 176 | return osr 177 | } 178 | 179 | // Assert that a jsonPointer into the result is equal to the given value 180 | func (osr *OverflowScriptResult) AssertWithPointer(t *testing.T, pointer string, value interface{}) *OverflowScriptResult { 181 | t.Helper() 182 | result, err := osr.GetWithPointer(pointer) 183 | assert.NoError(t, err) 184 | 185 | assert.Equal(t, value, result, "output", litter.Sdump(osr.Output)) 186 | 187 | return osr 188 | } 189 | 190 | // Assert that a jsonPointer into the result is equal to the given autogold Want 191 | func (osr *OverflowScriptResult) AssertWithPointerWant(t *testing.T, pointer string, want autogold.Value) *OverflowScriptResult { 192 | t.Helper() 193 | result, err := osr.GetWithPointer(pointer) 194 | assert.NoError(t, err) 195 | 196 | switch result.(type) { 197 | case []interface{}, map[interface{}]interface{}: 198 | want.Equal(t, litter.Sdump(result)) 199 | default: 200 | want.Equal(t, result) 201 | } 202 | return osr 203 | } 204 | 205 | // Assert that the length of a jsonPointer is equal to length 206 | func (osr *OverflowScriptResult) AssertLengthWithPointer(t *testing.T, pointer string, length int) *OverflowScriptResult { 207 | t.Helper() 208 | 209 | require.NoError(t, osr.Err) 210 | osr.Print() 211 | result, err := osr.GetWithPointer(pointer) 212 | require.NoError(t, err) 213 | switch res := result.(type) { 214 | case []interface{}: 215 | assert.Equal(t, length, len(res), litter.Sdump(osr.Output)) 216 | case map[interface{}]interface{}: 217 | assert.Equal(t, length, len(res), litter.Sdump(osr.Output)) 218 | default: 219 | assert.Equal(t, length, len(fmt.Sprintf("%v", res)), litter.Sdump(osr.Output)) 220 | } 221 | return osr 222 | } 223 | 224 | // Marshal the script output as the given sent in type 225 | func (osr *OverflowScriptResult) MarshalAs(marshalTo interface{}) error { 226 | if osr.Err != nil { 227 | return osr.Err 228 | } 229 | bytes, err := json.Marshal(osr.Output) 230 | if err != nil { 231 | return err 232 | } 233 | 234 | err = json.Unmarshal(bytes, marshalTo) 235 | if err != nil { 236 | return err 237 | } 238 | return nil 239 | } 240 | 241 | // Marshal the given jsonPointer as the given type 242 | func (osr *OverflowScriptResult) MarshalPointerAs(pointer string, marshalTo interface{}) error { 243 | ptr, err := gojsonpointer.NewJsonPointer(pointer) 244 | if err != nil { 245 | return err 246 | } 247 | result, _, err := ptr.Get(osr.Output) 248 | if err != nil { 249 | return err 250 | } 251 | 252 | bytes, err := json.Marshal(result) 253 | if err != nil { 254 | return err 255 | } 256 | 257 | err = json.Unmarshal(bytes, marshalTo) 258 | if err != nil { 259 | return err 260 | } 261 | return nil 262 | } 263 | 264 | // get the given jsonPointer as interface{} 265 | func (osr *OverflowScriptResult) GetWithPointer(pointer string) (interface{}, error) { 266 | ptr, err := gojsonpointer.NewJsonPointer(pointer) 267 | if err != nil { 268 | return nil, err 269 | } 270 | result, _, err := ptr.Get(osr.Output) 271 | return result, err 272 | } 273 | 274 | // Assert that the result is equal to the given autogold.Want 275 | func (osr *OverflowScriptResult) AssertWant(t *testing.T, want autogold.Value) *OverflowScriptResult { 276 | t.Helper() 277 | assert.NoError(t, osr.Err) 278 | 279 | switch osr.Output.(type) { 280 | case []interface{}, map[interface{}]interface{}: 281 | want.Equal(t, litter.Sdump(osr.Output)) 282 | default: 283 | want.Equal(t, osr.Output) 284 | } 285 | return osr 286 | } 287 | 288 | // Print the result 289 | func (osr *OverflowScriptResult) Print() *OverflowScriptResult { 290 | json, err := osr.GetAsJson() 291 | if err != nil { 292 | color.Red(err.Error()) 293 | return osr 294 | } 295 | fmt.Printf("%v Script %s run result:%v\n", emoji.Star, osr.Input.Name, json) 296 | return osr 297 | } 298 | -------------------------------------------------------------------------------- /script_integration_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hexops/autogold" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | type AwesomeStruct struct { 12 | First struct { 13 | Nested string `json:"nested"` 14 | } `json:"first"` 15 | } 16 | 17 | func TestScriptIntegrationNew(t *testing.T) { 18 | o, err := OverflowTesting() 19 | require.NoError(t, err) 20 | require.NotNil(t, o) 21 | 22 | mapScript := o.ScriptFileNameFN(`access(all) fun main() : {String: {String: String}} { 23 | return { "first" : { "nested" : "nestedvalue"}} 24 | 25 | }`) 26 | 27 | t.Run("Get length from pointer", func(t *testing.T) { 28 | mapScript().AssertLengthWithPointer(t, "/first/nested", 11) 29 | }) 30 | 31 | t.Run("Get length from pointer map", func(t *testing.T) { 32 | mapScript().AssertLengthWithPointer(t, "/first", 23) 33 | }) 34 | 35 | t.Run("Get value from pointer", func(t *testing.T) { 36 | mapScript().AssertWithPointer(t, "/first/nested", "nestedvalue") 37 | }) 38 | 39 | t.Run("Get error from pointer", func(t *testing.T) { 40 | mapScript().AssertWithPointerError(t, "/first/nested2", "Object has no key 'nested2'") 41 | }) 42 | 43 | t.Run("Get value using want", func(t *testing.T) { 44 | mapScript().AssertWithPointerWant(t, "/first/nested", autogold.Want("nested", "nestedvalue")) 45 | }) 46 | 47 | t.Run("Get value using want map", func(t *testing.T) { 48 | //Note that wants must have a unique name 49 | mapScript().AssertWithPointerWant(t, "/first", autogold.Want("nestedMap", map[string]interface{}{"nested": "nestedvalue"})) 50 | }) 51 | 52 | t.Run("Marhal result using pointer", func(t *testing.T) { 53 | 54 | var result map[string]string 55 | //Note that wants must have a unique name 56 | err := mapScript().MarshalPointerAs("/first", &result) 57 | assert.NoError(t, err) 58 | 59 | assert.Equal(t, "nestedvalue", result["nested"]) 60 | }) 61 | 62 | t.Run("Marhal result", func(t *testing.T) { 63 | 64 | var result AwesomeStruct 65 | err := mapScript().MarshalAs(&result) 66 | assert.NoError(t, err) 67 | 68 | assert.Equal(t, "nestedvalue", result.First.Nested) 69 | }) 70 | 71 | t.Run("Get assert with want", func(t *testing.T) { 72 | mapScript().AssertWant(t, autogold.Want("assertWant", map[string]interface{}{"first": map[string]interface{}{"nested": "nestedvalue"}})) 73 | }) 74 | 75 | t.Run("Use relative import", func(t *testing.T) { 76 | res := o.Script(` 77 | import Debug from "../contracts/Debug.cdc" 78 | 79 | access(all) fun main() : AnyStruct { 80 | return "foo" 81 | } 82 | 83 | `) 84 | require.NoError(t, res.Err) 85 | assert.Equal(t, "foo", res.Output) 86 | 87 | }) 88 | 89 | t.Run("Use new import syntax", func(t *testing.T) { 90 | res := o.Script(` 91 | import "Debug" 92 | 93 | access(all) fun main() : AnyStruct { 94 | return "foo" 95 | } 96 | 97 | `) 98 | require.NoError(t, res.Err) 99 | assert.Equal(t, "foo", res.Output) 100 | 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /script_v3_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestScript(t *testing.T) { 12 | o, err := OverflowTesting() 13 | require.NoError(t, err) 14 | require.NotNil(t, o) 15 | 16 | t.Run("Run simple script interface", func(t *testing.T) { 17 | res, err := o.Script("test", WithArg("account", "first")).GetAsInterface() 18 | assert.NoError(t, err) 19 | assert.Equal(t, "0x179b6b1cb6755e31", res) 20 | }) 21 | 22 | t.Run("Run simple script json", func(t *testing.T) { 23 | res, err := o.Script("test", WithArg("account", "first")).GetAsJson() 24 | assert.NoError(t, err) 25 | assert.Equal(t, `"0x179b6b1cb6755e31"`, res) 26 | }) 27 | 28 | t.Run("Run simple script marshal", func(t *testing.T) { 29 | var res string 30 | err := o.Script("test", WithArg("account", "first")).MarshalAs(&res) 31 | assert.NoError(t, err) 32 | assert.Equal(t, "0x179b6b1cb6755e31", res) 33 | }) 34 | 35 | t.Run("Run simple script marshal with underlying error", func(t *testing.T) { 36 | var res string 37 | err := o.Script("test2", WithArg("account", "first")).MarshalAs(&res) 38 | assert.Error(t, err) 39 | assert.ErrorContains(t, err, "Could not read interaction file from path") 40 | }) 41 | 42 | t.Run("compose a script", func(t *testing.T) { 43 | accountScript := o.ScriptFN(WithArg("account", "first")) 44 | res := accountScript("test") 45 | assert.NoError(t, res.Err) 46 | }) 47 | 48 | t.Run("create script with name", func(t *testing.T) { 49 | testScript := o.ScriptFileNameFN("test") 50 | res := testScript(WithArg("account", "first")) 51 | assert.NoError(t, res.Err) 52 | }) 53 | 54 | t.Run("Run script inline", func(t *testing.T) { 55 | res, err := o.Script(` 56 | access(all) fun main(): String { 57 | return "foo" 58 | } 59 | `).GetAsJson() 60 | assert.NoError(t, err) 61 | assert.Equal(t, `"foo"`, res) 62 | }) 63 | 64 | t.Run("Run script return type", func(t *testing.T) { 65 | res, err := o.Script("type").GetAsInterface() 66 | assert.NoError(t, err) 67 | assert.Equal(t, `A.0ae53cb6e3f42a79.FlowToken.Vault`, res) 68 | }) 69 | 70 | t.Run("Run script with string array", func(t *testing.T) { 71 | input := []string{"test", "foo"} 72 | res, err := o.Script(` 73 | access(all) fun main(input: [String]): [String] { 74 | return input 75 | } 76 | 77 | `, WithArg("input", input)).GetAsJson() 78 | assert.NoError(t, err) 79 | assert.JSONEq(t, `["test", "foo"]`, res) 80 | }) 81 | 82 | t.Run("Run script with string map", func(t *testing.T) { 83 | input := `{"test": "foo", "test2": "bar"}` 84 | res, err := o.Script(` 85 | access(all) fun main(input: {String : String}): {String: String} { 86 | return input 87 | } 88 | 89 | `, WithArg("input", input)).GetAsJson() 90 | assert.NoError(t, err) 91 | assert.JSONEq(t, `{"test": "foo", "test2":"bar"}`, res) 92 | }) 93 | 94 | t.Run("Run script with string map from native input", func(t *testing.T) { 95 | input := map[string]string{ 96 | "test": "foo", 97 | "test2": "bar", 98 | } 99 | res, err := o.Script(` 100 | access(all) fun main(input: {String : String}): {String: String} { 101 | return input 102 | } 103 | 104 | `, WithArg("input", input)).GetAsJson() 105 | assert.NoError(t, err) 106 | assert.JSONEq(t, `{"test": "foo", "test2":"bar"}`, res) 107 | }) 108 | 109 | t.Run("Run script with string:float64 map from native input", func(t *testing.T) { 110 | input := map[string]float64{ 111 | "test": 1.0, 112 | "test2": 2.0, 113 | } 114 | res, err := o.Script(` 115 | access(all) fun main(input: {String : UFix64}): {String: UFix64} { 116 | return input 117 | } 118 | 119 | `, WithArg("input", input)).GetAsJson() 120 | assert.NoError(t, err) 121 | assert.JSONEq(t, `{"test": 1.0, "test2":2.0}`, res) 122 | }) 123 | 124 | t.Run("Run script with string:uint64 map from native input", func(t *testing.T) { 125 | input := map[string]uint64{ 126 | "test": 1, 127 | "test2": 2, 128 | } 129 | res, err := o.Script(` 130 | access(all) fun main(input: {String : UInt64}): {String: UInt64} { 131 | return input 132 | } 133 | 134 | `, WithArg("input", input)).GetAsJson() 135 | assert.NoError(t, err) 136 | assert.JSONEq(t, `{"test": 1, "test2":2}`, res) 137 | }) 138 | 139 | t.Run("Run script with ufix64 array as string", func(t *testing.T) { 140 | res, err := o.Script(` 141 | access(all) fun main(input: [UFix64]): [UFix64] { 142 | return input 143 | } 144 | 145 | `, WithArg("input", `[10.1, 20.2]`)).GetAsJson() 146 | assert.NoError(t, err) 147 | assert.JSONEq(t, `[10.1, 20.2]`, res) 148 | }) 149 | 150 | t.Run("Run script with ufix64 array", func(t *testing.T) { 151 | res, err := o.Script(` 152 | access(all) fun main(input: [UFix64]): [UFix64] { 153 | return input 154 | } 155 | 156 | `, WithArg("input", []float64{10.1, 20.2})).GetAsJson() 157 | assert.NoError(t, err) 158 | assert.JSONEq(t, `[10.1, 20.2]`, res) 159 | }) 160 | 161 | t.Run("Run script with fix64 array", func(t *testing.T) { 162 | res, err := o.Script(` 163 | access(all) fun main(input: [Fix64]): [Fix64] { 164 | return input 165 | } 166 | 167 | `, WithArg("input", `[10.1, -20.2]`)).GetAsJson() 168 | assert.NoError(t, err) 169 | 170 | assert.JSONEq(t, `[10.1, -20.2]`, res) 171 | }) 172 | 173 | t.Run("Run script with uint64 array as string", func(t *testing.T) { 174 | res, err := o.Script(` 175 | access(all) fun main(input: [UInt64]): [UInt64] { 176 | return input 177 | } 178 | 179 | `, WithArg("input", `[10, 20]`)).GetAsJson() 180 | 181 | assert.NoError(t, err) 182 | assert.JSONEq(t, `[10, 20]`, res) 183 | }) 184 | 185 | t.Run("Run script with uint64 array", func(t *testing.T) { 186 | res, err := o.Script(` 187 | access(all) fun main(input: [UInt64]): [UInt64] { 188 | return input 189 | } 190 | 191 | `, WithArg("input", []uint64{10, 20})).GetAsJson() 192 | 193 | assert.NoError(t, err) 194 | assert.JSONEq(t, `[10, 20]`, res) 195 | }) 196 | 197 | t.Run("Run script with optional Address some", func(t *testing.T) { 198 | res, err := o.Script(` 199 | access(all) fun main(input: Address?): Address? { 200 | return input 201 | } 202 | 203 | `, WithArg("input", "0x01cf0e2f2f715450")).GetAsInterface() 204 | 205 | assert.NoError(t, err) 206 | assert.Equal(t, `0x01cf0e2f2f715450`, res) 207 | }) 208 | 209 | t.Run("Run script with optional Address empty", func(t *testing.T) { 210 | res, err := o.Script(` 211 | access(all) fun main(input: Address?): Address? { 212 | return input 213 | } 214 | 215 | `, WithArg("input", nil)).GetAsInterface() 216 | 217 | assert.NoError(t, err) 218 | assert.Equal(t, nil, res) 219 | }) 220 | 221 | t.Run("Run script at previous height", func(t *testing.T) { 222 | block, err := o.GetLatestBlock(context.Background()) 223 | require.NoError(t, err) 224 | res, err := o.Script("test", WithArg("account", "first"), WithExecuteScriptAtBlockHeight(block.Height-1)).GetAsInterface() 225 | assert.NoError(t, err) 226 | assert.Equal(t, "0x179b6b1cb6755e31", res) 227 | }) 228 | 229 | t.Run("Run script at block", func(t *testing.T) { 230 | block, err := o.GetLatestBlock(context.Background()) 231 | require.NoError(t, err) 232 | block, err = o.GetBlockAtHeight(context.Background(), block.Height-1) 233 | assert.NoError(t, err) 234 | res, err := o.Script("test", WithArg("account", "first"), WithExecuteScriptAtBlockIdentifier(block.ID)).GetAsInterface() 235 | assert.NoError(t, err) 236 | assert.Equal(t, "0x179b6b1cb6755e31", res) 237 | }) 238 | t.Run("Run script at block hex", func(t *testing.T) { 239 | block, err := o.GetLatestBlock(context.Background()) 240 | require.NoError(t, err) 241 | block, err = o.GetBlockAtHeight(context.Background(), block.Height-1) 242 | assert.NoError(t, err) 243 | res, err := o.Script("test", WithArg("account", "first"), WithExecuteScriptAtBlockIdHex(block.ID.Hex())).GetAsInterface() 244 | assert.NoError(t, err) 245 | assert.Equal(t, "0x179b6b1cb6755e31", res) 246 | }) 247 | } 248 | -------------------------------------------------------------------------------- /scripts/aScript.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | // This is a generic script 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/block.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | access(all) fun main(): UInt64 { 3 | let height = getCurrentBlock().height 4 | log(height) 5 | return height 6 | } 7 | -------------------------------------------------------------------------------- /scripts/emulatorFoo.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | import NonFungibleToken from "../contracts/NonFungibleToken.cdc" 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/mainnetFoo.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | import NonFungibleToken from "../contracts/NonFungibleToken.cdc" 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/mainnetaScript.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | // This is a mainnet specific script 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/mainnetzScript.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | // This is a mainnet specific script 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/test.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | import NonFungibleToken from "../contracts/NonFungibleToken.cdc" 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/testnetFoo.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | import NonFungibleToken from "../contracts/NonFungibleToken.cdc" 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/type.cdc: -------------------------------------------------------------------------------- 1 | // test script to ensure code is running 2 | import FlowToken from "../contracts/FlowToken.cdc" 3 | 4 | access(all) fun main(): Type { 5 | return Type<@FlowToken.Vault>() 6 | } 7 | -------------------------------------------------------------------------------- /scripts/zScript.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | // This is a generic script 3 | 4 | access(all) fun main(account: Address): String { 5 | return getAccount(account).address.toString() 6 | } 7 | -------------------------------------------------------------------------------- /setup_integration_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSetupIntegration(t *testing.T) { 10 | o := Overflow() 11 | t.Run("Should create inmemory emulator client", func(t *testing.T) { 12 | assert.Equal(t, "emulator", o.Network.Name) 13 | }) 14 | 15 | t.Run("should get account", func(t *testing.T) { 16 | account := o.Account("first") 17 | assert.Equal(t, "179b6b1cb6755e31", account.Address.String()) 18 | }) 19 | 20 | t.Run("should get address", func(t *testing.T) { 21 | account := o.Address("first") 22 | assert.Equal(t, "0x179b6b1cb6755e31", account) 23 | }) 24 | 25 | t.Run("panic on wrong account name", func(t *testing.T) { 26 | assert.Panics(t, func() { o.Address("foobar") }) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /setup_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/onflow/flowkit/v2/output" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestOverflowv3(t *testing.T) { 11 | t.Run("WithNetworkEmbedded", func(t *testing.T) { 12 | b := Apply(WithNetwork("embedded")) 13 | assert.Equal(t, "emulator", b.Network) 14 | assert.True(t, b.DeployContracts) 15 | assert.True(t, b.InitializeAccounts) 16 | assert.True(t, b.InMemory) 17 | assert.Equal(t, output.NoneLog, b.LogLevel) 18 | }) 19 | 20 | t.Run("WithNetworkTesting", func(t *testing.T) { 21 | b := Apply(WithNetwork("testing")) 22 | assert.Equal(t, "emulator", b.Network) 23 | assert.True(t, b.DeployContracts) 24 | assert.True(t, b.InitializeAccounts) 25 | assert.True(t, b.InMemory) 26 | assert.Equal(t, output.NoneLog, b.LogLevel) 27 | }) 28 | 29 | t.Run("WithNetworkEmulator", func(t *testing.T) { 30 | b := Apply(WithNetwork("emulator")) 31 | assert.Equal(t, "emulator", b.Network) 32 | assert.True(t, b.DeployContracts) 33 | assert.True(t, b.InitializeAccounts) 34 | assert.False(t, b.InMemory) 35 | }) 36 | 37 | t.Run("WithNetworkTestnet", func(t *testing.T) { 38 | b := Apply(WithNetwork("testnet")) 39 | assert.Equal(t, "testnet", b.Network) 40 | assert.False(t, b.DeployContracts) 41 | assert.False(t, b.InitializeAccounts) 42 | assert.False(t, b.InMemory) 43 | }) 44 | 45 | t.Run("WithNetworkMainnet", func(t *testing.T) { 46 | b := Apply(WithNetwork("mainnet")) 47 | assert.Equal(t, "mainnet", b.Network) 48 | assert.False(t, b.DeployContracts) 49 | assert.False(t, b.InitializeAccounts) 50 | assert.False(t, b.InMemory) 51 | }) 52 | 53 | t.Run("WithInMemory", func(t *testing.T) { 54 | b := Apply() 55 | assert.True(t, b.InMemory) 56 | assert.True(t, b.InitializeAccounts) 57 | assert.True(t, b.DeployContracts) 58 | assert.Equal(t, "emulator", b.Network) 59 | }) 60 | 61 | t.Run("WithExistingEmulator", func(t *testing.T) { 62 | b := Apply(WithExistingEmulator()) 63 | assert.False(t, b.InitializeAccounts) 64 | assert.False(t, b.DeployContracts) 65 | }) 66 | 67 | t.Run("DoNotPrependNetworkToAccountNames", func(t *testing.T) { 68 | b := Apply(WithNoPrefixToAccountNames()) 69 | assert.False(t, b.PrependNetworkName) 70 | }) 71 | 72 | t.Run("WithServiceAccountSuffix", func(t *testing.T) { 73 | b := Apply(WithServiceAccountSuffix("foo")) 74 | assert.Equal(t, "foo", b.ServiceSuffix) 75 | }) 76 | 77 | t.Run("WithBasePath", func(t *testing.T) { 78 | b := Apply(WithBasePath("../")) 79 | assert.Equal(t, "../", b.Path) 80 | }) 81 | 82 | t.Run("WithNoLog", func(t *testing.T) { 83 | b := Apply(WithLogNone()) 84 | assert.Equal(t, output.NoneLog, b.LogLevel) 85 | }) 86 | 87 | t.Run("WithGas", func(t *testing.T) { 88 | b := Apply(WithGas(42)) 89 | assert.Equal(t, 42, b.GasLimit) 90 | }) 91 | 92 | t.Run("WithFlowConfig", func(t *testing.T) { 93 | b := Apply(WithFlowConfig("foo.json", "bar.json")) 94 | assert.Equal(t, []string{"foo.json", "bar.json"}, b.ConfigFiles) 95 | }) 96 | 97 | t.Run("WithScriptFolderName", func(t *testing.T) { 98 | b := Apply(WithScriptFolderName("script")) 99 | assert.Equal(t, "script", b.ScriptFolderName) 100 | }) 101 | 102 | t.Run("WithGlobalPrintOptions", func(t *testing.T) { 103 | b := Apply(WithGlobalPrintOptions(WithoutId())) 104 | assert.Equal(t, 1, len(*b.PrintOptions)) 105 | }) 106 | 107 | t.Run("WithPrintResults", func(t *testing.T) { 108 | b := Apply(WithPrintResults(WithoutId())) 109 | assert.Equal(t, 1, len(*b.PrintOptions)) 110 | }) 111 | 112 | t.Run("WithTransactionFolderName", func(t *testing.T) { 113 | b := Apply(WithTransactionFolderName("tx")) 114 | assert.Equal(t, "tx", b.TransactionFolderName) 115 | }) 116 | 117 | t.Run("Overflow panics", func(t *testing.T) { 118 | assert.Panics(t, func() { 119 | Overflow(WithFlowConfig("nonexistant.json")) 120 | }) 121 | }) 122 | } 123 | 124 | func Apply(opt ...OverflowOption) *OverflowBuilder { 125 | return defaultOverflowBuilder.applyOptions(opt) 126 | } 127 | -------------------------------------------------------------------------------- /sign.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | 7 | "github.com/onflow/flow-go-sdk" 8 | ) 9 | 10 | // Sign a user message 11 | func (o *OverflowState) SignUserMessage(account string, message string) (string, error) { 12 | 13 | a, err := o.AccountE(account) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | signer, err := a.Key.Signer(context.Background()) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | signature, err := flow.SignUserMessage(signer, []byte(message)) 24 | if err != nil { 25 | return "", err 26 | } 27 | return hex.EncodeToString(signature), nil 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sign_integration_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestSignIntegration(t *testing.T) { 11 | g, err := OverflowTesting() 12 | require.NoError(t, err) 13 | require.NotNil(t, g) 14 | 15 | t.Run("fail on missing signer", func(t *testing.T) { 16 | _, err := g.SignUserMessage("foobar", "baaaaaaaaanzaaaai") 17 | assert.ErrorContains(t, err, "could not find account with name emulator-foobar") 18 | }) 19 | 20 | t.Run("should sign message", func(t *testing.T) { 21 | result, err := g.SignUserMessage("account", "overflow") 22 | assert.NoError(t, err) 23 | assert.Equal(t, 128, len(result)) 24 | }) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /stop_on_failure_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPanicIfStopOnFailure(t *testing.T) { 10 | o, err := OverflowTesting(WithPanicOnError()) 11 | assert.NoError(t, err) 12 | 13 | t.Run("transaction", func(t *testing.T) { 14 | assert.PanicsWithError(t, "💩 You need to set the proposer signer", func() { 15 | o.Tx("create_nft_collection") 16 | }) 17 | }) 18 | 19 | t.Run("script", func(t *testing.T) { 20 | 21 | assert.PanicsWithError(t, "💩 Could not read interaction file from path=./scripts/asdf.cdc", func() { 22 | o.Script("asdf") 23 | }) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /templates.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | ) 7 | 8 | // Templates 9 | // 10 | // A list of functions you can use to perform some common operations 11 | 12 | // UploadFile reads a file, base64 encodes it and chunk upload to /storage/upload 13 | func (o *OverflowState) UploadFile(filename string, accountName string) error { 14 | content, err := fileAsBase64(filename) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | return o.UploadString(content, accountName) 20 | } 21 | 22 | // DownloadAndUploadFile reads a file, base64 encodes it and chunk upload to /storage/upload 23 | func (o *OverflowState) DownloadAndUploadFile(url string, accountName string) error { 24 | body, err := getUrl(url) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | encoded := base64.StdEncoding.EncodeToString(body) 30 | return o.UploadString(encoded, accountName) 31 | } 32 | 33 | // DownloadImageAndUploadAsDataUrl download an image and upload as data url 34 | func (o *OverflowState) DownloadImageAndUploadAsDataUrl(url, accountName string) error { 35 | body, err := getUrl(url) 36 | if err != nil { 37 | return err 38 | } 39 | content := contentAsImageDataUrl(body) 40 | 41 | return o.UploadString(content, accountName) 42 | } 43 | 44 | // UploadImageAsDataUrl will upload a image file from the filesystem into /storage/upload of the given account 45 | func (o *OverflowState) UploadImageAsDataUrl(filename string, accountName string) error { 46 | content, err := fileAsImageData(filename) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return o.UploadString(content, accountName) 52 | } 53 | 54 | // UploadString will upload the given string data in 1mb chunkts to /storage/upload of the given account 55 | func (o *OverflowState) UploadString(content string, accountName string) error { 56 | // unload previous content if any. 57 | res := o.Tx(` 58 | transaction { 59 | prepare(signer: auth(LoadValue) &Account) { 60 | let path = /storage/upload 61 | let existing = signer.storage.load(from: path) ?? "" 62 | log(existing) 63 | } 64 | } 65 | `, WithSigner(accountName)) 66 | if res.Err != nil { 67 | return res.Err 68 | } 69 | 70 | parts := splitByWidthMake(content, 1_000_000) 71 | for _, part := range parts { 72 | res := o.Tx(` 73 | transaction(part: String) { 74 | prepare(signer: auth(Storage) &Account) { 75 | let path = /storage/upload 76 | let existing = signer.storage.load(from: path) ?? "" 77 | signer.storage.save(existing.concat(part), to: path) 78 | log(signer.address.toString()) 79 | log(part) 80 | } 81 | } 82 | `, WithSigner(accountName), WithArg("part", part)) 83 | if res.Err != nil { 84 | return res.Err 85 | } 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // Get the free capacity in an account 92 | func (o *OverflowState) GetFreeCapacity(accountName string) int { 93 | result := o.Script(` 94 | access(all) fun main(user:Address): UInt64{ 95 | let account=getAccount(user) 96 | return account.storage.capacity- account.storage.used 97 | } 98 | `, WithArg("user", accountName)) 99 | 100 | value, ok := result.Output.(uint64) 101 | if !ok { 102 | panic("Type conversion of free capacity failed") 103 | } 104 | return int(value) 105 | } 106 | 107 | func (o *OverflowState) MintFlowTokens(accountName string, amount float64) *OverflowState { 108 | if o.Network.Name != "emulator" { 109 | o.Error = fmt.Errorf("can only mint new flow on emulator") 110 | return o 111 | } 112 | result := o.Tx(` 113 | import FungibleToken from 0xee82856bf20e2aa6 114 | import FlowToken from 0x0ae53cb6e3f42a79 115 | 116 | 117 | transaction(recipient: Address, amount: UFix64) { 118 | let tokenAdmin: &FlowToken.Administrator 119 | let tokenReceiver: &{FungibleToken.Receiver} 120 | 121 | prepare(signer: auth(BorrowValue) &Account) { 122 | self.tokenAdmin = signer.storage 123 | .borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) 124 | ?? panic("Signer is not the token admin") 125 | 126 | self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) 127 | ?? panic("Unable to borrow receiver reference") 128 | } 129 | 130 | execute { 131 | let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) 132 | let mintedVault <- minter.mintTokens(amount: amount) 133 | 134 | self.tokenReceiver.deposit(from: <-mintedVault) 135 | 136 | destroy minter 137 | } 138 | } 139 | `, WithSignerServiceAccount(), 140 | WithArg("recipient", accountName), 141 | WithArg("amount", amount), 142 | WithName(fmt.Sprintf("Startup Mint tokens for %s", accountName)), 143 | WithoutLog(), 144 | ) 145 | 146 | if result.Err != nil { 147 | o.Error = result.Err 148 | } 149 | return o 150 | } 151 | 152 | // A method to fill up a users storage, useful when testing 153 | // This has some issues with transaction fees 154 | func (o *OverflowState) FillUpStorage(accountName string) *OverflowState { 155 | capacity := o.GetFreeCapacity(accountName) 156 | length := capacity - 50500 // we cannot fill up all of storage since we need flow to pay for the transaction that fills it up 157 | 158 | err := o.UploadString(randomString(length), accountName) 159 | if err != nil { 160 | o.Error = err 161 | } 162 | return o 163 | } 164 | -------------------------------------------------------------------------------- /testdata/TestArguments/AccountArray.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.Address{ 4 | 1, 5 | 207, 6 | 14, 7 | 47, 8 | 47, 9 | 113, 10 | 84, 11 | 80, 12 | }, 13 | cadence.Address{ 14 | 23, 15 | 155, 16 | 107, 17 | 28, 18 | 182, 19 | 117, 20 | 94, 21 | 49, 22 | }, 23 | }, 24 | }} 25 | -------------------------------------------------------------------------------- /testdata/TestArguments/Address.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Address{ 2 | 1, 3 | 207, 4 | 14, 5 | 47, 6 | 47, 7 | 113, 8 | 84, 9 | 80, 10 | }} 11 | -------------------------------------------------------------------------------- /testdata/TestArguments/Paths.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{ 2 | cadence.Path{ 3 | Domain: "public", 4 | Identifier: "foo", 5 | }, 6 | cadence.Path{ 7 | Domain: "storage", 8 | Identifier: "foo", 9 | }, 10 | cadence.Path{ 11 | Domain: "private", 12 | Identifier: "foo", 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /testdata/TestArguments/Raw_address.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Address{ 2 | 1, 3 | 207, 4 | 14, 5 | 47, 6 | 47, 7 | 113, 8 | 84, 9 | 80, 10 | }} 11 | -------------------------------------------------------------------------------- /testdata/TestArguments/ScalarMap.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Dictionary{ 2 | Pairs: []cadence.KeyValuePair{cadence.KeyValuePair{ 3 | Key: cadence.String("foo"), 4 | Value: cadence.UFix64(1.00000000), 5 | }}, 6 | }} 7 | -------------------------------------------------------------------------------- /testdata/TestArguments/ScalarMapArray.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.Dictionary{ 4 | Pairs: []cadence.KeyValuePair{cadence.KeyValuePair{ 5 | Key: cadence.String("Sith"), 6 | Value: cadence.UFix64(2.00000000), 7 | }}, 8 | }, 9 | cadence.Dictionary{Pairs: []cadence.KeyValuePair{cadence.KeyValuePair{ 10 | Key: cadence.String("Jedi"), 11 | Value: cadence.UFix64(1000.00000000), 12 | }}}, 13 | }, 14 | }} 15 | -------------------------------------------------------------------------------- /testdata/TestArguments/StringArray.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.String("foo"), 4 | cadence.String("bar"), 5 | }, 6 | }} 7 | -------------------------------------------------------------------------------- /testdata/TestArguments/StringMap.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Dictionary{ 2 | Pairs: []cadence.KeyValuePair{cadence.KeyValuePair{ 3 | Key: cadence.String("foo"), 4 | Value: cadence.String("bar"), 5 | }}, 6 | }} 7 | -------------------------------------------------------------------------------- /testdata/TestArguments/StringMapArray.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.Dictionary{ 4 | Pairs: []cadence.KeyValuePair{cadence.KeyValuePair{ 5 | Key: cadence.String("Sith"), 6 | Value: cadence.String("Darth Vader"), 7 | }}, 8 | }, 9 | cadence.Dictionary{Pairs: []cadence.KeyValuePair{cadence.KeyValuePair{ 10 | Key: cadence.String("Jedi"), 11 | Value: cadence.String("Luke"), 12 | }}}, 13 | }, 14 | }} 15 | -------------------------------------------------------------------------------- /testdata/TestArguments/UFix64Array.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.UFix64(1.00000000), 4 | cadence.UFix64(2.00000000), 5 | cadence.UFix64(3.00000000), 6 | }, 7 | }} 8 | -------------------------------------------------------------------------------- /testdata/TestArguments/Uint64Array.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.UInt64(1), 4 | cadence.UInt64(2), 5 | cadence.UInt64(3), 6 | }, 7 | }} 8 | -------------------------------------------------------------------------------- /testdata/TestArguments/Uint8Array.golden: -------------------------------------------------------------------------------- 1 | []cadence.Value{cadence.Array{ 2 | Values: []cadence.Value{ 3 | cadence.UInt8(1), 4 | cadence.UInt8(2), 5 | cadence.UInt8(3), 6 | }, 7 | }} 8 | -------------------------------------------------------------------------------- /testdata/TestCheckContractUpdate/Should_return_the_updatable_contracts.golden: -------------------------------------------------------------------------------- 1 | map[string]map[string]bool{"account": { 2 | "Debug": false, 3 | "MetadataViews": false, 4 | "NonFungibleToken": false, 5 | }} 6 | -------------------------------------------------------------------------------- /testdata/TestCheckContractUpdate/Should_return_the_updatable_contracts_(updatable).golden: -------------------------------------------------------------------------------- 1 | map[string]map[string]bool{"account": { 2 | "Debug": true, 3 | "MetadataViews": false, 4 | "NonFungibleToken": false, 5 | }} 6 | -------------------------------------------------------------------------------- /testdata/contracts/Debug.cdc: -------------------------------------------------------------------------------- 1 | import "NonFungibleToken" 2 | 3 | access(all) contract Debug { 4 | 5 | access(all) struct FooListBar { 6 | access(all) let foo:[Foo2] 7 | access(all) let bar:String 8 | 9 | init(foo:[Foo2], bar:String) { 10 | self.foo=foo 11 | self.bar=bar 12 | } 13 | } 14 | access(all) struct FooBar { 15 | access(all) let foo:Foo 16 | access(all) let bar:String 17 | 18 | init(foo:Foo, bar:String) { 19 | self.foo=foo 20 | self.bar=bar 21 | } 22 | } 23 | 24 | 25 | access(all) struct Foo2{ 26 | access(all) let bar: Address 27 | 28 | init(bar: Address) { 29 | self.bar=bar 30 | } 31 | } 32 | 33 | access(all) struct Foo{ 34 | access(all) let bar: String 35 | 36 | init(bar: String) { 37 | self.bar=bar 38 | } 39 | } 40 | 41 | access(all) event Log(msg: String) 42 | access(all) event LogNum(id: UInt64) 43 | 44 | access(all) fun id(_ id:UInt64) { 45 | emit LogNum(id:id) 46 | } 47 | 48 | access(all) fun log(_ msg: String) : String { 49 | emit Log(msg: msg) 50 | return msg 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /testdata/contracts/Debug2.cdc: -------------------------------------------------------------------------------- 1 | import "NonFungibleToken" 2 | 3 | access(all) contract Debug2 { 4 | 5 | access(all) struct FooListBar { 6 | access(all) let foo:[Foo2] 7 | access(all) let bar:String 8 | 9 | init(foo:[Foo2], bar:String) { 10 | self.foo=foo 11 | self.bar=bar 12 | } 13 | } 14 | access(all) struct FooBar { 15 | access(all) let foo:Foo 16 | access(all) let bar:String 17 | 18 | init(foo:Foo, bar:String) { 19 | self.foo=foo 20 | self.bar=bar 21 | } 22 | } 23 | 24 | 25 | access(all) struct Foo2{ 26 | access(all) let bar: Address 27 | 28 | init(bar: Address) { 29 | self.bar=bar 30 | } 31 | } 32 | 33 | access(all) struct Foo{ 34 | access(all) let bar: String 35 | 36 | init(bar: String) { 37 | self.bar=bar 38 | } 39 | } 40 | 41 | access(all) event Log(msg: String) 42 | access(all) event LogNum(id: UInt64) 43 | 44 | access(all) fun id(_ id:UInt64) { 45 | emit LogNum(id:id) 46 | } 47 | 48 | access(all) fun log(_ msg: String) : String { 49 | emit Log(msg: msg) 50 | return msg 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /testdata/contracts/FungibleToken.cdc: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | # The Flow Fungible Token standard 4 | 5 | ## `FungibleToken` contract interface 6 | 7 | The interface that all Fungible Token contracts would have to conform to. 8 | If a users wants to deploy a new token contract, their contract 9 | would need to implement the FungibleToken interface. 10 | 11 | Their contract would have to follow all the rules and naming 12 | that the interface specifies. 13 | 14 | ## `Vault` resource 15 | 16 | Each account that owns tokens would need to have an instance 17 | of the Vault resource stored in their account storage. 18 | 19 | The Vault resource has methods that the owner and other users can call. 20 | 21 | ## `Provider`, `Receiver`, and `Balance` resource interfaces 22 | 23 | These interfaces declare pre-conditions and post-conditions that restrict 24 | the execution of the functions in the Vault. 25 | 26 | They are separate because it gives the user the ability to share 27 | a reference to their Vault that only exposes the fields functions 28 | in one or more of the interfaces. 29 | 30 | It also gives users the ability to make custom resources that implement 31 | these interfaces to do various things with the tokens. 32 | For example, a faucet can be implemented by conforming 33 | to the Provider interface. 34 | 35 | By using resources and interfaces, users of Fungible Token contracts 36 | can send and receive tokens peer-to-peer, without having to interact 37 | with a central ledger smart contract. To send tokens to another user, 38 | a user would simply withdraw the tokens from their Vault, then call 39 | the deposit function on another user's Vault to complete the transfer. 40 | 41 | */ 42 | 43 | /// The interface that Fungible Token contracts implement. 44 | /// 45 | access(all) contract interface FungibleToken { 46 | 47 | // An entitlement for allowing the withdrawal of tokens from a Vault 48 | access(all) entitlement Withdrawable 49 | 50 | /// The total number of tokens in existence. 51 | /// It is up to the implementer to ensure that the total supply 52 | /// stays accurate and up to date 53 | access(all) var totalSupply: UFix64 54 | 55 | /// The event that is emitted when the contract is created 56 | access(all) event TokensInitialized(initialSupply: UFix64) 57 | 58 | /// The event that is emitted when tokens are withdrawn from a Vault 59 | access(all) event TokensWithdrawn(amount: UFix64, from: Address?) 60 | 61 | /// The event that is emitted when tokens are deposited into a Vault 62 | access(all) event TokensDeposited(amount: UFix64, to: Address?) 63 | 64 | /// The interface that enforces the requirements for withdrawing 65 | /// tokens from the implementing type. 66 | /// 67 | /// It does not enforce requirements on `balance` here, 68 | /// because it leaves open the possibility of creating custom providers 69 | /// that do not necessarily need their own balance. 70 | /// 71 | access(all) resource interface Provider { 72 | 73 | /// Subtracts tokens from the owner's Vault 74 | /// and returns a Vault with the removed tokens. 75 | /// 76 | /// The function's access level is public, but this is not a problem 77 | /// because only the owner storing the resource in their account 78 | /// can initially call this function. 79 | /// 80 | /// The owner may grant other accounts access by creating a private 81 | /// capability that allows specific other users to access 82 | /// the provider resource through a reference. 83 | /// 84 | /// The owner may also grant all accounts access by creating a public 85 | /// capability that allows all users to access the provider 86 | /// resource through a reference. 87 | /// 88 | /// @param amount: The amount of tokens to be withdrawn from the vault 89 | /// @return The Vault resource containing the withdrawn funds 90 | /// 91 | access(Withdrawable) fun withdraw(amount: UFix64): @Vault { 92 | post { 93 | // `result` refers to the return value 94 | result.balance == amount: 95 | "Withdrawal amount must be the same as the balance of the withdrawn Vault" 96 | } 97 | } 98 | } 99 | 100 | /// The interface that enforces the requirements for depositing 101 | /// tokens into the implementing type. 102 | /// 103 | /// We do not include a condition that checks the balance because 104 | /// we want to give users the ability to make custom receivers that 105 | /// can do custom things with the tokens, like split them up and 106 | /// send them to different places. 107 | /// 108 | access(all) resource interface Receiver { 109 | 110 | /// Takes a Vault and deposits it into the implementing resource type 111 | /// 112 | /// @param from: The Vault resource containing the funds that will be deposited 113 | /// 114 | access(all) fun deposit(from: @Vault) 115 | 116 | /// Below is referenced from the FLIP #69 https://github.com/onflow/flips/blob/main/flips/20230206-fungible-token-vault-type-discovery.md 117 | /// 118 | /// Returns the dictionary of Vault types that the the receiver is able to accept in its `deposit` method 119 | /// this then it would return `{Type<@FlowToken.Vault>(): true}` and if any custom receiver 120 | /// uses the default implementation then it would return empty dictionary as its parent 121 | /// resource doesn't conform with the `FungibleToken.Vault` resource. 122 | /// 123 | /// Custom receiver implementations are expected to upgrade their contracts to add an implementation 124 | /// that supports this method because it is very valuable for various applications to have. 125 | /// 126 | /// @return dictionary of supported deposit vault types by the implementing resource. 127 | /// 128 | access(all) view fun getSupportedVaultTypes(): {Type: Bool} { 129 | // Below check is implemented to make sure that run-time type would 130 | // only get returned when the parent resource conforms with `FungibleToken.Vault`. 131 | if self.getType().isSubtype(of: Type<@FungibleToken.Vault>()) { 132 | return {self.getType(): true} 133 | } else { 134 | // Return an empty dictionary as the default value for resource who don't 135 | // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc. 136 | return {} 137 | } 138 | } 139 | } 140 | 141 | /// The interface that contains the `balance` field of the Vault 142 | /// and enforces that when new Vaults are created, the balance 143 | /// is initialized correctly. 144 | /// 145 | access(all) resource interface Balance { 146 | 147 | /// The total balance of a vault 148 | /// 149 | access(all) var balance: UFix64 150 | 151 | init(balance: UFix64) { 152 | post { 153 | self.balance == balance: 154 | "Balance must be initialized to the initial balance" 155 | } 156 | } 157 | 158 | /// Function that returns all the Metadata Views implemented by a Fungible Token 159 | /// 160 | /// @return An array of Types defining the implemented views. This value will be used by 161 | /// developers to know which parameter to pass to the resolveView() method. 162 | /// 163 | access(all) view fun getViews(): [Type] { 164 | return [] 165 | } 166 | 167 | /// Function that resolves a metadata view for this fungible token by type. 168 | /// 169 | /// @param view: The Type of the desired view. 170 | /// @return A structure representing the requested view. 171 | /// 172 | access(all) fun resolveView(_ view: Type): AnyStruct? { 173 | return nil 174 | } 175 | } 176 | 177 | /// The resource that contains the functions to send and receive tokens. 178 | /// The declaration of a concrete type in a contract interface means that 179 | /// every Fungible Token contract that implements the FungibleToken interface 180 | /// must define a concrete `Vault` resource that conforms to the `Provider`, `Receiver`, 181 | /// and `Balance` interfaces, and declares their required fields and functions 182 | /// 183 | access(all) resource Vault: Provider, Receiver, Balance { 184 | 185 | /// The total balance of the vault 186 | access(all) var balance: UFix64 187 | 188 | // The conforming type must declare an initializer 189 | // that allows providing the initial balance of the Vault 190 | // 191 | init(balance: UFix64) 192 | 193 | /// Subtracts `amount` from the Vault's balance 194 | /// and returns a new Vault with the subtracted balance 195 | /// 196 | /// @param amount: The amount of tokens to be withdrawn from the vault 197 | /// @return The Vault resource containing the withdrawn funds 198 | /// 199 | access(Withdrawable) fun withdraw(amount: UFix64): @Vault { 200 | pre { 201 | self.balance >= amount: 202 | "Amount withdrawn must be less than or equal than the balance of the Vault" 203 | } 204 | post { 205 | // use the special function `before` to get the value of the `balance` field 206 | // at the beginning of the function execution 207 | // 208 | self.balance == before(self.balance) - amount: 209 | "New Vault balance must be the difference of the previous balance and the withdrawn Vault" 210 | } 211 | } 212 | 213 | /// Takes a Vault and deposits it into the implementing resource type 214 | /// 215 | /// @param from: The Vault resource containing the funds that will be deposited 216 | /// 217 | access(all) fun deposit(from: @Vault) { 218 | // Assert that the concrete type of the deposited vault is the same 219 | // as the vault that is accepting the deposit 220 | pre { 221 | from.isInstance(self.getType()): 222 | "Cannot deposit an incompatible token type" 223 | } 224 | post { 225 | self.balance == before(self.balance) + before(from.balance): 226 | "New Vault balance must be the sum of the previous balance and the deposited Vault" 227 | } 228 | } 229 | } 230 | 231 | /// Allows any user to create a new Vault that has a zero balance 232 | /// 233 | /// @return The new Vault resource 234 | /// 235 | access(all) fun createEmptyVault(): @Vault { 236 | post { 237 | result.balance == 0.0: "The newly created Vault must have zero balance" 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /testdata/contracts/NonFungibleToken.cdc: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | ## The Flow Non-Fungible Token standard 4 | 5 | ## `NonFungibleToken` contract interface 6 | 7 | The interface that all Non-Fungible Token contracts could conform to. 8 | If a user wants to deploy a new NFT contract, their contract would need 9 | to implement the NonFungibleToken interface. 10 | 11 | Their contract would have to follow all the rules and naming 12 | that the interface specifies. 13 | 14 | ## `NFT` resource 15 | 16 | The core resource type that represents an NFT in the smart contract. 17 | 18 | ## `Collection` Resource 19 | 20 | The resource that stores a user's NFT collection. 21 | It includes a few functions to allow the owner to easily 22 | move tokens in and out of the collection. 23 | 24 | ## `Provider` and `Receiver` resource interfaces 25 | 26 | These interfaces declare functions with some pre and post conditions 27 | that require the Collection to follow certain naming and behavior standards. 28 | 29 | They are separate because it gives the user the ability to share a reference 30 | to their Collection that only exposes the fields and functions in one or more 31 | of the interfaces. It also gives users the ability to make custom resources 32 | that implement these interfaces to do various things with the tokens. 33 | 34 | By using resources and interfaces, users of NFT smart contracts can send 35 | and receive tokens peer-to-peer, without having to interact with a central ledger 36 | smart contract. 37 | 38 | To send an NFT to another user, a user would simply withdraw the NFT 39 | from their Collection, then call the deposit function on another user's 40 | Collection to complete the transfer. 41 | 42 | */ 43 | 44 | /// The main NFT contract interface. Other NFT contracts will 45 | /// import and implement this interface 46 | /// 47 | access(all) contract interface NonFungibleToken { 48 | 49 | // An entitlement for allowing the withdrawal of tokens from a Vault 50 | access(all) entitlement Withdrawable 51 | 52 | /// The total number of tokens of this type in existence 53 | access(all) var totalSupply: UInt64 54 | 55 | /// Event that emitted when the NFT contract is initialized 56 | /// 57 | access(all) event ContractInitialized() 58 | 59 | /// Event that is emitted when a token is withdrawn, 60 | /// indicating the owner of the collection that it was withdrawn from. 61 | /// 62 | /// If the collection is not in an account's storage, `from` will be `nil`. 63 | /// 64 | access(all) event Withdraw(id: UInt64, from: Address?) 65 | 66 | /// Event that emitted when a token is deposited to a collection. 67 | /// 68 | /// It indicates the owner of the collection that it was deposited to. 69 | /// 70 | access(all) event Deposit(id: UInt64, to: Address?) 71 | 72 | /// Interface that the NFTs have to conform to 73 | /// The metadata views methods are included here temporarily 74 | /// because enforcing the metadata interfaces in the standard 75 | /// would break many contracts in an upgrade. Those breaking changes 76 | /// are being saved for the stable cadence milestone 77 | /// 78 | access(all) resource interface INFT { 79 | /// The unique ID that each NFT has 80 | access(all) let id: UInt64 81 | 82 | /// Function that returns all the Metadata Views implemented by a Non Fungible Token 83 | /// 84 | /// @return An array of Types defining the implemented views. This value will be used by 85 | /// developers to know which parameter to pass to the resolveView() method. 86 | /// 87 | access(all) view fun getViews(): [Type] { 88 | return [] 89 | } 90 | 91 | /// Function that resolves a metadata view for this token. 92 | /// 93 | /// @param view: The Type of the desired view. 94 | /// @return A structure representing the requested view. 95 | /// 96 | access(all) fun resolveView(_ view: Type): AnyStruct? { 97 | return nil 98 | } 99 | } 100 | 101 | /// Requirement that all conforming NFT smart contracts have 102 | /// to define a resource called NFT that conforms to INFT 103 | /// 104 | access(all) resource NFT: INFT { 105 | access(all) let id: UInt64 106 | } 107 | 108 | /// Interface to mediate withdraws from the Collection 109 | /// 110 | access(all) resource interface Provider { 111 | /// Removes an NFT from the resource implementing it and moves it to the caller 112 | /// 113 | /// @param withdrawID: The ID of the NFT that will be removed 114 | /// @return The NFT resource removed from the implementing resource 115 | /// 116 | access(Withdrawable) fun withdraw(withdrawID: UInt64): @NFT { 117 | post { 118 | result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" 119 | } 120 | } 121 | } 122 | 123 | /// Interface to mediate deposits to the Collection 124 | /// 125 | access(all) resource interface Receiver { 126 | 127 | /// Adds an NFT to the resource implementing it 128 | /// 129 | /// @param token: The NFT resource that will be deposited 130 | /// 131 | access(all) fun deposit(token: @NFT) 132 | } 133 | 134 | /// Interface that an account would commonly 135 | /// publish for their collection 136 | /// 137 | access(all) resource interface CollectionPublic { 138 | access(all) fun deposit(token: @NFT) 139 | access(all) view fun getIDs(): [UInt64] 140 | access(all) view fun borrowNFT(id: UInt64): &NFT 141 | /// Safe way to borrow a reference to an NFT that does not panic 142 | /// 143 | /// @param id: The ID of the NFT that want to be borrowed 144 | /// @return An optional reference to the desired NFT, will be nil if the passed id does not exist 145 | /// 146 | access(all) view fun borrowNFTSafe(id: UInt64): &NFT? { 147 | post { 148 | result == nil || result!.id == id: "The returned reference's ID does not match the requested ID" 149 | } 150 | return nil 151 | } 152 | } 153 | 154 | /// Requirement for the concrete resource type 155 | /// to be declared in the implementing contract 156 | /// 157 | access(all) resource Collection: Provider, Receiver, CollectionPublic { 158 | 159 | /// Dictionary to hold the NFTs in the Collection 160 | access(all) var ownedNFTs: @{UInt64: NFT} 161 | 162 | /// Removes an NFT from the collection and moves it to the caller 163 | /// 164 | /// @param withdrawID: The ID of the NFT that will be withdrawn 165 | /// @return The resource containing the desired NFT 166 | /// 167 | access(Withdrawable) fun withdraw(withdrawID: UInt64): @NFT 168 | 169 | /// Takes a NFT and adds it to the collections dictionary 170 | /// and adds the ID to the ID array 171 | /// 172 | /// @param token: An NFT resource 173 | /// 174 | access(all) fun deposit(token: @NFT) 175 | 176 | /// Returns an array of the IDs that are in the collection 177 | /// 178 | /// @return An array containing all the IDs on the collection 179 | /// 180 | access(all) view fun getIDs(): [UInt64] 181 | 182 | /// Returns a borrowed reference to an NFT in the collection 183 | /// so that the caller can read data and call methods from it 184 | /// 185 | /// @param id: The ID of the NFT that want to be borrowed 186 | /// @return A reference to the NFT 187 | /// 188 | access(all) view fun borrowNFT(id: UInt64): &NFT { 189 | pre { 190 | self.ownedNFTs[id] != nil: "NFT does not exist in the collection!" 191 | } 192 | } 193 | } 194 | 195 | /// Creates an empty Collection and returns it to the caller so that they can own NFTs 196 | /// 197 | /// @return A new Collection resource 198 | /// 199 | access(all) fun createEmptyCollection(): @Collection { 200 | post { 201 | result.getIDs().length == 0: "The created collection must be empty!" 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /testdata/contracts/ViewResolver.cdc: -------------------------------------------------------------------------------- 1 | // Taken from the NFT Metadata standard, this contract exposes an interface to let 2 | // anyone borrow a contract and resolve views on it. 3 | // 4 | // This will allow you to obtain information about a contract without necessarily knowing anything about it. 5 | // All you need is its address and name and you're good to go! 6 | access(all) contract interface ViewResolver { 7 | /// Function that returns all the Metadata Views implemented by the resolving contract 8 | /// 9 | /// @return An array of Types defining the implemented views. This value will be used by 10 | /// developers to know which parameter to pass to the resolveView() method. 11 | /// 12 | access(all) view fun getViews(): [Type] { 13 | return [] 14 | } 15 | 16 | /// Function that resolves a metadata view for this token. 17 | /// 18 | /// @param view: The Type of the desired view. 19 | /// @return A structure representing the requested view. 20 | /// 21 | access(all) fun resolveView(_ view: Type): AnyStruct? { 22 | return nil 23 | } 24 | 25 | /// Provides access to a set of metadata views. A struct or 26 | /// resource (e.g. an NFT) can implement this interface to provide access to 27 | /// the views that it supports. 28 | /// 29 | access(all) resource interface Resolver { 30 | access(all) view fun getViews(): [Type] { 31 | return [] 32 | } 33 | access(all) fun resolveView(_ view: Type): AnyStruct? { 34 | return nil 35 | } 36 | } 37 | 38 | /// A group of view resolvers indexed by ID. 39 | /// 40 | access(all) resource interface ResolverCollection { 41 | access(all) view fun borrowViewResolver(id: UInt64): &{Resolver}? { 42 | pre { true: "dummy" } 43 | } 44 | access(all) view fun getIDs(): [UInt64] { 45 | pre { true: "dummy" } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /testdata/flow-with-multiple-deployments.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "default": { 4 | "port": 3569, 5 | "serviceAccount": "emulator-account" 6 | } 7 | }, 8 | "contracts": { 9 | "Debug": "./contracts/Debug.cdc", 10 | "Debug2": "./contracts/Debug2.cdc", 11 | "FlowToken": { 12 | "source": "./contracts/FlowToken.cdc", 13 | "aliases": { 14 | "emulator": "0x0ae53cb6e3f42a79", 15 | "testnet": "0x7e60df042a9c0868", 16 | "mainnet": "0x1654653399040a61" 17 | } 18 | }, 19 | "FungibleToken": { 20 | "source": "./contracts/FungibleToken.cdc", 21 | "aliases": { 22 | "emulator": "0xee82856bf20e2aa6", 23 | "testnet": "0x9a0766d93b6608b7", 24 | "mainnet": "0xf233dcee88fe0abe" 25 | } 26 | }, 27 | "NonFungibleToken": { 28 | "source": "./contracts/NonFungibleToken.cdc", 29 | "aliases": { 30 | "testnet": "0x631e88ae7f1d7c20", 31 | "mainnet": "0x1d7e57aa55817448", 32 | "emulator": "0xf8d6e0586b0a20c7" 33 | } 34 | }, 35 | "MetadataViews": { 36 | "source": "./contracts/MetadataViews.cdc", 37 | "aliases": { 38 | "testnet": "0x631e88ae7f1d7c20", 39 | "mainnet": "0x1d7e57aa55817448", 40 | "emulator": "0xf8d6e0586b0a20c7" 41 | } 42 | }, 43 | "ViewResolver": { 44 | "source": "./contracts/ViewResolver.cdc", 45 | "aliases": { 46 | "testnet": "0x631e88ae7f1d7c20", 47 | "mainnet": "0x1d7e57aa55817448", 48 | "emulator": "0xf8d6e0586b0a20c7" 49 | } 50 | } 51 | }, 52 | "networks": { 53 | "emulator": "127.0.0.1:3569", 54 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 55 | "testnet": "access.devnet.nodes.onflow.org:9000" 56 | }, 57 | "accounts": { 58 | "emulator-account": { 59 | "address": "f8d6e0586b0a20c7", 60 | "key": "dc0097a6b58533e56af78c955e7b0c0f386b5f44f22b75c390beab7fcb1af13f" 61 | }, 62 | "emulator-first": { 63 | "address": "0x179b6b1cb6755e31", 64 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 65 | }, 66 | "emulator-second": { 67 | "address": "0xf3fcd2c1a78f5eee", 68 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 69 | }, 70 | "emulator-3": { 71 | "address": "0xe03daebed8ca0615", 72 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 73 | }, 74 | "emulator-4": { 75 | "address": "0x045a1763c93006ca", 76 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 77 | } 78 | }, 79 | "deployments": { 80 | "emulator": { 81 | "emulator-account": [ 82 | "Debug" 83 | ], 84 | "emulator-first": [ 85 | "Debug2" 86 | ], 87 | "emulator-second": [] 88 | }, 89 | "testnet": { 90 | "emulator-first": [ 91 | "Debug" 92 | ] 93 | }, 94 | "mainnet": { 95 | "emulator-second": [ 96 | "Debug" 97 | ] 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /testdata/graffle-event.golden: -------------------------------------------------------------------------------- 1 | map[string]interface{}{"amount": 10} 2 | -------------------------------------------------------------------------------- /testdata/invalid_account_in_deployment.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "emulators": { 4 | "default": { 5 | "port": 3569, 6 | "serviceAccount": "emulator-account" 7 | } 8 | }, 9 | "contracts": { 10 | "Debug": "./contracts/Debug.cdc", 11 | "FlowToken": { 12 | "source": "./contracts/FlowToken.cdc", 13 | "aliases": { 14 | "emulator": "0x0ae53cb6e3f42a79", 15 | "testnet": "0x7e60df042a9c0868" 16 | } 17 | }, 18 | "FungibleToken": { 19 | "source": "./contracts/FungibleToken.cdc", 20 | "aliases": { 21 | "emulator": "0xee82856bf20e2aa6", 22 | "testnet": "0x9a0766d93b6608b7" 23 | } 24 | }, 25 | "NonFungibleToken": { 26 | "source": "./contracts/NonFungibleToken.cdc", 27 | "aliases": { 28 | "testnet": "0x631e88ae7f1d7c20" 29 | } 30 | } 31 | }, 32 | "networks": { 33 | "emulator": "127.0.0.1:3569", 34 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 35 | "testnet": "access.devnet.nodes.onflow.org:9000" 36 | }, 37 | "accounts": { 38 | "emulator-account": { 39 | "address": "f8d6e0586b0a20c7", 40 | "key": "dc0097a6b58533e56af78c955e7b0c0f386b5f44f22b75c390beab7fcb1af13f" 41 | }, 42 | "emulator-first": { 43 | "address": "01cf0e2f2f715450", 44 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 45 | }, 46 | "emulator-second": { 47 | "address": "179b6b1cb6755e31", 48 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 49 | }, 50 | "emulator-3": { 51 | "address": "f3fcd2c1a78f5eee", 52 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 53 | }, 54 | "emulator-4": { 55 | "address": "e03daebed8ca0615", 56 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 57 | }, 58 | "emulator-5": { 59 | "address": "045a1763c93006ca", 60 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 61 | } 62 | }, 63 | "deployments": { 64 | "emulator": { 65 | "emulator-account": [ 66 | "NonFungibleToken", 67 | "Debug" 68 | ], 69 | "emulator-firs": [], 70 | "emulator-second": [] 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /testdata/invalid_env_flow.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "emulators": { 4 | "default": { 5 | "port": 3569, 6 | "serviceAccount": "emulator-account" 7 | } 8 | }, 9 | "contracts": { 10 | "Debug": "./contracts/Debug.cdc", 11 | "FlowToken": { 12 | "source": "./contracts/FlowToken.cdc", 13 | "aliases": { 14 | "emulator": "0x0ae53cb6e3f42a79", 15 | "testnet": "0x7e60df042a9c0868", 16 | "mainnet" : "0x1654653399040a61" 17 | } 18 | }, 19 | "FungibleToken": { 20 | "source": "./contracts/FungibleToken.cdc", 21 | "aliases": { 22 | "emulator": "0xee82856bf20e2aa6", 23 | "testnet": "0x9a0766d93b6608b7", 24 | "mainnet" :"0xf233dcee88fe0abe" 25 | } 26 | }, 27 | "NonFungibleToken": { 28 | "source": "./contracts/NonFungibleToken.cdc", 29 | "aliases": { 30 | "testnet": "0x631e88ae7f1d7c20", 31 | "mainnet": "0x1d7e57aa55817448" 32 | } 33 | } 34 | }, 35 | "networks": { 36 | "emulator": "127.0.0.1:3569", 37 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 38 | "testnet": "access.devnet.nodes.onflow.org:9000" 39 | }, 40 | "accounts": { 41 | "emulator-account": { 42 | "address": "f8d6e0586b0a20c7", 43 | "key": "dc0097a6b58533e56af78c955e7b0c0f386b5f44f22b75c390beab7fcb1af13f" 44 | }, 45 | "emulator-first": { 46 | "address": "01cf0e2f2f715450", 47 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 48 | }, 49 | "emulator-second": { 50 | "address": "179b6b1cb6755e31", 51 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 52 | }, 53 | "emulator-3": { 54 | "address": "f3fcd2c1a78f5eee", 55 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 56 | }, 57 | "emulator-4": { 58 | "address": "e03daebed8ca0615", 59 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 60 | }, 61 | "emulator-5": { 62 | "address": "045a1763c93006ca", 63 | "key": "${INVALID_ENV_VAR}" 64 | } 65 | }, 66 | "deployments": { 67 | "emulator": { 68 | "emulator-account": [ "NonFungibleToken"], 69 | "emulator-first": ["Debug"], 70 | "emulator-second": [] 71 | }, 72 | "testnet" : { 73 | "emulator-first" : [ 74 | "Debug" 75 | ] 76 | }, 77 | "mainnet" : { 78 | "emulator-second" : [ 79 | "Debug" 80 | ] 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /testdata/non_existing_contract.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "emulators": { 4 | "default": { 5 | "port": 3569, 6 | "serviceAccount": "emulator-account" 7 | } 8 | }, 9 | "contracts": { 10 | "Debug": "./contracts/Debug.cdc", 11 | "FlowToken": { 12 | "source": "./contracts/FlowToken.cdc", 13 | "aliases": { 14 | "emulator": "0x0ae53cb6e3f42a79", 15 | "testnet": "0x7e60df042a9c0868", 16 | "mainnet" : "0x1654653399040a61" 17 | } 18 | }, 19 | "FungibleToken": { 20 | "source": "./contracts/FungibleToken.cdc", 21 | "aliases": { 22 | "emulator": "0xee82856bf20e2aa6", 23 | "testnet": "0x9a0766d93b6608b7", 24 | "mainnet" :"0xf233dcee88fe0abe" 25 | } 26 | }, 27 | "NonFungibleToken": { 28 | "source": "./contracts/NonFungibleToken.cdc", 29 | "aliases": { 30 | "testnet": "0x631e88ae7f1d7c20", 31 | "mainnet": "0x1d7e57aa55817448" 32 | } 33 | } 34 | }, 35 | "networks": { 36 | "emulator": "127.0.0.1:3569", 37 | "mainnet": "access.mainnet.nodes.onflow.org:9000", 38 | "testnet": "access.devnet.nodes.onflow.org:9000" 39 | }, 40 | "accounts": { 41 | "emulator-account": { 42 | "address": "f8d6e0586b0a20c7", 43 | "key": "dc0097a6b58533e56af78c955e7b0c0f386b5f44f22b75c390beab7fcb1af13f" 44 | }, 45 | "emulator-first": { 46 | "address": "01cf0e2f2f715450", 47 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 48 | }, 49 | "emulator-second": { 50 | "address": "179b6b1cb6755e31", 51 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 52 | }, 53 | "emulator-3": { 54 | "address": "f3fcd2c1a78f5eee", 55 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 56 | }, 57 | "emulator-4": { 58 | "address": "e03daebed8ca0615", 59 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 60 | }, 61 | "emulator-5": { 62 | "address": "045a1763c93006ca", 63 | "key": "d5457a187e9642a8e49d4032b3b4f85c92da7202c79681d9302c6e444e7033a8" 64 | } 65 | }, 66 | "deployments": { 67 | "emulator": { 68 | "emulator-account": [ 69 | "NonFungibleToken", 70 | "Debug2" 71 | ], 72 | "emulator-first": [], 73 | "emulator-second": [] 74 | }, 75 | "testnet" : { 76 | "emulator-first" : [ 77 | "Debug" 78 | ] 79 | }, 80 | "mainnet" : { 81 | "emulator-second" : [ 82 | "Debug" 83 | ] 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /testdata/pig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bjartek/overflow/92bd84282f9bb7b67a0dcdd813ce0ed5d900d8c9/testdata/pig.png -------------------------------------------------------------------------------- /testdata/testFile.txt: -------------------------------------------------------------------------------- 1 | This is a file -------------------------------------------------------------------------------- /testing.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/onflow/flow-go/utils/io" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | type OverflowTest struct { 13 | O *OverflowState 14 | height uint64 15 | } 16 | 17 | func (ot *OverflowTest) Reset() error { 18 | block, err := ot.O.GetLatestBlock(context.Background()) 19 | if err != nil { 20 | return err 21 | } 22 | height := block.Height 23 | if ot.height != height { 24 | return ot.O.RollbackToBlockHeight(ot.height) 25 | } 26 | return nil 27 | } 28 | 29 | func (ot *OverflowTest) Run(t *testing.T, name string, f func(t *testing.T)) { 30 | t.Helper() 31 | err := ot.Reset() 32 | require.NoError(t, err) 33 | t.Run(name, f) 34 | err = ot.Reset() 35 | require.NoError(t, err) 36 | } 37 | 38 | func (ot *OverflowTest) Teardown() { 39 | 40 | report := ot.O.GetCoverageReport() 41 | if report == nil { 42 | return 43 | } 44 | 45 | bytes, err := json.MarshalIndent(report, "", " ") 46 | if err != nil { 47 | panic(err) 48 | } 49 | err = io.WriteFile("coverage-report.json", bytes) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | } 55 | 56 | func SetupTest(opts []OverflowOption, setup func(o *OverflowState) error) (*OverflowTest, error) { 57 | allOpts := []OverflowOption{WithNetwork("testing")} 58 | allOpts = append(allOpts, opts...) 59 | 60 | o := Overflow(allOpts...) 61 | if o.Error != nil { 62 | return nil, o.Error 63 | } 64 | 65 | err := setup(o) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | if o.Error != nil { 71 | return nil, err 72 | } 73 | 74 | block, err := o.GetLatestBlock(context.Background()) 75 | if err != nil { 76 | return nil, err 77 | } 78 | height := block.Height 79 | 80 | ot := &OverflowTest{O: o, height: height} 81 | return ot, nil 82 | } 83 | -------------------------------------------------------------------------------- /transaction.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math" 7 | "strings" 8 | 9 | "github.com/bjartek/underflow" 10 | "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" 11 | "github.com/onflow/cadence/common" 12 | "github.com/onflow/cadence/parser" 13 | "github.com/onflow/flow-go-sdk" 14 | ) 15 | 16 | type FilterFunction func(OverflowTransaction) bool 17 | 18 | type Argument struct { 19 | Value interface{} 20 | Key string 21 | } 22 | 23 | type OverflowTransaction struct { 24 | Error error 25 | AuthorizerTypes map[string][]string 26 | Stakeholders map[string][]string 27 | Payer string 28 | Id string 29 | Status string 30 | BlockId string 31 | Authorizers []string 32 | Arguments []Argument 33 | Events []OverflowEvent 34 | Imports []Import 35 | Script []byte 36 | ProposalKey flow.ProposalKey 37 | Fee float64 38 | TransactionIndex int 39 | GasLimit uint64 40 | GasUsed uint64 41 | ExecutionEffort float64 42 | Balances FeeBalance 43 | PayloadSignatures []flow.TransactionSignature 44 | EnvelopeSignatures []flow.TransactionSignature 45 | } 46 | 47 | func (o *OverflowState) CreateOverflowTransaction(blockId string, transactionResult flow.TransactionResult, transaction flow.Transaction, txIndex int) (*OverflowTransaction, error) { 48 | feeAmount := 0.0 49 | events, fee := o.ParseEvents(transactionResult.Events) 50 | feeRaw, ok := fee.Fields["amount"] 51 | if ok { 52 | feeAmount, ok = feeRaw.(float64) 53 | if !ok { 54 | return nil, fmt.Errorf("failed casting fee amount to float64") 55 | } 56 | } 57 | 58 | executionEffort, ok := fee.Fields["executionEffort"].(float64) 59 | gas := 0 60 | if ok { 61 | factor := 100000000 62 | gas = int(math.Round(executionEffort * float64(factor))) 63 | } 64 | 65 | status := transactionResult.Status.String() 66 | 67 | args := []Argument{} 68 | argInfo := declarationInfo(transaction.Script) 69 | for i := range transaction.Arguments { 70 | arg, err := transaction.Argument(i) 71 | if err != nil { 72 | status = fmt.Sprintf("%s failed getting argument at index %d", status, i) 73 | } 74 | var key string 75 | if len(argInfo.ParameterOrder) <= i { 76 | key = "invalid" 77 | } else { 78 | key = argInfo.ParameterOrder[i] 79 | } 80 | argStruct := Argument{ 81 | Key: key, 82 | Value: underflow.CadenceValueToInterfaceWithOption(arg, o.UnderflowOptions), 83 | } 84 | args = append(args, argStruct) 85 | } 86 | 87 | standardStakeholders := map[string][]string{} 88 | imports, err := GetAddressImports(transaction.Script) 89 | if err != nil { 90 | status = fmt.Sprintf("%s failed getting imports", status) 91 | } 92 | 93 | authorizerTypes := map[string][]string{} 94 | 95 | authorizers := []string{} 96 | for i, authorizer := range transaction.Authorizers { 97 | auth := fmt.Sprintf("0x%s", authorizer.Hex()) 98 | authorizers = append(authorizers, auth) 99 | standardStakeholders[auth] = []string{"authorizer"} 100 | if len(argInfo.Authorizers) > i { 101 | authorizerTypes[auth] = argInfo.Authorizers[i].Entitlements 102 | } 103 | } 104 | 105 | payerRoles, ok := standardStakeholders[fmt.Sprintf("0x%s", transaction.Payer.Hex())] 106 | if !ok { 107 | standardStakeholders[fmt.Sprintf("0x%s", transaction.Payer.Hex())] = []string{"payer"} 108 | } else { 109 | payerRoles = append(payerRoles, "payer") 110 | standardStakeholders[fmt.Sprintf("0x%s", transaction.Payer.Hex())] = payerRoles 111 | } 112 | 113 | proposer, ok := standardStakeholders[fmt.Sprintf("0x%s", transaction.ProposalKey.Address.Hex())] 114 | if !ok { 115 | standardStakeholders[fmt.Sprintf("0x%s", transaction.ProposalKey.Address.Hex())] = []string{"proposer"} 116 | } else { 117 | proposer = append(proposer, "proposer") 118 | standardStakeholders[fmt.Sprintf("0x%s", transaction.ProposalKey.Address.Hex())] = proposer 119 | } 120 | 121 | // TODO: here we need to get out the balance of the payer and the fee recipient 122 | eventsWithoutFees, balanceFees := events.FilterFees(feeAmount, fmt.Sprintf("0x%s", transaction.Payer.Hex())) 123 | 124 | eventList := []OverflowEvent{} 125 | for _, evList := range eventsWithoutFees { 126 | eventList = append(eventList, evList...) 127 | } 128 | 129 | return &OverflowTransaction{ 130 | Id: transactionResult.TransactionID.String(), 131 | TransactionIndex: txIndex, 132 | BlockId: blockId, 133 | Status: status, 134 | Events: eventList, 135 | Stakeholders: eventsWithoutFees.GetStakeholders(standardStakeholders), 136 | Imports: imports, 137 | Error: transactionResult.Error, 138 | Arguments: args, 139 | Fee: feeAmount, 140 | Script: transaction.Script, 141 | Payer: fmt.Sprintf("0x%s", transaction.Payer.String()), 142 | ProposalKey: transaction.ProposalKey, 143 | GasLimit: transaction.GasLimit, 144 | GasUsed: uint64(gas), 145 | ExecutionEffort: executionEffort, 146 | Authorizers: authorizers, 147 | AuthorizerTypes: authorizerTypes, 148 | Balances: balanceFees, 149 | PayloadSignatures: transaction.PayloadSignatures, 150 | EnvelopeSignatures: transaction.EnvelopeSignatures, 151 | }, nil 152 | } 153 | 154 | func (o *OverflowState) GetOverflowTransactionById(ctx context.Context, id flow.Identifier) (*OverflowTransaction, error) { 155 | ctx = logging.InjectLogField(ctx, "transaction_id", id) 156 | tx, txr, err := o.Flowkit.GetTransactionByID(ctx, id, false) 157 | if err != nil { 158 | return nil, err 159 | } 160 | txIndex := 0 161 | if len(txr.Events) > 0 { 162 | txIndex = txr.Events[0].TransactionIndex 163 | } 164 | return o.CreateOverflowTransaction(txr.BlockID.String(), *txr, *tx, txIndex) 165 | } 166 | 167 | func (o *OverflowState) GetTransactionById(ctx context.Context, id flow.Identifier) (*flow.Transaction, error) { 168 | ctx = logging.InjectLogField(ctx, "transaction_id", id) 169 | tx, _, err := o.Flowkit.GetTransactionByID(ctx, id, false) 170 | if err != nil { 171 | return nil, err 172 | } 173 | return tx, nil 174 | } 175 | 176 | func (o *OverflowState) GetTransactionsByBlockId(ctx context.Context, id flow.Identifier) ([]*flow.Transaction, []*flow.TransactionResult, error) { 177 | ctx = logging.InjectLogField(ctx, "block_id", id) 178 | tx, txr, err := o.Flowkit.GetTransactionsByBlockID(ctx, id) 179 | if err != nil { 180 | return nil, nil, err 181 | } 182 | return tx, txr, nil 183 | } 184 | 185 | func GetAddressImports(code []byte) ([]Import, error) { 186 | deps := []Import{} 187 | program, err := parser.ParseProgram(nil, code, parser.Config{}) 188 | if err != nil { 189 | return deps, err 190 | } 191 | 192 | for _, imp := range program.ImportDeclarations() { 193 | address, isAddressImport := imp.Location.(common.AddressLocation) 194 | if isAddressImport { 195 | for _, id := range imp.Identifiers { 196 | deps = append(deps, Import{ 197 | Address: fmt.Sprintf("0x%s", address.Address.Hex()), 198 | Name: id.Identifier, 199 | }) 200 | } 201 | } 202 | } 203 | return deps, nil 204 | } 205 | 206 | type Import struct { 207 | Address string 208 | Name string 209 | } 210 | 211 | func (i Import) Identifier() string { 212 | return fmt.Sprintf("A.%s.%s", strings.TrimPrefix(i.Address, "0x"), i.Name) 213 | } 214 | -------------------------------------------------------------------------------- /transaction_test.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/sanity-io/litter" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | /* 13 | * OverflowTesting 14 | * - state 15 | * - testing 16 | * - func to setup overflow 17 | * - store the height 18 | * - run method that will revert to height before run method and revert again 19 | * - at the end of TestMain expose coverage report to file, if we start with coverage flag 20 | */ 21 | 22 | func TestTransaction(t *testing.T) { 23 | o, err := OverflowTesting() 24 | require.NotNil(t, o) 25 | require.NoError(t, err) 26 | t.Run("Run simple tx", func(t *testing.T) { 27 | res := o.Tx("arguments", WithArg("test", "foo"), WithSignerServiceAccount()) 28 | assert.NoError(t, res.Err) 29 | }) 30 | 31 | t.Run("error on missing argument", func(t *testing.T) { 32 | res := o.Tx("arguments", WithSignerServiceAccount()) 33 | assert.ErrorContains(t, res.Err, "the interaction 'arguments' is missing [test]") 34 | }) 35 | 36 | t.Run("error on redundant argument", func(t *testing.T) { 37 | res := o.Tx("arguments", WithArg("test2", "foo"), WithArg("test", "foo"), WithSignerServiceAccount()) 38 | assert.ErrorContains(t, res.Err, "the interaction 'arguments' has the following extra arguments [test2]") 39 | }) 40 | 41 | t.Run("Run simple tx with sa proposer", func(t *testing.T) { 42 | res := o.Tx("arguments", WithArg("test", "foo"), WithPayloadSigner("first"), WithProposerServiceAccount()) 43 | assert.Contains(t, res.EmulatorLog[0], "0x179b6b1cb6755e31") 44 | }) 45 | 46 | t.Run("Run simple tx with custom proposer", func(t *testing.T) { 47 | res := o.Tx("arguments", WithArg("test", "foo"), WithPayloadSigner("first"), WithProposer("account")) 48 | litter.Dump(res.EmulatorLog) 49 | assert.Contains(t, res.EmulatorLog[0], "0x179b6b1cb6755e31") 50 | }) 51 | t.Run("Fail when invalid proposer", func(t *testing.T) { 52 | res := o.Tx("arguments", WithArg("test", "foo"), WithPayloadSigner("first"), WithProposer("account2")) 53 | assert.ErrorContains(t, res.Err, "could not find account with name emulator-account2 in the configuration") 54 | }) 55 | 56 | t.Run("Run linine tx", func(t *testing.T) { 57 | res := o.Tx(` 58 | transaction(test:String) { 59 | prepare(acct: auth(BorrowValue) &Account) { 60 | log(test) 61 | } 62 | } 63 | `, WithArg("test", "foo"), WithSignerServiceAccount()) 64 | assert.NoError(t, res.Err) 65 | }) 66 | 67 | t.Run("Run linine tx", func(t *testing.T) { 68 | res := o.Tx(` 69 | transaction(test:UInt64) { 70 | prepare(acct: auth(BorrowValue) &Account) { 71 | log(test) 72 | } 73 | } 74 | `, WithArg("test", uint64(1)), WithSignerServiceAccount()) 75 | assert.NoError(t, res.Err) 76 | }) 77 | 78 | t.Run("Run simple tx with custom signer", func(t *testing.T) { 79 | res := o.Tx("arguments", WithArg("test", "foo"), WithSigner("account")) 80 | assert.NoError(t, res.Err) 81 | }) 82 | 83 | t.Run("Error on wrong signer name", func(t *testing.T) { 84 | res := o.Tx("arguments", WithArg("test", "foo"), WithSigner("account2")) 85 | assert.ErrorContains(t, res.Err, "could not find account with name emulator-account2") 86 | }) 87 | 88 | t.Run("compose a function", func(t *testing.T) { 89 | serviceAccountTx := o.TxFN(WithSignerServiceAccount()) 90 | res := serviceAccountTx("arguments", WithArg("test", "foo")) 91 | assert.NoError(t, res.Err) 92 | }) 93 | 94 | t.Run("create function with name", func(t *testing.T) { 95 | argumentTx := o.TxFileNameFN("arguments", WithSignerServiceAccount()) 96 | res := argumentTx(WithArg("test", "foo")) 97 | assert.NoError(t, res.Err) 98 | }) 99 | 100 | t.Run("Should not allow varags builder arg with single element", func(t *testing.T) { 101 | res := o.Tx("arguments", WithArgs("test")) 102 | assert.ErrorContains(t, res.Err, "please send in an even number of string : interface{} pairs") 103 | }) 104 | 105 | t.Run("Should not allow varag with non string keys", func(t *testing.T) { 106 | res := o.Tx("arguments", WithArgs(1, "test")) 107 | assert.ErrorContains(t, res.Err, "even parameters in Args needs to be string") 108 | }) 109 | 110 | t.Run("Arg, with cadence raw value", func(t *testing.T) { 111 | res := o.Tx("arguments", WithSignerServiceAccount(), WithArg("test", cadenceString("test"))) 112 | assert.NoError(t, res.Err) 113 | }) 114 | 115 | t.Run("date time arg", func(t *testing.T) { 116 | res := o.BuildInteraction(` 117 | transaction(test:UFix64) { 118 | prepare(acct: auth(BorrowValue) &Account) { 119 | 120 | } 121 | } 122 | `, "transaction", WithArgDateTime("test", "July 29, 2021 08:00:00 AM", "America/New_York"), WithSignerServiceAccount()) 123 | assert.NoError(t, res.Error) 124 | }) 125 | 126 | t.Run("date time arg error", func(t *testing.T) { 127 | res := o.BuildInteraction(` 128 | transaction(test:UFix64) { 129 | prepare(acct: auth(BorrowValue) &Account) { 130 | 131 | } 132 | } 133 | `, "transaction", WithArgDateTime("test", "July 29021 08:00:00 AM", "America/New_York"), WithSignerServiceAccount()) 134 | assert.ErrorContains(t, res.Error, "cannot parse") 135 | }) 136 | 137 | t.Run("Map args", func(t *testing.T) { 138 | res := o.Tx("arguments", WithSignerServiceAccount(), WithArgsMap(map[string]interface{}{"test": "test"})) 139 | assert.NoError(t, res.Err) 140 | }) 141 | 142 | t.Run("Parse addresses should fail if not valid account name and hex", func(t *testing.T) { 143 | res := o.BuildInteraction(` 144 | transaction(test:[Address]) { 145 | prepare(acct: auth(BorrowValue) &Account) { 146 | 147 | } 148 | } 149 | `, "transaction", WithAddresses("test", "bjartek"), WithSignerServiceAccount()) 150 | assert.ErrorContains(t, res.Error, "bjartek is not an valid account name or an address") 151 | }) 152 | 153 | t.Run("Parse array of addresses", func(t *testing.T) { 154 | res := o.BuildInteraction(` 155 | transaction(test:[Address]) { 156 | prepare(acct: auth(BorrowValue) &Account) { 157 | 158 | } 159 | } 160 | `, "transaction", WithAddresses("test", "account", "45a1763c93006ca"), WithSignerServiceAccount()) 161 | assert.Equal(t, "[0xf8d6e0586b0a20c7, 0x045a1763c93006ca]", fmt.Sprintf("%v", res.NamedArgs["test"])) 162 | }) 163 | 164 | t.Run("Parse String to String map", func(t *testing.T) { 165 | res := o.BuildInteraction(` 166 | transaction(test:{String:String}) { 167 | prepare(acct: auth(BorrowValue) &Account) { 168 | 169 | } 170 | } 171 | `, "transaction", WithArg("test", `{ "foo" : "bar"}`), WithSignerServiceAccount()) 172 | assert.Equal(t, `{ "foo" : "bar"}`, fmt.Sprintf("%v", res.NamedArgs["test"])) 173 | }) 174 | 175 | t.Run("Parse String to UFix64 map", func(t *testing.T) { 176 | res := o.BuildInteraction(` 177 | transaction(test:{String:UFix64}) { 178 | prepare(acct: auth(BorrowValue) &Account) { 179 | 180 | } 181 | } 182 | `, "transaction", WithArg("test", `{ "foo" : 1.0}`), WithSignerServiceAccount()) 183 | assert.Equal(t, `{ "foo" : 1.0}`, fmt.Sprintf("%v", res.NamedArgs["test"])) 184 | }) 185 | 186 | t.Run("Error when parsing invalid address", func(t *testing.T) { 187 | res := o.BuildInteraction(` 188 | transaction(test:Address) { 189 | prepare(acct: auth(BorrowValue) &Account) { 190 | 191 | } 192 | } 193 | `, "transaction", WithArg("test", "bjartek"), WithSignerServiceAccount()) 194 | assert.ErrorContains(t, res.Error, "argument `test` with value `bjartek` is not expected type `Address`") 195 | }) 196 | 197 | t.Run("Should set gas", func(t *testing.T) { 198 | res := o.BuildInteraction(` 199 | transaction(test:Address) { 200 | prepare(acct: auth(BorrowValue) &Account) { 201 | 202 | } 203 | } 204 | `, "transaction", WithArg("test", "bjartek"), WithSignerServiceAccount(), WithMaxGas(100)) 205 | 206 | assert.Equal(t, uint64(100), res.GasLimit) 207 | }) 208 | 209 | t.Run("Should report error if invalid payload signer", func(t *testing.T) { 210 | res := o.Tx(` 211 | transaction{ 212 | prepare(acct: auth(BorrowValue) &Account, user:auth(BorrowValue) &Account) { 213 | 214 | } 215 | } 216 | `, WithSignerServiceAccount(), WithPayloadSigner("bjartek")) 217 | 218 | assert.Error(t, res.Err, "asd") 219 | }) 220 | 221 | t.Run("ufix64", func(t *testing.T) { 222 | res := o.BuildInteraction(` 223 | transaction(test:UFix64) { 224 | prepare(acct: auth(BorrowValue) &Account) { 225 | 226 | } 227 | } 228 | `, "transaction", WithArg("test", 1.0), WithSignerServiceAccount()) 229 | assert.NoError(t, res.Error) 230 | }) 231 | 232 | t.Run("add printer args", func(t *testing.T) { 233 | res := o.BuildInteraction(` 234 | transaction(test:UFix64) { 235 | prepare(acct: auth(BorrowValue) &Account) { 236 | 237 | } 238 | } 239 | `, "transaction", WithArg("test", 1.0), WithSignerServiceAccount(), WithPrintOptions(WithEmulatorLog()), WithPrintOptions(WithFullMeter())) 240 | assert.NoError(t, res.Error) 241 | assert.Equal(t, 2, len(*res.PrintOptions)) 242 | }) 243 | 244 | t.Run("add event filter", func(t *testing.T) { 245 | filter := OverflowEventFilter{ 246 | "Deposit": []string{"id"}, 247 | } 248 | 249 | res := o.BuildInteraction(` 250 | transaction(test:UFix64) { 251 | prepare(acct: auth(BorrowValue) &Account) { 252 | } 253 | } 254 | `, "transaction", WithArg("test", 1.0), WithSignerServiceAccount(), WithoutGlobalEventFilter(), WithEventsFilter(filter)) 255 | assert.NoError(t, res.Error) 256 | assert.True(t, res.IgnoreGlobalEventFilters) 257 | assert.Equal(t, 1, len(res.EventFilter)) 258 | }) 259 | } 260 | -------------------------------------------------------------------------------- /transactions/aTransaction.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | 3 | transaction() { 4 | // This is a generic transaction 5 | } 6 | -------------------------------------------------------------------------------- /transactions/arguments.cdc: -------------------------------------------------------------------------------- 1 | 2 | // This transaction creates an empty NFT Collection in the signer's account 3 | transaction(test:String) { 4 | prepare(acct: auth(Storage) &Account) { 5 | log(acct) 6 | log(test) 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /transactions/argumentsWithAccount.cdc: -------------------------------------------------------------------------------- 1 | 2 | // This transaction creates an empty NFT Collection in the signer's account 3 | transaction(test:Address) { 4 | prepare(acct: &Account) { 5 | log("signer") 6 | log(acct) 7 | log("argument") 8 | log(test) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /transactions/create_nft_collection.cdc: -------------------------------------------------------------------------------- 1 | 2 | // This transaction creates an empty NFT Collection in the signer's account 3 | transaction { 4 | prepare(acct: auth(Storage) &Account) { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /transactions/emulatorFoo.cdc: -------------------------------------------------------------------------------- 1 | 2 | // This transaction creates an empty NFT Collection in the signer's account 3 | transaction(test:String) { 4 | prepare(acct: auth(BorrowValue) &Account) { 5 | log(acct) 6 | log(test) 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /transactions/mainnetFoo.cdc: -------------------------------------------------------------------------------- 1 | 2 | // This transaction creates an empty NFT Collection in the signer's account 3 | transaction(test:String) { 4 | prepare(acct: &Account) { 5 | log(acct) 6 | log(test) 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /transactions/mainnetaTransaction.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | 3 | transaction() { 4 | // This is a mainnet specific transaction 5 | } 6 | -------------------------------------------------------------------------------- /transactions/mainnetzTransaction.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | 3 | transaction() { 4 | // This is a mainnet specific transaction 5 | } 6 | -------------------------------------------------------------------------------- /transactions/mint_tokens.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from 0xee82856bf20e2aa6 2 | import FlowToken from 0x0ae53cb6e3f42a79 3 | 4 | 5 | transaction(recipient: Address, amount: UFix64) { 6 | let tokenAdmin: &FlowToken.Administrator 7 | let tokenReceiver: &{FungibleToken.Receiver} 8 | 9 | prepare(signer: auth(BorrowValue) &Account) { 10 | self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) 11 | ?? panic("Signer is not the token admin") 12 | 13 | self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Unable to borrow receiver reference") 14 | } 15 | 16 | execute { 17 | let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) 18 | let mintedVault <- minter.mintTokens(amount: amount) 19 | 20 | self.tokenReceiver.deposit(from: <-mintedVault) 21 | 22 | destroy minter 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /transactions/sendFlow.cdc: -------------------------------------------------------------------------------- 1 | import "FungibleToken" 2 | 3 | transaction(amount: UFix64, to: Address) { 4 | 5 | let vault: @{FungibleToken.Vault} 6 | 7 | prepare(signer: auth(BorrowValue) &Account) { 8 | let vaultRef = signer.storage.borrow(from: /storage/flowTokenVault) 9 | ?? panic("Could not borrow reference to the owner's Vault!") 10 | 11 | self.vault <- vaultRef.withdraw(amount:amount) 12 | 13 | } 14 | execute { 15 | // Get the recipient's public account object 16 | let recipient = getAccount(to) 17 | 18 | // Get a reference to the recipient's Receiver 19 | let receiverRef = recipient.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) 20 | ?? panic("Could not borrow receiver reference to the recipient's Vault") 21 | 22 | receiverRef.deposit(from: <-self.vault) 23 | // Deposit the withdrawn tokens in the recipient's receiver 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /transactions/signWithMultipleAccounts.cdc: -------------------------------------------------------------------------------- 1 | // This transaction creates an empty NFT Collection in the signer's account 2 | transaction(test:String) { 3 | prepare(acct: &Account, account2: &Account) { 4 | log(acct) 5 | log(account2) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /transactions/testnetFoo.cdc: -------------------------------------------------------------------------------- 1 | 2 | // This transaction creates an empty NFT Collection in the signer's account 3 | transaction(test:String) { 4 | prepare(acct: auth(Storage) &Account) { 5 | log(acct) 6 | log(test) 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /transactions/zTransaction.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from "../contracts/FungibleToken.cdc" 2 | 3 | transaction() { 4 | // This is a generic transaction 5 | } 6 | -------------------------------------------------------------------------------- /tx/create_nft_collection.cdc: -------------------------------------------------------------------------------- 1 | import NonFungibleToken from "../contracts/NonFungibleToken.cdc" 2 | 3 | // This transaction creates an empty NFT Collection in the signer's account 4 | transaction { 5 | prepare(acct: auth(Storage) &Account) { 6 | // store an empty NFT Collection in account storage 7 | acct.save<@NonFungibleToken.Collection>(<-NonFungibleToken.createEmptyCollection(), to: /storage/NFTCollection) 8 | 9 | // publish a capability to the Collection in storage 10 | acct.link<&{NonFungibleToken.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection) 11 | 12 | log("Created a new empty collection and published a reference") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tx/mint_tokens.cdc: -------------------------------------------------------------------------------- 1 | import FungibleToken from 0xee82856bf20e2aa6 2 | import FlowToken from 0x0ae53cb6e3f42a79 3 | 4 | 5 | transaction(recipient: Address, amount: UFix64) { 6 | let tokenAdmin: &FlowToken.Administrator 7 | let tokenReceiver: &{FungibleToken.Receiver} 8 | 9 | prepare(signer: auth(BorrowValue) &Account) { 10 | self.tokenAdmin = signer.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin) 11 | ?? panic("Signer is not the token admin") 12 | 13 | self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver) ?? panic("Unable to borrow receiver reference") 14 | } 15 | 16 | execute { 17 | let minter <- self.tokenAdmin.createNewMinter(allowedAmount: amount) 18 | let mintedVault <- minter.mintTokens(amount: amount) 19 | 20 | self.tokenReceiver.deposit(from: <-mintedVault) 21 | 22 | destroy minter 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package overflow 2 | 3 | import ( 4 | "bufio" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "fmt" 8 | "io" 9 | "math" 10 | "math/rand" 11 | "net/http" 12 | "os" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "github.com/araddon/dateparse" 18 | "github.com/onflow/cadence" 19 | ) 20 | 21 | // go string to cadence string panic if error 22 | func cadenceString(input string) cadence.String { 23 | value, err := cadence.NewString(input) 24 | if err != nil { 25 | panic(err) 26 | } 27 | return value 28 | } 29 | 30 | func parseTime(timeString string, location string) (string, error) { 31 | loc, err := time.LoadLocation(location) 32 | if err != nil { 33 | return "", err 34 | } 35 | time.Local = loc 36 | t, err := dateparse.ParseLocal(timeString) 37 | if err != nil { 38 | return "", err 39 | } 40 | return fmt.Sprintf("%d.0", t.Unix()), nil 41 | } 42 | 43 | func exists(path string) (bool, error) { 44 | _, err := os.Stat(path) 45 | 46 | if err == nil { 47 | return true, nil 48 | } 49 | 50 | if os.IsNotExist(err) { 51 | return false, nil 52 | } 53 | 54 | return true, err 55 | } 56 | 57 | func writeProgressToFile(fileName string, blockHeight int64) error { 58 | err := os.WriteFile(fileName, []byte(fmt.Sprintf("%d", blockHeight)), 0644) 59 | if err != nil { 60 | return fmt.Errorf("could not create initial progress file %v", err) 61 | } 62 | return nil 63 | } 64 | 65 | func readProgressFromFile(fileName string) (int64, error) { 66 | dat, err := os.ReadFile(fileName) 67 | if err != nil { 68 | return 0, fmt.Errorf("ProgressFile is not valid %v", err) 69 | } 70 | 71 | stringValue := strings.TrimSpace(string(dat)) 72 | 73 | return strconv.ParseInt(stringValue, 10, 64) 74 | } 75 | 76 | func splitByWidthMake(str string, size int) []string { 77 | strLength := len(str) 78 | splitedLength := int(math.Ceil(float64(strLength) / float64(size))) 79 | splited := make([]string, splitedLength) 80 | var start, stop int 81 | for i := 0; i < splitedLength; i += 1 { 82 | start = i * size 83 | stop = start + size 84 | if stop > strLength { 85 | stop = strLength 86 | } 87 | splited[i] = str[start:stop] 88 | } 89 | return splited 90 | } 91 | 92 | func fileAsImageData(path string) (string, error) { 93 | f, _ := os.Open(path) 94 | 95 | defer f.Close() 96 | 97 | // Read entire JPG into byte slice. 98 | reader := bufio.NewReader(f) 99 | content, err := io.ReadAll(reader) 100 | if err != nil { 101 | return "", fmt.Errorf("could not read imageFile %s, %w", path, err) 102 | } 103 | 104 | return contentAsImageDataUrl(content), nil 105 | } 106 | 107 | func contentAsImageDataUrl(content []byte) string { 108 | contentType := http.DetectContentType(content) 109 | 110 | // Encode as base64. 111 | encoded := base64.StdEncoding.EncodeToString(content) 112 | 113 | return "data:" + contentType + ";base64, " + encoded 114 | } 115 | 116 | func fileAsBase64(path string) (string, error) { 117 | f, _ := os.Open(path) 118 | 119 | defer f.Close() 120 | 121 | // Read entire JPG into byte slice. 122 | reader := bufio.NewReader(f) 123 | content, err := io.ReadAll(reader) 124 | if err != nil { 125 | return "", fmt.Errorf("could not read file %s, %w", path, err) 126 | } 127 | 128 | // Encode as base64. 129 | encoded := base64.StdEncoding.EncodeToString(content) 130 | 131 | return encoded, nil 132 | } 133 | 134 | func getUrl(url string) ([]byte, error) { 135 | resp, err := http.Get(url) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | defer resp.Body.Close() 141 | 142 | return io.ReadAll(resp.Body) 143 | } 144 | 145 | func randomString(length int) string { 146 | rand.Seed(time.Now().UnixNano()) 147 | b := make([]byte, length) 148 | rand.Read(b) 149 | return fmt.Sprintf("%x", b)[:length] 150 | } 151 | 152 | // HexToAddress converts a hex string to an Address. 153 | func hexToAddress(h string) (*cadence.Address, error) { 154 | trimmed := strings.TrimPrefix(h, "0x") 155 | if len(trimmed)%2 == 1 { 156 | trimmed = "0" + trimmed 157 | } 158 | b, err := hex.DecodeString(trimmed) 159 | if err != nil { 160 | return nil, err 161 | } 162 | address := cadence.BytesToAddress(b) 163 | return &address, nil 164 | } 165 | --------------------------------------------------------------------------------