├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── api.adoc │ ├── choosing-a-test-runner.adoc │ ├── getting-started.adoc │ ├── index.adoc │ └── migrating-from-truffle.adoc ├── jest.config.js ├── netlify.toml ├── package-lock.json ├── package.json ├── scripts └── run-integration-test.js ├── src ├── __mocks__ │ └── child_process.ts ├── accounts.test.ts ├── accounts.ts ├── config.test.ts ├── config.ts ├── coverage.ts ├── ganache-server.ts ├── helpers.ts ├── index.ts ├── log.ts ├── provider.ts ├── setup-ganache.test.ts ├── setup-ganache.ts ├── setup-loader.ts ├── setup-provider.ts ├── test-provider.test.ts ├── test-provider.ts ├── typing │ ├── ethereumjs-wallet.d.ts │ └── try-require.d.ts └── utils.ts ├── test-integration ├── ava │ ├── contracts │ │ └── FooBar.sol │ ├── package-lock.json │ ├── package.json │ └── test │ │ └── foo-bar.test.js ├── jest │ ├── contracts │ │ └── FooBar.sol │ ├── package-lock.json │ ├── package.json │ └── test │ │ └── foo-bar.test.js ├── mocha-config │ ├── contracts │ │ └── Fatty.sol │ ├── package-lock.json │ ├── package.json │ ├── test-environment.config.js │ └── test │ │ └── config.test.js ├── mocha-defaults │ ├── contracts │ │ └── Fatty.sol │ ├── package-lock.json │ ├── package.json │ └── test │ │ └── defaultConfig.test.js ├── mocha-fork │ ├── contracts │ │ └── DAI.sol │ ├── package-lock.json │ ├── package.json │ ├── test-environment.config.js │ └── test │ │ └── config.test.js └── mocha │ ├── contracts │ └── FooBar.sol │ ├── coverage.js │ ├── package-lock.json │ ├── package.json │ └── test │ └── foo-bar.test.js └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | aliases: 4 | - &defaults 5 | docker: 6 | - image: circleci/node:10 7 | 8 | commands: 9 | npm-install-with-cache: 10 | parameters: 11 | dir: 12 | type: string 13 | default: "." 14 | steps: 15 | - restore_cache: &cache-key-node-modules 16 | key: v1-node_modules-{{ checksum "<< parameters.dir >>/package-lock.json" }} 17 | - run: 18 | name: Install npm dependencies 19 | working_directory: "<< parameters.dir >>" 20 | command: | 21 | if [ ! -d node_modules ]; then 22 | npm ci 23 | fi 24 | - save_cache: 25 | <<: *cache-key-node-modules 26 | paths: 27 | - "<< parameters.dir >>/node_modules" 28 | 29 | integration-test: 30 | parameters: 31 | test-name: 32 | type: string 33 | steps: 34 | - checkout 35 | - attach_workspace: 36 | at: . 37 | - npm-install-with-cache: 38 | dir: "test-integration/<< parameters.test-name >>" 39 | - run: 40 | name: "Integration Test: << parameters.test-name >>" 41 | command: "npm run test:integration run-tarball package.tgz << parameters.test-name >>" 42 | 43 | jobs: 44 | setup: 45 | <<: *defaults 46 | steps: 47 | - checkout 48 | - npm-install-with-cache 49 | - run: 50 | name: Lint check 51 | command: npm run lint:check 52 | - run: 53 | name: "Pack tarball for integration tests" 54 | command: "npm run test:integration pack package.tgz" 55 | - persist_to_workspace: 56 | root: . 57 | paths: 58 | - package.tgz 59 | 60 | test-unit: 61 | <<: *defaults 62 | steps: 63 | - checkout 64 | - npm-install-with-cache 65 | - run: 66 | name: Unit tests 67 | command: npm run test:unit 68 | 69 | test-mocha: 70 | <<: *defaults 71 | steps: 72 | - integration-test: 73 | test-name: mocha 74 | 75 | test-ava: 76 | <<: *defaults 77 | steps: 78 | - integration-test: 79 | test-name: ava 80 | 81 | test-jest: 82 | <<: *defaults 83 | steps: 84 | - integration-test: 85 | test-name: jest 86 | 87 | test-mocha-defaults: 88 | <<: *defaults 89 | steps: 90 | - integration-test: 91 | test-name: mocha-defaults 92 | 93 | test-mocha-config: 94 | <<: *defaults 95 | steps: 96 | - integration-test: 97 | test-name: mocha-config 98 | test-mocha-fork: 99 | <<: *defaults 100 | steps: 101 | - integration-test: 102 | test-name: mocha-fork 103 | 104 | workflows: 105 | version: 2 106 | everything: 107 | jobs: 108 | - setup 109 | - test-unit: 110 | requires: 111 | - setup 112 | - test-mocha: 113 | requires: 114 | - setup 115 | - test-ava: 116 | requires: 117 | - setup 118 | - test-jest: 119 | requires: 120 | - setup 121 | - test-mocha-defaults: 122 | requires: 123 | - setup 124 | - test-mocha-config: 125 | requires: 126 | - setup 127 | - test-mocha-fork: 128 | requires: 129 | - setup 130 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /test -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 6 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module', // Allows for the use of imports 11 | }, 12 | rules: {}, 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .zos.session 3 | zos.* 4 | build 5 | /lib 6 | .openzeppelin 7 | *.tgz 8 | coverage -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('code-style/.prettierrc.js'), 3 | }; 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.1.9 (2020-12-22) 8 | - Updated web3 dependency. 9 | 10 | ## 0.1.8 (2020-11-26) 11 | - Fixed HTML coverage report. 12 | 13 | ## 0.1.7 (2020-11-25) 14 | - Changed the way configuration is passed to Ganache, so as to enable use of config options like `db`. 15 | 16 | ## 0.1.6 (2020-10-21) 17 | ### Fixed 18 | - Fixed error handling in Ganache's child process when the parent process dies. 19 | 20 | ## 0.1.5 (2020-09-15) 21 | ### Added 22 | - Added support for [solidity-coverage](https://github.com/sc-forks/solidity-coverage). 23 | - Bumped ganache-core to 2.11.2. 24 | 25 | ## 0.1.4 (2020-04-15) 26 | ### Added 27 | - Added a `node` option to `test-environment.config` which contains options directly passed to Ganache, including `fork` and `unlocked_accounts` which are handy for tesing on forked chains. `allowUnlimitedContractSize` is supported as well. [See all Ganache options](https://github.com/trufflesuite/ganache-cli). 28 | ### Deprecated 29 | - Deprecated the configuration options `blockGasLimit` and `gasPrice`. Users should instead use the options `node.gasLimit` and `node.gasPrice`. 30 | 31 | ## 0.1.3 (2020-02-18) 32 | ### Fixed 33 | * Add missing dependency for exported types. 34 | 35 | ## 0.1.2 (2020-01-20) 36 | ### Added 37 | * Added `artifactsDir` and `defaultGasPrice` options. ([943d74](https://github.com/OpenZeppelin/openzeppelin-test-environment/commit/943d74)) 38 | 39 | ## 0.1.1 (2019-11-28) 40 | ### Added 41 | * Initial release 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 OpenZeppelin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: **This project is not being actively maintained.** It is advisable to use an alternative ([Hardhat](https://hardhat.org/), [Truffle](https://www.trufflesuite.com/truffle)). 2 | 3 | # OpenZeppelin Test Environment 4 | 5 | [![Docs](https://img.shields.io/badge/docs-%F0%9F%93%84-blue)](https://docs.openzeppelin.com/test-environment) 6 | [![NPM Package](https://img.shields.io/npm/v/@openzeppelin/test-environment.svg)](https://www.npmjs.org/package/@openzeppelin/test-environment) 7 | [![Build Status](https://circleci.com/gh/OpenZeppelin/openzeppelin-test-environment.svg?style=shield)](https://circleci.com/gh/OpenZeppelin/openzeppelin-test-environment) 8 | 9 | **Blazing fast smart contract testing.** One-line setup for an awesome testing experience. 10 | 11 | - Near-instant start up: have your code running in under 2s after typing `npm test`. 12 | - Test runner agnostic – from the familiarity of Mocha, to [_parallel tests_](https://docs.openzeppelin.com/test-environment/choosing-a-test-runner#parallel-tests) using Jest or Ava! 13 | - Non-opinionated: use either [`@truffle/contract`](https://www.npmjs.com/package/@truffle/contract) or [`web3-eth-contract`](https://web3js.readthedocs.io/en/v1.2.0/web3-eth-contract.html) as you see fit. 14 | - First class support for the [OpenZeppelin Test Helpers](https://docs.openzeppelin.com/test-helpers). 15 | - Highly configurable: from gas limit and initial balance, to complex custom web3 providers. 16 | - No global variables, no hacks. 17 | 18 | _`test-environment` is the result of our learnings while developing the [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts), combining best practices and the tools we've come to rely on over the years. We think you'll love it!_ 19 | 20 | ## Overview 21 | 22 | ### Installation 23 | 24 | ```bash 25 | npm install --save-dev @openzeppelin/test-environment 26 | ``` 27 | 28 | ### Usage 29 | 30 | By including `require('@openzeppelin/test-environment')` in your test files, a local [ganache-powered blockchain](https://github.com/trufflesuite/ganache-core) with unlocked accounts will be spun up, and all tools configured to work with it. 31 | 32 | Here's a quick sample of how using `test-environment` in a [Mocha](https://mochajs.org/) + [Chai](https://www.chaijs.com/) setup looks like. 33 | 34 | ```javascript 35 | const { accounts, contract } = require('@openzeppelin/test-environment'); 36 | const [ owner ] = accounts; 37 | 38 | const { expect } = require('chai'); 39 | 40 | const MyContract = contract.fromArtifact('MyContract'); // Loads a compiled contract 41 | 42 | describe('MyContract', function () { 43 | it('deployer is owner', async function () { 44 | const myContract = await MyContract.new({ from: owner }); 45 | expect(await myContract.owner()).to.equal(owner); 46 | }); 47 | }); 48 | ``` 49 | 50 | If you're used to `truffle test`, this probably looks very familiar. Follow our guide on [migrating from Truffle](https://docs.openzeppelin.com/test-environment/migrating-from-truffle) to have your project running with `test-environment` in a breeze! 51 | 52 | _Note: if you'd rather not rely on truffle contracts and use web3 contract types directly, worry not: you can [configure `test-environment`](https://docs.openzeppelin.com/test-environment/getting-started#configuration) to use the `web3-eth-contract` abstraction._ 53 | 54 | ## Learn More 55 | 56 | * Check out [Getting Started](https://docs.openzeppelin.com/test-environment/getting-started) to use Test Environment in a new project. 57 | * If you are currently using `truffle test`, head instead to [Migrating from Truffle](https://docs.openzeppelin.com/test-environment/migrating-from-truffle). 58 | * The [Choosing a Test Runner](https://docs.openzeppelin.com/test-environment/choosing-a-test-runner) guide will teach you how to use each of the different runners. 59 | * For detailed usage information, take a look at the [API Reference](https://docs.openzeppelin.com/test-environment/api). 60 | 61 | ## License 62 | 63 | Released under the [MIT License](LICENSE). 64 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: 'test-environment' 2 | title: 'Test Environment' 3 | version: '0.1' 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Overview] 2 | * xref:getting-started.adoc[Getting Started] 3 | * xref:migrating-from-truffle.adoc[Migrating from Truffle] 4 | * xref:choosing-a-test-runner.adoc[Choosing a Test Runner] 5 | * xref:contract-loader::index.adoc[Contract Loader] 6 | * xref:api.adoc[API Reference] 7 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/api.adoc: -------------------------------------------------------------------------------- 1 | = API Reference 2 | 3 | `test-environment` exposes a number of variables that are used to interact with the local testing blockchain it sets up. These are described in detail here: 4 | 5 | [source,javascript] 6 | ---- 7 | const { accounts, defaultSender, contract, web3, provider, isHelpersConfigured } = require('@openzeppelin/test-environment'); 8 | ---- 9 | 10 | == accounts 11 | 12 | [source,typescript] 13 | ---- 14 | accounts: string[] 15 | ---- 16 | 17 | An array of strings with the addresses of the accounts available for testing. By default, there are 10 unlocked accounts with 100 ETH each, but this can be xref:getting-started.adoc#configuration[configured]. 18 | 19 | [source,javascript] 20 | ---- 21 | const [ sender, receiver ] = accounts; 22 | 23 | await myToken.transfer(receiver, 100, { from: sender }); 24 | ---- 25 | 26 | [[default-sender]] 27 | == defaultSender 28 | 29 | [source,typescript] 30 | ---- 31 | defaultSender: string 32 | ---- 33 | 34 | A special account that is used by contracts created via `contract` when no account is specified for a transaction (i.e. there is no explicit `from`). This account is _not_ included in `accounts` to prevent accidental bugs during testing: whenever you want an account to make an action (deploy a contract, transfer ownership, etc.) you should be explicit about the sender of the transaction: 35 | 36 | [source,javascript] 37 | ---- 38 | const [ owner ] = accounts; 39 | 40 | // The deployment will be made by 'defaultSender' (not 'owner'!), making it 41 | // the contract's owner 42 | const myContract = await Ownable.new(); 43 | 44 | // And the following test will fail 45 | expect(await myContract.owner()).to.equal(owner); 46 | ---- 47 | 48 | == contract 49 | 50 | [source,typescript] 51 | ---- 52 | contract.fromArtifact: (contract: string) => any; 53 | contract.fromABI: (abi: object, bytecode?: string | undefined) => any; 54 | ---- 55 | 56 | The `contract` object is in charge of creating contracts from compilation artifacts. It does this via two functions: 57 | 58 | * `fromArtifact` looks for a `.json` file in the local or a dependency's `build/contracts` directory (equivalent to Truffle's `artifact.require`). 59 | * `fromABI` receives an ABI object directly, useful when the full compilation artifacts are not available. 60 | 61 | They both return instances of either https://www.npmjs.com/package/@truffle/contract[`@truffle/contract`] (by default) or https://web3js.readthedocs.io/en/v1.2.0/web3-eth-contract.html[`web3-eth-contract`], depending on your xref:getting-started.adoc#configuration[configuration]. 62 | 63 | [source,javascript] 64 | ---- 65 | const ERC20 = contract.fromArtifact('ERC20'); 66 | 67 | const myToken = await ERC20.new(initialBalance, initialHolder); 68 | ---- 69 | 70 | NOTE: Head over to the xref:contract-loader::index.adoc[documentation for Contract Loader] to learn more. 71 | 72 | == web3 73 | 74 | A https://www.npmjs.com/package/web3[`web3`] instance, connected to the local testing blockchain. Useful to access utilities like `web3.eth.sign`, `web3.eth.getTransaction`, or `web3.utils.sha3`. 75 | 76 | == provider 77 | 78 | A https://github.com/ethereum/web3.js/[`web3`] provider, connected to the local testing blockchain. Used in more advanced scenarios, such as creation of custom `web3` or https://www.npmjs.com/package/ethers[`ethers`] instances. 79 | 80 | == isHelpersConfigured 81 | 82 | [source,typescript] 83 | ---- 84 | isHelpersConfigured: boolean 85 | ---- 86 | 87 | A boolean indicating if the xref:test-helpers::index.adoc[*OpenZeppelin Test Helpers*] library was autodetected and configured. 88 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/choosing-a-test-runner.adoc: -------------------------------------------------------------------------------- 1 | = Choosing a Test Runner 2 | 3 | Test Environment is only a testing _library_: something you use _in_ your tests, but not what actually _runs_ them. 4 | 5 | This is very much by design: it gives you the freedom to use **any regular JavaScript test runner**. 6 | 7 | We recommend picking one of the following: 8 | 9 | * https://mochajs.org/[Mocha]: simple and straightforward, the easiest way to get started when migrating from `truffle test`. 10 | * https://jestjs.io/[Jest]: the most popular runner out there, featuring lightning speed, parallel tests, and extensive guides. 11 | * https://www.npmjs.com/package/ava/[Ava]: a minimalistic runner with parallel tests and support for ES6 and TypeScript. 12 | 13 | NOTE: Both Jest and Ava have their own assertions library, but for Mocha, you may want to also use https://www.chaijs.com[Chai]. 14 | 15 | == Example Contract 16 | 17 | To showcase the different runners, we will write tests for the following contract using each one. 18 | 19 | It consists of a simple access control mechanism via xref:contracts:api:ownership.adoc#Ownable[*OpenZeppelin Contracts*' `Ownable`]: 20 | 21 | ```solidity 22 | // contracts/MyContract.sol 23 | 24 | pragma solidity ^0.5.0; 25 | 26 | import "@openzeppelin/contracts/ownership/Ownable.sol"; 27 | 28 | contract MyContract is Ownable { } 29 | ``` 30 | 31 | == Mocha & Chai 32 | 33 | Test cases are declared with the `it` function, and grouped in `describe` blocks: 34 | 35 | ```javascript 36 | // test/MyContract.test.js 37 | 38 | const { accounts, contract } = require('@openzeppelin/test-environment'); 39 | 40 | const { expect } = require('chai'); 41 | 42 | const MyContract = contract.fromArtifact('MyContract'); 43 | 44 | describe('MyContract', function () { 45 | const [ owner ] = accounts; 46 | 47 | beforeEach(async function { 48 | this.myContract = await MyContract.new({ from: owner }); 49 | }); 50 | 51 | it('the deployer is the owner', async function () { 52 | expect(await this.myContract.owner()).to.equal(owner); 53 | }); 54 | }); 55 | ``` 56 | 57 | To install Mocha and Chai, run: 58 | 59 | ```bash 60 | $ npm install --save-dev mocha chai 61 | ``` 62 | 63 | All tests in the `test` directory are then executed with: 64 | 65 | ```bash 66 | $ npx mocha --recursive --exit 67 | ``` 68 | 69 | WARNING: Mocha's `--exit` flag is required when using Truffle contracts. Otherwise, the test suite will not exit. https://github.com/trufflesuite/truffle/issues/2560[Learn more]. 70 | 71 | Mocha will run tests sequentially, waiting for each to finish before starting the next one. A single local blockchain will be created, and shared by all tests. 72 | 73 | [[parallel-tests]] 74 | == Parallel Tests 75 | 76 | Unlike Mocha, both Jest and Ava will let you run tests in parallel: each test file will be executed _at the same time_, leading to massive time savings when running a large test suite. 77 | 78 | When using these runners, Test Environment will create an independent local blockchain for each parallel run, so there is zero risk of your tests unexpectedly interacting with each other. 79 | 80 | Migrating from Mocha to Jest is rather straightforward, and is well worth the effort if you are spending much time waiting for tests to finish. 81 | 82 | == Jest 83 | 84 | Jest looks very similar to Mocha (`describe`, `it` and `beforeEach` are all there), but there are two big differences: 85 | 86 | * You don't need to use Chai, since Jest has its own assertion library (see the https://jestjs.io/docs/en/using-matchers[official documentation]) 87 | * You cannot store objects in `this` inside `beforeEach` 88 | 89 | ```javascript 90 | // test/MyContract.test.js 91 | 92 | const { accounts, contract } = require('@openzeppelin/test-environment'); 93 | 94 | const MyContract = contract.fromArtifact('MyContract'); 95 | let myContract; 96 | 97 | describe('MyContract', function () { 98 | const [ owner ] = accounts; 99 | 100 | beforeEach(async function { 101 | myContract = await MyContract.new({ from: owner }); 102 | }); 103 | 104 | it('the deployer is the owner', async function () { 105 | expect(await myContract.owner()).toEqual(owner); 106 | }); 107 | }); 108 | ``` 109 | 110 | TIP: If migrating from Mocha, you can still use Chai to reduce the number of changes you need to make to your tests. Just be careful not to get Chai's and Jest's `expect` mixed up! 111 | 112 | To install Jest, run: 113 | 114 | ```bash 115 | $ npm install --save-dev jest 116 | ``` 117 | 118 | Jest will execute all tests in files matching `\*.test.js` with: 119 | 120 | ```bash 121 | $ npx jest ./test 122 | ``` 123 | 124 | == Ava 125 | 126 | Ava is a new, modern test runner, product of learnings on the JavaScript ecosystem over the years. As such, it may look different from what you're used to, but it is a great tool that is worth learning how to use. https://github.com/avajs/ava/blob/master/docs/01-writing-tests.md[Their documentation] is a great starting point. 127 | 128 | ```javascript 129 | // test/MyContract.test.js 130 | 131 | import test from 'ava'; 132 | 133 | import { accounts, contract } from '@openzeppelin/test-environment'; 134 | const [ owner ] = accounts; 135 | 136 | const MyContract = contract.fromArtifact('MyContract'); 137 | 138 | test.before(async t => { 139 | t.context.myContract = await MyContract.new({ from: owner }); 140 | }); 141 | 142 | test('the deployer is the owner', async t => { 143 | t.is(await myContract.owner(), owner); 144 | }); 145 | ``` 146 | 147 | To install Ava, run 148 | 149 | ```bash 150 | $ npm install --save-dev ava 151 | ``` 152 | 153 | Ava will execute all tests in all files in the `test` directory with: 154 | 155 | ```bash 156 | $ npx ava 157 | ``` 158 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/getting-started.adoc: -------------------------------------------------------------------------------- 1 | = Getting Started 2 | 3 | This guide will cover what you need to know to start using Test Environment, from installation of dependencies to configuration. 4 | 5 | To learn instead how to migrate an existing Truffle project, check out our xref:migrating-from-truffle.adoc[Migrating from Truffle] guide. 6 | 7 | TIP: If you're new to smart contract testing, you should probably first look at our [guide for automated tests], and then come back here. 8 | 9 | == Installing Dependencies 10 | 11 | Unlike Truffle, Test Environment is a testing _library_: it provides utilities that help you write tests, but it doesn't run your tests for you. 12 | 13 | This is a good thing: it means you are free to use any test runner of your choice. Here we will use https://mochajs.org/[Mocha] (along with https://www.chaijs.com[Chai]): 14 | 15 | ```bash 16 | $ npm install --save-dev @openzeppelin/test-environment mocha chai 17 | ``` 18 | 19 | TIP: Head to our xref:choosing-a-test-runner.adoc[Choosing a Test Runner] guide to learn about different test runners, their pros and cons, and how to use them. 20 | 21 | == Writing Tests 22 | 23 | Each test file should have a `require` statement importing Test Environment. This will bring up the local blockchain where your tests will run, and provide utilities to interact with it. 24 | 25 | ```javascript 26 | const { accounts, contract } = require('@openzeppelin/test-environment'); 27 | ``` 28 | 29 | The exports you will be using the most are `accounts` and `contract`. 30 | 31 | === `accounts` 32 | 33 | This is an array with the addresses of all unlocked accounts in the local blockchain. They are also prefunded, so they can all be used to send transactions. 34 | 35 | A good practice is to use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment[array destructuring] to give meaningful names to each account, such as: 36 | 37 | ```javascript 38 | const [ admin, purchaser, user ] = accounts; 39 | ``` 40 | 41 | This will let you write clearer tests by making it obvious which actor is executing each action: 42 | 43 | ```javascript 44 | const crowdsale = await Crowdsale.new({ from: admin }); 45 | 46 | await crowdsale.buyTokens(amount, { from: purchaser }); 47 | ``` 48 | 49 | NOTE: Unlike Truffle's `accounts` array, the default address (the one that is used when you don't provide one) is **not** included in `accounts`. This will help you avoid common pitfalls while writing tests. Head to the xref:api.adoc#default-sender[API reference] to learn more. 50 | 51 | === `contract` 52 | 53 | You will use this function to load your compiled contracts into JavaScript objects. By default these will be https://www.npmjs.com/package/@truffle/contract[Truffle contracts], but they can be <> to be https://web3js.readthedocs.io/en/v1.2.4/web3-eth-contract.html[web3 contracts]. 54 | 55 | ```javascript 56 | // Loads the built artifact from build/contracts/Crowdsale.json 57 | const Crowdsale = contracts.fromArtifact('Crowdsale'); 58 | ``` 59 | 60 | === Test Cases 61 | 62 | The overall structure of your tests will be determined not by Test Environment, but by your test runner of choice. 63 | 64 | In Mocha, test cases are declared with the `it` function, and grouped in `describe` blocks. 65 | 66 | We'll use the following contract to provide an example. It consists of a simple access control mechanism via xref:contracts:api:ownership.adoc#Ownable[*OpenZeppelin Contracts*' `Ownable`]: 67 | 68 | ```solidity 69 | // contracts/MyContract.sol 70 | 71 | pragma solidity ^0.5.0; 72 | 73 | import "@openzeppelin/contracts/ownership/Ownable.sol"; 74 | 75 | contract MyContract is Ownable { } 76 | ``` 77 | 78 | And here's how testing this contract using Test Environment and Mocha looks like: 79 | 80 | ```javascript 81 | // test/MyContract.test.js 82 | 83 | const { accounts, contract } = require('@openzeppelin/test-environment'); 84 | 85 | const { expect } = require('chai'); 86 | 87 | const MyContract = contract.fromArtifact('MyContract'); 88 | 89 | describe('MyContract', function () { 90 | const [ owner ] = accounts; 91 | 92 | beforeEach(async function { 93 | this.myContract = await MyContract.new({ from: owner }); 94 | }); 95 | 96 | it('the deployer is the owner', async function () { 97 | expect(await this.myContract.owner()).to.equal(owner); 98 | }); 99 | }); 100 | ``` 101 | 102 | In xref:choosing-a-test-runner.adoc[Choosing a Test Runner] you can compare how this example looks in each test runner. 103 | 104 | == Running your Tests 105 | 106 | Test Environment is not executable: tests are run by invoking your runner of choice directly. We recommend that you do this by adding a `test` script to your `package.json`. 107 | 108 | This is what that looks like when using Mocha: 109 | 110 | ```javascript 111 | // package.json 112 | 113 | "scripts": { 114 | "test": "mocha --exit --recursive" 115 | } 116 | ``` 117 | 118 | WARNING: Mocha's `--exit` flag is required when using Truffle contracts. Otherwise, the test suite will not exit. https://github.com/trufflesuite/truffle/issues/2560[Learn more]. 119 | 120 | All set! Running `npm test` to execute the test suite: 121 | 122 | ```bash 123 | $ npm test 124 | 125 | MyContract 126 | ✓ the deployer is the owner 127 | ``` 128 | 129 | [TIP] 130 | ==== 131 | Because we're running Mocha directly, it is very easy to pass additional options to it. Try the following, which will cause Mocha to stop immediately on the first failing test: 132 | 133 | ```bash 134 | $ npm test -- --bail 135 | ``` 136 | ==== 137 | 138 | [[compiling]] 139 | == Compiling your Contracts 140 | 141 | Test Environment requires your contracts to be compiled: you can use https://www.trufflesuite.com/docs/truffle/getting-started/compiling-contracts[*Truffle*] to do this. 142 | 143 | ```bash 144 | $ npx truffle compile 145 | ``` 146 | 147 | Compilation artifacts will be stored in the `build/contracts` directory, where Test Environment (and most other tools) will read them from. 148 | 149 | [TIP] 150 | ==== 151 | You can set your project to recompile all contracts when running tests by adding this step to your `test` script: 152 | 153 | ```javascript 154 | // package.json 155 | 156 | "scripts": { 157 | "test": "truffle compile && mocha --exit --recursive" 158 | } 159 | ``` 160 | ==== 161 | 162 | == Using OpenZeppelin Test Helpers 163 | 164 | Complex assertions, such as testing for reverts or events being emitted, can be performed by using the xref:test-helpers::index.adoc[*OpenZeppelin Test Helpers*]. 165 | 166 | When used alongside Test Environment, there is no need for manual configuration: `require` the helpers and use them as usual. 167 | 168 | [[configuration]] 169 | == Configuration 170 | 171 | Multiple aspects of Test Environment can be configured. The default values are very sensible and should work fine for most testing setups, but you are free to modify these. 172 | 173 | To do this, create a file named `test-environment.config.js` at the root level of your project: its contents will be automatically loaded. 174 | 175 | ```javascript 176 | // test-environment.config.js 177 | 178 | module.exports = { 179 | accounts: { 180 | amount: 10, // Number of unlocked accounts 181 | ether: 100, // Initial balance of unlocked accounts (in ether) 182 | }, 183 | 184 | contracts: { 185 | type: 'truffle', // Contract abstraction to use: 'truffle' for @truffle/contract or 'web3' for web3-eth-contract 186 | defaultGas: 6e6, // Maximum gas for contract calls (when unspecified) 187 | 188 | // Options available since v0.1.2 189 | defaultGasPrice: 20e9, // Gas price for contract calls (when unspecified) 190 | artifactsDir: 'build/contracts', // Directory where contract artifacts are stored 191 | }, 192 | 193 | node: { // Options passed directly to Ganache client 194 | gasLimit: 8e6, // Maximum gas per block 195 | gasPrice: 20e9 // Sets the default gas price for transactions if not otherwise specified. 196 | }, 197 | }; 198 | ``` 199 | 200 | [[advanced-options]] 201 | === Advanced Options 202 | 203 | These settings are meant to support more complex use cases: most applications will not require using them. 204 | 205 | ==== `setupProvider` 206 | 207 | ```javascript 208 | async function setupProvider(baseProvider) 209 | ``` 210 | 211 | Returns a new https://web3js.readthedocs.io/en/v1.2.0/web3.html#providers[web3 provider] that will be used by all contracts and helpers. 212 | 213 | Often used to wrap the base provider in one that performs additional tasks, such as logging or https://docs.openzeppelin.com/gsn-provider[Gas Station Network integration]: 214 | 215 | ```javascript 216 | // test-environment.config.js 217 | 218 | module.exports = { 219 | setupProvider: (baseProvider) => { 220 | const { GSNDevProvider } = require('@openzeppelin/gsn-provider'); 221 | const { accounts } = require('@openzeppelin/test-environment'); 222 | 223 | return new GSNDevProvider(baseProvider, { 224 | txfee: 70, 225 | useGSN: false, 226 | ownerAddress: accounts[8], 227 | relayerAddress: accounts[9], 228 | }); 229 | }, 230 | }; 231 | ``` 232 | 233 | ==== `fork` and `unlocked_accounts` 234 | 235 | These options allow Test Environment's local blockchain to _fork_ an existing one instead of starting from an empty state. By using them you can test how your code interacts with live third party protocols (like the MakerDAO system) without having to deploy them yourself! 236 | 237 | In forked mode, you will also be able to send transactions from any of the `unlocked_accounts` (even if you don't know their private keys!). 238 | 239 | 240 | ```javascript 241 | // test-environment.config.js 242 | 243 | module.exports = { 244 | node: { // Options passed directly to Ganache client 245 | fork: 'https://mainnet.infura.io/v3/{token}@{blocknumber}, // An url to Ethereum node to use as a source for a fork 246 | unlocked_accounts: ['0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'], // Array of addresses specifying which accounts should be unlocked. 247 | }, 248 | }; 249 | ``` 250 | 251 | ==== `allowUnlimitedContractSize` 252 | 253 | Allows unlimited contract sizes. By enabling this flag, the check within the EVM for contract size limit of 24kB (see https://eips.ethereum.org/EIPS/eip-170[EIP-170]) is bypassed. Useful when testing unoptimized contracts that wouldn't be otherwise deployable. 254 | 255 | WARNING: Enabling this flag causes Ganache to behave differently from production environments. 256 | 257 | ```javascript 258 | // test-environment.config.js 259 | 260 | module.exports = { 261 | node: { // Options passed directly to Ganache client 262 | allowUnlimitedContractSize: true, // Allows unlimited contract sizes. 263 | }, 264 | }; 265 | ``` 266 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = Test Environment 2 | 3 | *Blazing fast smart contract testing.* One-line setup for an awesome testing experience. 4 | 5 | * Near-instant start up: have your code running in under 2s after typing `npm test`. 6 | * Test runner agnostic – from the familiarity of Mocha, to xref:choosing-a-test-runner.adoc#parallel-tests[_parallel tests_] using Jest or Ava! 7 | * Non-opinionated: use either https://www.npmjs.com/package/@truffle/contract[`@truffle/contract`] or https://web3js.readthedocs.io/en/v1.2.0/web3-eth-contract.html[`web3-eth-contract`] as you see fit. 8 | * First class support for the xref:test-helpers::index.adoc[OpenZeppelin Test Helpers]. 9 | * Highly configurable: from gas limit and initial balance, to complex custom web3 providers and **forking Mainnet state!** 10 | * No global variables, no hacks. 11 | 12 | _Test Environment is the result of our learnings while developing the xref:contracts::index.adoc[OpenZeppelin Contracts], combining best practices and the tools we've come to rely on over the years. We think you'll love it!_ 13 | 14 | == Overview 15 | 16 | === Installation 17 | 18 | ```bash 19 | $ npm install --save-dev @openzeppelin/test-environment 20 | ``` 21 | 22 | === Usage 23 | 24 | By including `require('@openzeppelin/test-environment')` in your test files, a local https://github.com/trufflesuite/ganache-core[ganache-powered blockchain] with unlocked accounts will be spun up, and all tools configured to work with it. 25 | 26 | ```javascript 27 | const { accounts, contract } = require('@openzeppelin/test-environment'); 28 | const [ owner ] = accounts; 29 | 30 | const { expect } = require('chai'); 31 | 32 | const MyContract = contract.fromArtifact('MyContract'); // Loads a compiled contract 33 | 34 | describe('MyContract', function () { 35 | it('deployer is owner', async function () { 36 | const myContract = await MyContract.new({ from: owner }); 37 | expect(await myContract.owner()).to.equal(owner); 38 | }); 39 | }); 40 | ``` 41 | 42 | If you're used to `truffle test`, this probably looks very familiar. Follow our guide on xref:migrating-from-truffle.adoc[migrating from Truffle] to have your project running with Test Environment in a breeze! 43 | 44 | _Note: if you'd rather not rely on truffle contracts and use web3 contract types directly, worry not: you can xref:getting-started.adoc#configuration[configure Test Environment] to use the `web3-eth-contract` abstraction._ 45 | 46 | == Learn More 47 | 48 | * Check out xref:getting-started.adoc[Getting Started] to use Test Environment in a new project. 49 | * If you are currently using `truffle test`, head instead to xref:migrating-from-truffle.adoc[Migrating from Truffle]. 50 | * The xref:choosing-a-test-runner.adoc[Choosing a Test Runner] guide will teach you how to use each of the different runners. 51 | * For detailed usage information, take a look at the xref:api.adoc[API Reference]. 52 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/migrating-from-truffle.adoc: -------------------------------------------------------------------------------- 1 | = Migrating from Truffle 2 | 3 | Test Environment's scope is much smaller than Truffle's: it is only a _testing library_, while Truffle is an all-out development framework. 4 | 5 | Despite this, it is still quite simple to migrate from a `truffle test`-based suite. Doing the whole process on the xref:contracts::index.adoc[*OpenZeppelin Contracts*] repository took less than thirty minutes! 6 | 7 | NOTE: We will only be replacing `truffle test` in this guide. You can continue using other Truffle commands such as `truffle compile`, `truffle migrate` or `truffle console`. 8 | 9 | == Installing Dependencies 10 | 11 | Since we're not running `truffle test` any more, we'll need a test runner to take care of this. 12 | 13 | Under the hood, Truffle runs tests with a lightly modified https://mochajs.org/[Mocha] (bundled with https://www.chaijs.com/[Chai] for assertions). Because of this, these two make best choice for a simple migration: 14 | 15 | ```bash 16 | $ npm install --save-dev @openzeppelin/test-environment mocha chai 17 | ``` 18 | 19 | TIP: If you want to use a test runner other than Mocha, it is still recommended to first migrate out of Truffle using Mocha, and then switch to your xref:choosing-a-test-runner.adoc[test runner of choice]. 20 | 21 | Don't forget to make Mocha the entry point of your test suite once you install it: 22 | 23 | [source,diff] 24 | ---- 25 | // package.json 26 | 27 | "scripts": { 28 | - "test": "truffle test" 29 | + "test": "mocha --exit --recursive" 30 | } 31 | ---- 32 | 33 | WARNING: Mocha's `--exit` flag is required when using Truffle contracts. Otherwise, the test suite will not exit. https://github.com/trufflesuite/truffle/issues/2560[Learn more]. 34 | 35 | == Switching to Test Environment 36 | 37 | Some bits of your test files will need to be modified during the migration. The changes are only a few, but important: 38 | 39 | 1. Add `require('@openzeppelin/test-environment')` and export relevant objects, such as `accounts`, `contract` and `web3`. 40 | 2. Add `require('chai')` and set it up (Truffle does this automagically). 41 | 3. Replace all instances of `artifacts.require` with `contract.fromArtifact` 42 | 4. Replace all instances of Truffle's `contract` function with a regular Mocha `describe`. You can still access the accounts array in `accounts`. 43 | 44 | That's it! Let's see what a full migration might look like: 45 | 46 | [source,diff] 47 | ---- 48 | +const { accounts, contract, web3 } = require('@openzeppelin/test-environment'); 49 | 50 | // Setup Chai for 'expect' or 'should' style assertions (you only need one) 51 | +const { expect } = require('chai'); 52 | +require('chai').should(); 53 | 54 | -const ERC20 = artifacts.require('ERC20'); 55 | +const ERC20 = contract.fromArtifact('ERC20'); 56 | 57 | -contract('ERC20', function (accounts) { 58 | +describe('ERC20', function () { 59 | ... 60 | } 61 | ---- 62 | 63 | == Running Tests 64 | 65 | With all changes in place, you are are now ready to run your tests: 66 | 67 | ```bash 68 | $ npm test 69 | 70 | ERC20 71 | total supply 72 | ✓ returns the total amount of tokens 73 | balanceOf 74 | when the requested account has no tokens 75 | ✓ returns zero 76 | when the requested account has some tokens 77 | ✓ returns the total amount of tokens 78 | transfer 79 | when the recipient is not the zero address 80 | ... 81 | ``` 82 | 83 | You will notice your tests start executing almost immediately: the xref:contracts::index.adoc[*OpenZeppelin Contracts*] have over 150 contracts and 2000 individual tests, and **testing setup time is under 2 seconds**. 84 | 85 | Enjoy lightning fast testing! 86 | 87 | NOTE: Unlike Truffle, Test Environment will not compile your contracts. You can do this manually with `truffle compile`. 88 | 89 | == Learn More 90 | 91 | * Check out how to xref:getting-started.adoc#configuration[configure your new testing environment]. 92 | * The xref:choosing-a-test-runner.adoc[Choosing a Test Runner] guide will teach you how to use each of the different runners. 93 | * For detailed usage information, take a look at the xref:api.adoc[API Reference]. 94 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | globals: { 7 | 'ts-jest': { 8 | diagnostics: true, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run docs" 3 | publish = "build/site" 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openzeppelin/test-environment", 3 | "version": "0.1.9", 4 | "description": "One-line setup for an awesome testing experience.", 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "compile": "rm -rf lib && tsc", 9 | "docs": "oz-docs -c docs", 10 | "docs:watch": "npm run docs watch", 11 | "prepare": "npm run compile", 12 | "test:integration": "node scripts/run-integration-test.js", 13 | "test:unit": "jest --verbose", 14 | "test": "npm run test:unit && npm run test:integration", 15 | "watch": "tsc -w", 16 | "lint": "yarn lint:check --fix", 17 | "lint:check": "npx eslint 'src/**/*.{js,ts}' --quiet" 18 | }, 19 | "files": [ 20 | "lib", 21 | "!*.test.js", 22 | "!*.test.js.map", 23 | "!*.test.d.ts" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/OpenZeppelin/openzeppelin-test-environment.git" 28 | }, 29 | "keywords": [ 30 | "smart-contracts", 31 | "solidity", 32 | "ethereum", 33 | "test", 34 | "javascript", 35 | "web3", 36 | "test" 37 | ], 38 | "author": "Francisco Giordano , Nicolás Venturo , Igor Yalovoy ", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/OpenZeppelin/openzeppelin-test-environment/issues" 42 | }, 43 | "homepage": "https://github.com/OpenZeppelin/openzeppelin-test-environment#readme", 44 | "devDependencies": { 45 | "@openzeppelin/docs-utils": "^0.1.0", 46 | "@types/fs-extra": "^9.0.1", 47 | "@types/jest": "^24.0.23", 48 | "@types/lodash.merge": "^4.6.6", 49 | "@types/node": "^12.12.7", 50 | "@types/semver": "^7.1.0", 51 | "@typescript-eslint/eslint-plugin": "^2.1.0", 52 | "@typescript-eslint/parser": "^2.1.0", 53 | "code-style": "git+https://github.com/OpenZeppelin/code-style.git", 54 | "eslint": "^6.3.0", 55 | "eslint-config-prettier": "^6.2.0", 56 | "eslint-plugin-prettier": "^3.1.0", 57 | "ethereumjs-util": "^6.2.0", 58 | "jest": "^25.2.4", 59 | "prettier": "^2.0.4", 60 | "ts-jest": "^25.3.0", 61 | "typescript": "^3.6.2" 62 | }, 63 | "dependencies": { 64 | "@openzeppelin/contract-loader": "^0.6.1", 65 | "@truffle/contract": "^4.0.38", 66 | "ansi-colors": "^4.1.1", 67 | "ethereumjs-wallet": "^0.6.3", 68 | "exit-hook": "^2.2.0", 69 | "find-up": "^4.1.0", 70 | "fs-extra": "^9.0.1", 71 | "ganache-core": "^2.11.2", 72 | "lodash.merge": "^4.6.2", 73 | "p-queue": "^6.2.0", 74 | "semver": "^7.1.3", 75 | "try-require": "^1.2.1", 76 | "web3": "^1.3.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scripts/run-integration-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable */ 4 | 5 | const path = require('path'); 6 | const { promises: fs } = require('fs'); 7 | const { promisify } = require('util'); 8 | const { once } = require('events'); 9 | const proc = require('child_process'); 10 | const execFile = promisify(proc.execFile); 11 | 12 | const args = process.argv.slice(2); 13 | 14 | if (args[0] === 'run-tarball') { 15 | run(args.slice(2), args[1]); 16 | } else if (args[0] === 'pack') { 17 | pack(args[1]); 18 | } else { 19 | run(args); 20 | } 21 | 22 | async function run(tests, package) { 23 | if (tests.length === 0) { 24 | tests = await fs.readdir('test-integration'); 25 | } else { 26 | for (const test of tests) { 27 | if (!await exists(`test-integration/${test}`)) { 28 | throw new Error(`Integration test '${test}' does not exist`); 29 | } 30 | } 31 | } 32 | 33 | if (package === undefined) { 34 | package = await pack(package); 35 | } else if (!await exists(package)) { 36 | console.error(`File not found: '${package}'\n`); 37 | process.exit(1); 38 | } 39 | 40 | for (const test of tests) { 41 | console.error(`[test:integration running ${test}]`); 42 | const cwd = `test-integration/${test}`; 43 | if (!await exists(`${cwd}/node_modules`)) { 44 | await spawn('npm', ['ci'], { cwd, stdio: 'inherit' }); 45 | } 46 | 47 | await spawn('npm', ['install', '--no-save', path.resolve(package)], { cwd, stdio: 'inherit' }); 48 | await spawn('npm', ['test'], { cwd, stdio: 'inherit' }); 49 | } 50 | } 51 | 52 | async function pack(dest) { 53 | console.error(`[test:integration packing tarball]`); 54 | const { stdout } = await execFile('npm', ['pack']); 55 | const package = stdout.match(/\n(.+)\n$/)[1]; 56 | if (dest === undefined) { 57 | return package; 58 | } else { 59 | await fs.mkdir(path.dirname(dest), { recursive: true }); 60 | await fs.rename(package, dest); 61 | return dest; 62 | } 63 | } 64 | 65 | async function exists(path) { 66 | try { 67 | await fs.access(path); 68 | return true; 69 | } catch (e) { 70 | if (e.code === 'ENOENT') { 71 | return false; 72 | } else { 73 | throw e; 74 | } 75 | } 76 | } 77 | 78 | async function spawn(...args) { 79 | const child = proc.spawn(...args); 80 | const [code] = await once(child, 'exit'); 81 | if (code !== 0) { 82 | throw new Error(`Process exited with an error`); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/__mocks__/child_process.ts: -------------------------------------------------------------------------------- 1 | export const fork = jest.fn(() => { 2 | return { 3 | send: jest.fn(), 4 | once: jest.fn((event: string, listener: any) => { 5 | listener({ 6 | type: 'ready', 7 | port: 42, 8 | }); 9 | }), 10 | unref: jest.fn(), 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /src/accounts.test.ts: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import { privateToAddress, toChecksumAddress } from 'ethereumjs-util'; 3 | 4 | import { generateAccounts } from './accounts'; 5 | 6 | describe('generateAccounts function', (): void => { 7 | it('generates a required number of accounts with a right amount of ether', (): void => { 8 | const accountsNumber = 34; 9 | const balance = 234324; 10 | const config = generateAccounts(accountsNumber, balance); 11 | 12 | expect(config.accounts.length).toBe(accountsNumber); 13 | expect(config.privateKeys.length).toBe(accountsNumber); 14 | expect(config.accountsConfig.length).toBe(accountsNumber); 15 | 16 | config.privateKeys.forEach((privateKey, index) => { 17 | const address = privateToAddress(Buffer.from(privateKey.replace(/0x/, ''), 'hex')); 18 | const checksumAddress = toChecksumAddress(address.toString('hex')); 19 | expect(checksumAddress).toBe(config.accounts[index]); 20 | }); 21 | 22 | for (const account of config.accountsConfig) { 23 | expect(account.balance).toBe(Web3.utils.toWei(balance.toString(), 'ether')); 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/accounts.ts: -------------------------------------------------------------------------------- 1 | import wallet, { Wallet } from 'ethereumjs-wallet'; 2 | import Web3 from 'web3'; 3 | 4 | import config from './config'; 5 | import { AccountConfig } from './setup-ganache'; 6 | 7 | const { utils } = Web3; 8 | 9 | function getConfig(ether: number) { 10 | return function (wallet: Wallet): AccountConfig { 11 | return { 12 | balance: utils.toWei(ether.toString(), 'ether'), 13 | secretKey: wallet.getPrivateKeyString(), 14 | }; 15 | }; 16 | } 17 | 18 | function generateAccounts(count: number, ether: number) { 19 | const wallets = Array.from({ length: count }, wallet.generate); 20 | const accounts = wallets.map((w) => w.getChecksumAddressString()); 21 | const accountsConfig = wallets.map(getConfig(ether)); 22 | const privateKeys = accountsConfig.map((c) => c.secretKey); 23 | return { accounts, privateKeys, accountsConfig }; 24 | } 25 | 26 | const { accounts: allAccounts, privateKeys: allPrivateKeys, accountsConfig } = generateAccounts( 27 | config.accounts.amount + 1, // extra account for the default sender 28 | config.accounts.ether, 29 | ); 30 | 31 | // We use the first account as the default sender (when no sender is specified), 32 | // which provides versatility for tests where this sender is not important 33 | // (e.g. when calling view functions). 34 | // We also don't expose this account so that it is not possible to explicitly 35 | // use it, creating a scenario where the default account and an explicit account 36 | // are the same one, which can create hard to debug failing tests. 37 | 38 | const defaultSender = allAccounts[0]; 39 | const accounts = allAccounts.slice(1); 40 | const privateKeys = allPrivateKeys.slice(1); 41 | 42 | export { accounts, privateKeys, accountsConfig, defaultSender, getConfig, generateAccounts }; 43 | -------------------------------------------------------------------------------- /src/config.test.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_BLOCK_GAS_LIMIT, Config, getConfig } from './config'; 2 | 3 | const defaultConfig: Config = { 4 | accounts: { 5 | amount: 10, 6 | ether: 100, 7 | }, 8 | 9 | contracts: { 10 | type: 'truffle', 11 | defaultGas: DEFAULT_BLOCK_GAS_LIMIT * 0.75, 12 | defaultGasPrice: 20e9, // 20 gigawei 13 | artifactsDir: 'build/contracts', 14 | }, 15 | 16 | setupProvider: async (baseProvider) => baseProvider, 17 | 18 | coverage: false, 19 | 20 | node: { 21 | allowUnlimitedContractSize: false, 22 | gasLimit: DEFAULT_BLOCK_GAS_LIMIT, 23 | gasPrice: '0x4a817c800', // 20 gigawei 24 | }, 25 | }; 26 | 27 | const coverageConfig: Config = { 28 | accounts: { 29 | amount: 10, 30 | ether: 100, 31 | }, 32 | 33 | contracts: { 34 | type: 'truffle', 35 | defaultGas: 0xffffffffff, 36 | defaultGasPrice: 1, 37 | artifactsDir: 'build/contracts', 38 | }, 39 | 40 | setupProvider: async (baseProvider) => baseProvider, 41 | 42 | coverage: true, 43 | 44 | node: { 45 | allowUnlimitedContractSize: false, 46 | gasLimit: DEFAULT_BLOCK_GAS_LIMIT, 47 | gasPrice: '0x4a817c800', // 20 gigawei 48 | }, 49 | }; 50 | 51 | describe('config', (): void => { 52 | it('provides default value', (): void => { 53 | // because setupProvider wouldn't match with toEqual 54 | const config = getConfig(); 55 | expect(JSON.stringify(config)).toBe(JSON.stringify(defaultConfig)); 56 | }); 57 | it('provide correct config for coverage', (): void => { 58 | process.env.OZ_TEST_ENV_COVERAGE = 'true'; 59 | const config = getConfig(); 60 | expect(JSON.stringify(config)).toBe(JSON.stringify(coverageConfig)); 61 | process.env.OZ_TEST_ENV_COVERAGE = undefined; 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import findUp from 'find-up'; 3 | import merge from 'lodash.merge'; 4 | 5 | import { warn } from './log'; 6 | import type { Provider } from './provider'; 7 | 8 | const CONFIG_FILE = 'test-environment.config.js'; 9 | 10 | const configHelpUrl = 'https://zpl.in/test-env-config'; 11 | 12 | interface InputConfig { 13 | accounts: { amount: number; ether: number }; 14 | contracts: { type: string; defaultGas: number; defaultGasPrice: number; artifactsDir: string }; 15 | blockGasLimit?: number; 16 | gasPrice?: number; 17 | setupProvider: (baseProvider: Provider) => Promise; 18 | coverage: boolean; 19 | node: { 20 | gasLimit?: number; 21 | gasPrice?: number | string; 22 | allowUnlimitedContractSize?: boolean; 23 | // {url:port@blocknumber} for example http://localhost:8545@1599200 24 | fork?: string; 25 | unlocked_accounts?: string[]; 26 | }; 27 | } 28 | 29 | export type Config = InputConfig & { 30 | node: { 31 | gasPrice?: string; 32 | }; 33 | }; 34 | 35 | export const DEFAULT_BLOCK_GAS_LIMIT = 8e6; 36 | 37 | const defaultConfig: InputConfig = { 38 | accounts: { 39 | amount: 10, 40 | ether: 100, 41 | }, 42 | 43 | contracts: { 44 | type: 'truffle', 45 | defaultGas: DEFAULT_BLOCK_GAS_LIMIT * 0.75, 46 | defaultGasPrice: 20e9, // 20 gigawei 47 | artifactsDir: 'build/contracts', 48 | }, 49 | 50 | setupProvider: async (baseProvider) => baseProvider, 51 | 52 | coverage: false, 53 | 54 | node: { 55 | allowUnlimitedContractSize: false, 56 | gasLimit: DEFAULT_BLOCK_GAS_LIMIT, 57 | gasPrice: 20e9, // 20 gigawei 58 | }, 59 | }; 60 | 61 | export function getConfig(): Config { 62 | const location = findUp.sync(CONFIG_FILE, { type: 'file' }); 63 | const providedConfig: Partial = 64 | location !== undefined && fs.existsSync(location) ? require(location) : {}; 65 | 66 | if (providedConfig.blockGasLimit !== undefined) { 67 | warn(`blockGasLimit is deprecated. Use node.gasLimit instead. See ${configHelpUrl} for details.`); 68 | } 69 | 70 | if (providedConfig.gasPrice !== undefined) { 71 | warn(`Please move gasPrice option inside node option. See ${configHelpUrl} for more details.`); 72 | } 73 | 74 | if (providedConfig.gasPrice !== undefined && providedConfig.node?.gasPrice !== undefined) { 75 | throw new Error( 76 | `GasPrice is specified twice in config. Please fix your config. See ${configHelpUrl} for more details.`, 77 | ); 78 | } 79 | 80 | if (!!providedConfig.blockGasLimit && !!providedConfig.node?.gasLimit) { 81 | throw new Error( 82 | `GasLimit is specified twice in config. Please fix your config. See ${configHelpUrl} for more details.`, 83 | ); 84 | } 85 | 86 | const config: InputConfig = merge(defaultConfig, providedConfig); 87 | 88 | if (config.gasPrice !== undefined) config.node.gasPrice = config.gasPrice; 89 | if (config.blockGasLimit) config.node.gasLimit = config.blockGasLimit; 90 | 91 | if (config.node.gasPrice !== undefined && typeof config.node.gasPrice !== 'string') 92 | config.node.gasPrice = `0x${config.node.gasPrice.toString(16)}`; 93 | 94 | if (process.env.OZ_TEST_ENV_COVERAGE !== undefined) { 95 | config.coverage = true; 96 | config.contracts.defaultGas = 0xffffffffff; 97 | config.contracts.defaultGasPrice = 1; 98 | } 99 | 100 | return config as Config; 101 | } 102 | 103 | export default getConfig(); 104 | -------------------------------------------------------------------------------- /src/coverage.ts: -------------------------------------------------------------------------------- 1 | import { once } from 'events'; 2 | import { log } from './log'; 3 | import { fork, execSync } from 'child_process'; 4 | import path from 'path'; 5 | import { existsSync } from 'fs'; 6 | import { removeSync, moveSync } from 'fs-extra'; 7 | import exitHook from 'exit-hook'; 8 | 9 | /* eslint-disable @typescript-eslint/no-var-requires */ 10 | export async function runCoverage(skipFiles: string[], compileCommand: string, testCommand: string[]): Promise { 11 | const client = require('ganache-cli'); 12 | const CoverageAPI = require('solidity-coverage/api'); 13 | const utils = require('solidity-coverage/utils'); 14 | 15 | const api = new CoverageAPI({ client }); 16 | 17 | const config = { 18 | workingDir: process.cwd(), 19 | contractsDir: path.join(process.cwd(), 'contracts'), 20 | logger: { 21 | log: (msg: string): void => log(msg), 22 | }, 23 | }; 24 | 25 | try { 26 | const { tempContractsDir, tempArtifactsDir } = utils.getTempLocations(config); 27 | 28 | function cleanUp(): void { 29 | if (existsSync('./contracts-backup/')) { 30 | moveSync('./contracts-backup', './contracts', { overwrite: true }); 31 | } 32 | removeSync('./build/contracts/'); 33 | removeSync(tempArtifactsDir); 34 | removeSync(tempContractsDir); 35 | } 36 | 37 | exitHook(cleanUp); 38 | 39 | utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir); 40 | 41 | const { targets, skipped } = utils.assembleFiles(config, skipFiles); 42 | const instrumented = api.instrument(targets); 43 | utils.save(instrumented, config.contractsDir, tempContractsDir); 44 | utils.save(skipped, config.contractsDir, tempContractsDir); 45 | 46 | // backup original contracts 47 | moveSync('./contracts/', './contracts-backup'); 48 | moveSync(tempContractsDir, './contracts/'); 49 | 50 | // compile instrumented contracts 51 | execSync(compileCommand); 52 | 53 | // run tests 54 | const forked = fork(testCommand[0], testCommand.slice(1), { 55 | env: { 56 | ...process.env, 57 | cwd: __dirname, 58 | OZ_TEST_ENV_COVERAGE: 'TRUE', 59 | }, 60 | }); 61 | 62 | const [accounts] = await once(forked, 'message'); 63 | api.providerOptions = { accounts: accounts }; 64 | 65 | // run Ganache 66 | const address = await api.ganache(); 67 | 68 | // start test-env tests 69 | forked.send(address); 70 | 71 | // wait for the tests to finish 72 | await once(forked, 'close'); 73 | 74 | // Clean up before writing report 75 | cleanUp(); 76 | 77 | // write a report 78 | await api.report(); 79 | } catch (e) { 80 | log(e); 81 | process.exitCode = 1; 82 | } finally { 83 | await utils.finish(config, api); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ganache-server.ts: -------------------------------------------------------------------------------- 1 | import ganache from 'ganache-core'; 2 | import events from 'events'; 3 | 4 | import type { Message, NodeOptions } from './setup-ganache'; 5 | import type { Server } from 'net'; 6 | import { getConfig } from './config'; 7 | 8 | function send(msg: Message): void { 9 | if (process.send === undefined) { 10 | throw new Error('Module must be started through child_process.fork'); 11 | } 12 | process.send(msg, (err: Error) => { 13 | if (err) process.exit(); 14 | }); 15 | } 16 | 17 | function setupServer(nodeOptions: NodeOptions): Server { 18 | if (!nodeOptions.coverage) { 19 | return ganache.server(nodeOptions); 20 | } else { 21 | return require('ganache-core-coverage').server({ 22 | ...nodeOptions, 23 | emitFreeLogs: true, 24 | allowUnlimitedContractSize: true, 25 | }); 26 | } 27 | } 28 | 29 | process.once('message', async (options: NodeOptions) => { 30 | const config = getConfig(); 31 | const server = setupServer({ ...options, ...config.node }); 32 | 33 | process.on('disconnect', () => { 34 | server.close(); 35 | }); 36 | 37 | // An undefined port number makes ganache-core choose a random free port, 38 | // which plays nicely with environments such as jest and ava, where multiple 39 | // processes of test-environment may be run in parallel. 40 | // It also means however that the port (and therefore host URL) is not 41 | // available until the server finishes initialization. 42 | server.listen(undefined); 43 | 44 | try { 45 | await events.once(server, 'listening'); 46 | const addr = server.address(); 47 | if (typeof addr === 'object' && addr !== null) { 48 | send({ type: 'ready', port: addr.port }); 49 | } else { 50 | send({ type: 'error' }); 51 | } 52 | } catch (err) { 53 | send({ type: 'error' }); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import tryRequire from 'try-require'; 2 | import semver from 'semver'; 3 | 4 | import { defaultSender } from './accounts'; 5 | import config from './config'; 6 | import { provider } from './setup-provider'; 7 | import { warn } from './log'; 8 | 9 | let configured = false; 10 | 11 | const testHelpersPackage = tryRequire('@openzeppelin/test-helpers/package.json'); 12 | if (testHelpersPackage !== undefined) { 13 | // TODO: skip if already configured? 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-var-requires 16 | const configure = require('@openzeppelin/test-helpers/configure'); 17 | 18 | const version = testHelpersPackage.version; 19 | if (semver.satisfies(version, '^0.5.4')) { 20 | // The 'singletons' field was introduced in 0.5.4 21 | configure({ 22 | provider, 23 | singletons: { 24 | abstraction: config.contracts.type, 25 | defaultGas: config.contracts.defaultGas, 26 | defaultSender, 27 | }, 28 | }); 29 | 30 | configured = true; 31 | } else if (semver.satisfies(version, '^0.5.0 <0.5.4')) { 32 | // Whitespaces indicate intersection ('and') in semver 33 | // Alternatively, 'environment' was available from 0.5.0, but the gas and 34 | // sender could not be configured 35 | configure({ provider, environment: config.contracts.type }); 36 | 37 | configured = true; 38 | } else { 39 | warn( 40 | `Currently installed version of @openzeppelin/test-helpers (${version}) is unsupported, cannot configure. 41 | 42 | Please upgrade to v0.5.0 or newer: 43 | npm install --save-dev @openzeppelin/test-helpers@latest`, 44 | ); 45 | } 46 | } 47 | 48 | export default configured; 49 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import isHelpersConfigured from './helpers'; 2 | import { web3, provider } from './setup-provider'; 3 | import { accounts, privateKeys, defaultSender } from './accounts'; 4 | import contract from './setup-loader'; 5 | import { runCoverage } from './coverage'; 6 | import config from './config'; 7 | 8 | export { accounts, privateKeys, defaultSender, web3, provider, contract, isHelpersConfigured, runCoverage, config }; 9 | -------------------------------------------------------------------------------- /src/log.ts: -------------------------------------------------------------------------------- 1 | import colors from 'ansi-colors'; 2 | 3 | export function log(msg: string): void { 4 | console.log(`${colors.white.bgBlack('@openzeppelin/test-environment')} ${msg}`); 5 | } 6 | 7 | export function warn(msg: string): void { 8 | log(`${colors.black.bgYellow('WARN')} ${msg}`); 9 | } 10 | -------------------------------------------------------------------------------- /src/provider.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers'; 2 | 3 | export type JsonRpcCallback = (error: Error | null, result?: JsonRpcResponse) => void; 4 | 5 | export interface Provider { 6 | send(payload: JsonRpcPayload, callback: JsonRpcCallback): void; 7 | } 8 | -------------------------------------------------------------------------------- /src/setup-ganache.test.ts: -------------------------------------------------------------------------------- 1 | import setupGanache from './setup-ganache'; 2 | import { mocked } from 'ts-jest/utils'; 3 | import { fork } from 'child_process'; 4 | import config from './config'; 5 | import { accountsConfig } from './accounts'; 6 | 7 | jest.mock('child_process'); 8 | 9 | describe('setupGanache', (): void => { 10 | it('forks ganache-server', async (): Promise => { 11 | const forkMock = mocked(fork); 12 | expect(await setupGanache()).toBe('http://localhost:42'); 13 | expect(fork).toHaveBeenCalled(); 14 | const server = forkMock.mock.results[0]; 15 | expect(server.value.once).toHaveBeenCalled(); 16 | expect(server.value.unref).toHaveBeenCalled(); 17 | }); 18 | 19 | it('sends accounts in coverage mode', async (): Promise => { 20 | config.coverage = true; 21 | const mockSend = jest.spyOn(process, 'send').mockImplementation(() => { 22 | return true; 23 | }); 24 | const mockOn = jest.spyOn(process, 'on').mockImplementation((message, resolve) => { 25 | const trueResolve = resolve as (message: any, sendHandle: any) => void; 26 | trueResolve('42', undefined); 27 | return process; 28 | }); 29 | expect(await setupGanache()).toBe('42'); 30 | expect(mockSend).toHaveBeenCalledWith(accountsConfig); 31 | config.coverage = false; 32 | mockSend.mockRestore(); 33 | mockOn.mockRestore(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/setup-ganache.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { fork, ChildProcess } from 'child_process'; 3 | 4 | import { accountsConfig } from './accounts'; 5 | import config from './config'; 6 | 7 | interface ErrorMessage { 8 | type: 'error'; 9 | } 10 | 11 | interface ReadyMessage { 12 | type: 'ready'; 13 | port: number; 14 | } 15 | 16 | export type Message = ErrorMessage | ReadyMessage; 17 | 18 | export type AccountConfig = { 19 | balance: string; 20 | secretKey: string; 21 | }; 22 | 23 | export type NodeOptions = { 24 | accounts: AccountConfig[]; 25 | coverage: boolean; 26 | gasPrice?: string; 27 | gasLimit?: number; 28 | allowUnlimitedContractSize?: boolean; 29 | fork?: string; 30 | unlocked_accounts?: string[]; 31 | }; 32 | 33 | export default async function (): Promise { 34 | if (!config.coverage) { 35 | const server = fork(path.join(__dirname, 'ganache-server'), [], { 36 | // Prevent the child process from also being started in inspect mode, which 37 | // would cause issues due to parent and child sharing the port. 38 | // See https://github.com/OpenZeppelin/openzeppelin-test-environment/pull/23 39 | execArgv: process.execArgv.filter((opt) => opt !== '--inspect'), 40 | }); 41 | 42 | const options: NodeOptions = { 43 | accounts: accountsConfig, 44 | coverage: false, 45 | }; 46 | server.send(options); 47 | 48 | const messageReceived: Promise = new Promise( 49 | (resolve): ChildProcess => { 50 | return server.once('message', resolve); 51 | }, 52 | ); 53 | 54 | const message = await messageReceived; 55 | 56 | switch (message.type) { 57 | case 'ready': 58 | if (server.channel) { 59 | server.channel.unref(); 60 | } 61 | server.unref(); 62 | return `http://localhost:${message.port}`; 63 | case 'error': 64 | server.kill(); 65 | throw new Error('Unhandled server error'); 66 | default: 67 | throw new Error(`Uknown server message: '${message}'`); 68 | } 69 | } else { 70 | if (process.send === undefined) { 71 | throw new Error('Module must be started through child_process.fork for solidity-coverage support.'); 72 | } 73 | process.send(accountsConfig); 74 | const address: string = await new Promise( 75 | (resolve): NodeJS.Process => { 76 | return process.on('message', resolve); 77 | }, 78 | ); 79 | return address; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/setup-loader.ts: -------------------------------------------------------------------------------- 1 | import { setupLoader } from '@openzeppelin/contract-loader'; 2 | 3 | import config from './config'; 4 | import { defaultSender } from './accounts'; 5 | import { provider } from './setup-provider'; 6 | 7 | if (config.contracts.type !== 'truffle' && config.contracts.type !== 'web3') { 8 | throw new Error(`Unknown contract type: '${config.contracts.type}'`); 9 | } 10 | 11 | export default setupLoader({ 12 | provider, 13 | defaultSender, 14 | defaultGas: config.contracts.defaultGas, 15 | defaultGasPrice: config.contracts.defaultGasPrice, 16 | artifactsDir: config.contracts.artifactsDir, 17 | })[config.contracts.type]; 18 | -------------------------------------------------------------------------------- /src/setup-provider.ts: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import { provider } from 'web3-core'; 3 | 4 | import TestProvider from './test-provider'; 5 | 6 | const provider = new TestProvider(); 7 | 8 | // because web3 types is a joke 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | const web3 = new Web3(provider as any); 11 | 12 | export { web3, provider }; 13 | -------------------------------------------------------------------------------- /src/test-provider.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-ignore */ 2 | import PQueue from 'p-queue'; 3 | import type { JsonRpcPayload } from 'web3-core-helpers'; 4 | 5 | import { mocked } from 'ts-jest/utils'; 6 | 7 | import { flushPromises } from './utils'; 8 | import TestProvider from './test-provider'; 9 | import config from './config'; 10 | import setupGanache from './setup-ganache'; 11 | 12 | jest.mock('p-queue'); 13 | const mockedPQueue = mocked(PQueue, true); 14 | 15 | jest.mock('./config'); 16 | const mockedConfig = mocked(config, true); 17 | 18 | jest.mock('./setup-ganache'); 19 | const mockedGanache = mocked(setupGanache); 20 | 21 | describe('TestProvider class', (): void => { 22 | let provider: TestProvider; 23 | beforeEach(() => { 24 | provider = new TestProvider(); 25 | }); 26 | 27 | it('constructs TestProvider instance', async () => { 28 | expect(mockedPQueue).toHaveBeenCalled(); 29 | const queueInstance = mockedPQueue.mock.instances[0]; 30 | expect(queueInstance.add).toHaveBeenCalled(); 31 | 32 | const addFunc = mocked(queueInstance.add).mock.calls[0][0]; 33 | await addFunc(); 34 | expect(mockedGanache).toHaveBeenCalled(); 35 | 36 | expect(mockedConfig.setupProvider).toHaveBeenCalled(); 37 | }); 38 | 39 | it('sends a request', async () => { 40 | const request: JsonRpcPayload = { 41 | jsonrpc: '2.0', 42 | method: 'web3_getAllTheMoney', 43 | params: [], 44 | id: 324, 45 | }; 46 | // eslint-disable-next-line @typescript-eslint/no-empty-function 47 | const callback = (): void => {}; 48 | 49 | // @ts-ignore 50 | provider.queue.onIdle = jest.fn((): Promise => Promise.resolve()); 51 | // @ts-ignore 52 | provider.wrappedProvider = { 53 | send: jest.fn(), 54 | }; 55 | 56 | provider.send(request, callback); 57 | 58 | await flushPromises(); 59 | 60 | // @ts-ignore 61 | expect(provider.queue.onIdle).toHaveBeenCalled(); 62 | 63 | // @ts-ignore 64 | expect(provider.wrappedProvider.send).toHaveBeenCalledWith(request, callback); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/test-provider.ts: -------------------------------------------------------------------------------- 1 | import PQueue from 'p-queue'; 2 | 3 | import Web3 from 'web3'; 4 | import type { JsonRpcPayload } from 'web3-core-helpers'; 5 | import type { Provider, JsonRpcCallback } from './provider'; 6 | 7 | import setupGanache from './setup-ganache'; 8 | import config from './config'; 9 | 10 | export default class TestProvider implements Provider { 11 | private queue: PQueue; 12 | private wrappedProvider?: Provider; 13 | sendAsync: (payload: JsonRpcPayload, callback: JsonRpcCallback) => void; 14 | 15 | constructor() { 16 | this.sendAsync = this.send.bind(this); 17 | this.queue = new PQueue({ concurrency: 1 }); 18 | 19 | this.queue.add(async () => { 20 | // Setup node 21 | const url = await setupGanache(); 22 | // Create base provider (connection to node) 23 | const baseProvider = new Web3(url).eth.currentProvider as Provider; 24 | 25 | // Create a custom provider (e.g. GSN provider) and wrap it 26 | this.wrappedProvider = await config.setupProvider(baseProvider); 27 | }); 28 | } 29 | 30 | public send(payload: JsonRpcPayload, callback: JsonRpcCallback): void { 31 | this.queue.onIdle().then(() => { 32 | // wrapped provider is always not a null due to PQueue running the provider initialization 33 | // before any send calls yet TypeScript can't possibly knows that 34 | this.wrappedProvider?.send(payload, callback); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/typing/ethereumjs-wallet.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'ethereumjs-wallet' { 2 | function generate(): Wallet; 3 | 4 | class Wallet { 5 | getPrivateKeyString(): string; 6 | getAddress(): Buffer; 7 | getChecksumAddressString(): string; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/typing/try-require.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'try-require' { 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | function require(module: string): any; 4 | 5 | namespace require { 6 | function resolve(module: string): string; 7 | } 8 | 9 | export = require; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function flushPromises(): Promise { 2 | return new Promise((resolve): NodeJS.Immediate => setImmediate(resolve)); 3 | } 4 | -------------------------------------------------------------------------------- /test-integration/ava/contracts/FooBar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract FooBar { 4 | 5 | function foo() public pure returns (string memory) { 6 | return "bar"; 7 | } 8 | 9 | function reverts() public pure returns (uint) { 10 | revert("Just do it!"); 11 | } 12 | 13 | function requires(uint answer) public pure returns (uint) { 14 | require(answer == 42, "Wrong answer"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /test-integration/ava/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ava-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "npx oz compile", 8 | "test": "npm run compile && npx ava" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@openzeppelin/cli": "^2.5.3", 14 | "ava": "^2.4.0", 15 | "prettier": "^1.18.2", 16 | "web3": "^1.2.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test-integration/ava/test/foo-bar.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | const { accounts, contract } = require('@openzeppelin/test-environment'); 4 | const [ deployer ] = accounts; 5 | 6 | const FooBar = contract.fromArtifact('FooBar'); 7 | 8 | test.before(async t => { 9 | t.context.fooBar = await FooBar.new(); 10 | }); 11 | 12 | test('foo with a bar', async t => { 13 | t.is(await t.context.fooBar.foo(), 'bar'); 14 | }); 15 | 16 | test('reverts a transaction', async t => { 17 | const error = await t.throwsAsync(t.context.fooBar.reverts()); 18 | t.regex(error.message, /Just do it!/); 19 | }); 20 | 21 | test('fails requires', async t => { 22 | const error = await t.throwsAsync(t.context.fooBar.requires(324)); 23 | t.regex(error.message, /Wrong answer/); 24 | }); 25 | 26 | test('pass require with a right answer ', async t => { 27 | const ret = await t.context.fooBar.requires(42); 28 | t.not(ret, null); 29 | }); 30 | -------------------------------------------------------------------------------- /test-integration/jest/contracts/FooBar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract FooBar { 4 | 5 | function foo() public pure returns (string memory) { 6 | return "bar"; 7 | } 8 | 9 | function reverts() public pure returns (uint) { 10 | revert("Just do it!"); 11 | } 12 | 13 | function requires(uint answer) public pure returns (uint) { 14 | require(answer == 42, "Wrong answer"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /test-integration/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "npx oz compile", 8 | "test": "npm run compile && npx jest --testTimeout 10000" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@openzeppelin/cli": "^2.5.3", 14 | "jest": "^24.9.0", 15 | "prettier": "^1.18.2", 16 | "web3": "^1.2.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test-integration/jest/test/foo-bar.test.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract } = require('@openzeppelin/test-environment'); 2 | const [ deployer ] = accounts; 3 | 4 | const FooBar = contract.fromArtifact('FooBar'); 5 | let fooBar; 6 | 7 | describe('FooBar', function() { 8 | beforeEach(async function() { 9 | fooBar = await FooBar.new(); 10 | }); 11 | 12 | it('foo with a bar', async function() { 13 | expect(await fooBar.foo()).toEqual('bar'); 14 | }); 15 | 16 | it('reverts a transaction', async function() { 17 | try { 18 | const ret = await fooBar.reverts(); 19 | } catch (err) { 20 | return expect(err.message).toMatch(/Just do it/); 21 | } 22 | expect.fail('Expected an exception but none was received'); 23 | }); 24 | 25 | it('fails requires', async function() { 26 | try { 27 | const ret = await fooBar.requires(324); 28 | } catch (err) { 29 | return expect(err.message).toMatch(/Wrong answer/); 30 | } 31 | expect.fail('Expected an exception but none was received'); 32 | }); 33 | 34 | it('pass require with a right answer ', async function() { 35 | const ret = await fooBar.requires(42); 36 | expect(ret).not.toBeNull(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test-integration/mocha-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npx mocha --exit --timeout 10000" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "bn.js": "^5.0.0", 13 | "chai": "^4.2.0", 14 | "lodash.uniq": "^4.5.0", 15 | "mocha": "^6.2.0", 16 | "prettier": "^1.18.2", 17 | "web3": "^1.2.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test-integration/mocha-config/test-environment.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | accounts: { 3 | amount: 20, 4 | ether: 1e6, 5 | }, 6 | 7 | node: { 8 | allowUnlimitedContractSize: true, 9 | gasLimit: 7000000, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test-integration/mocha-config/test/config.test.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract, provider, web3 } = require('@openzeppelin/test-environment'); 2 | const { expect } = require('chai'); 3 | const BN = require('bn.js'); 4 | 5 | const config = require('../test-environment.config.js'); 6 | 7 | const uniq = require('lodash.uniq'); 8 | 9 | const Fatty = contract.fromArtifact('Fatty'); 10 | 11 | describe('config', function() { 12 | describe('accounts', function() { 13 | it('the number of unlocked accounts can be configured', function() { 14 | expect(accounts.length).to.equal(config.accounts.amount); 15 | }); 16 | 17 | it('all accounts are different', function() { 18 | expect(uniq(accounts).length).to.equal(config.accounts.amount); 19 | }); 20 | 21 | it('accounts initial can be configured', async function() { 22 | // We check the balance of the last account, in the hope that it hasn't been used yet and therefore has the full 23 | // unspent initial balance 24 | expect(await web3.eth.getBalance(accounts[accounts.length - 1])).to.equal( 25 | new BN(config.accounts.ether).mul(new BN((1e18).toString())).toString(), 26 | ); 27 | }); 28 | }); 29 | 30 | describe('blockchain', function() { 31 | it('the block gas limit can be configured', async function() { 32 | await web3.eth.sendTransaction({ from: accounts[0], to: accounts[1], gas: config.node.gasLimit }); 33 | 34 | try { 35 | await web3.eth.sendTransaction({ from: accounts[0], to: accounts[1], gas: config.node.gasLimit + 1 }); 36 | expect.fail('Transaction over gas limit was executed'); 37 | } catch {} 38 | }); 39 | 40 | it('the unlimited size contract option is supported', async function() { 41 | const fatty = await Fatty.new(); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test-integration/mocha-defaults/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npx mocha --exit --timeout 10000" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "chai": "^4.2.0", 13 | "lodash.uniq": "^4.5.0", 14 | "mocha": "^6.2.0", 15 | "prettier": "^1.18.2", 16 | "web3": "^1.2.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test-integration/mocha-defaults/test/defaultConfig.test.js: -------------------------------------------------------------------------------- 1 | const { accounts, provider, contract, web3, defaultSender } = require('@openzeppelin/test-environment'); 2 | const { expect } = require('chai'); 3 | 4 | const uniq = require('lodash.uniq'); 5 | 6 | const Fatty = contract.fromArtifact('Fatty'); 7 | 8 | describe('default config', function() { 9 | describe('accounts', function() { 10 | it('there are ten unlocked accounts', function() { 11 | expect(accounts.length).to.equal(10); 12 | }); 13 | 14 | it('all accounts are different', function() { 15 | expect(uniq(accounts).length).to.equal(10); 16 | }); 17 | 18 | it('accounts initial balance is 100 ether', async function() { 19 | // We check the balance of the last account, in the hope that it hasn't been used yet and therefore has the full 20 | // unspent initial balance 21 | expect(await web3.eth.getBalance(accounts[accounts.length - 1])).to.equal((100 * 1e18).toString()); 22 | }); 23 | 24 | it('the default sender is not on the accounts array', async function() { 25 | expect(accounts).to.not.include(defaultSender); 26 | }); 27 | }); 28 | 29 | describe('blockchain', function() { 30 | it('gasLimit is 8 000 000', async function() { 31 | await web3.eth.sendTransaction({ from: accounts[0], to: accounts[1], gas: 8000000 }); 32 | 33 | try { 34 | await web3.eth.sendTransaction({ from: accounts[0], to: accounts[1], gas: 8000001 }); 35 | expect.fail('Transaction over gas limit was executed'); 36 | } catch {} 37 | }); 38 | 39 | it('fails to deploy a contract over the limit', async function() { 40 | try { 41 | const fatty = await Fatty.new(); 42 | expect.fail('Attempted to deploy a contract over the limit'); 43 | } catch {} 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test-integration/mocha-fork/contracts/DAI.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2019-11-14 3 | */ 4 | 5 | // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol 6 | pragma solidity =0.5.12; 7 | 8 | ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol 9 | // This program is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU General Public License as published by 11 | // the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU General Public License for more details. 18 | 19 | // You should have received a copy of the GNU General Public License 20 | // along with this program. If not, see . 21 | 22 | /* pragma solidity 0.5.12; */ 23 | 24 | contract LibNote { 25 | event LogNote( 26 | bytes4 indexed sig, 27 | address indexed usr, 28 | bytes32 indexed arg1, 29 | bytes32 indexed arg2, 30 | bytes data 31 | ) anonymous; 32 | 33 | modifier note { 34 | _; 35 | assembly { 36 | // log an 'anonymous' event with a constant 6 words of calldata 37 | // and four indexed topics: selector, caller, arg1 and arg2 38 | let mark := msize // end of memory ensures zero 39 | mstore(0x40, add(mark, 288)) // update free memory pointer 40 | mstore(mark, 0x20) // bytes type data offset 41 | mstore(add(mark, 0x20), 224) // bytes size (padded) 42 | calldatacopy(add(mark, 0x40), 0, 224) // bytes payload 43 | log4(mark, 288, // calldata 44 | shl(224, shr(224, calldataload(0))), // msg.sig 45 | caller, // msg.sender 46 | calldataload(4), // arg1 47 | calldataload(36) // arg2 48 | ) 49 | } 50 | } 51 | } 52 | 53 | ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol 54 | // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico 55 | 56 | // This program is free software: you can redistribute it and/or modify 57 | // it under the terms of the GNU Affero General Public License as published by 58 | // the Free Software Foundation, either version 3 of the License, or 59 | // (at your option) any later version. 60 | // 61 | // This program is distributed in the hope that it will be useful, 62 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 63 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 64 | // GNU Affero General Public License for more details. 65 | // 66 | // You should have received a copy of the GNU Affero General Public License 67 | // along with this program. If not, see . 68 | 69 | /* pragma solidity 0.5.12; */ 70 | 71 | /* import "./lib.sol"; */ 72 | 73 | contract Dai is LibNote { 74 | // --- Auth --- 75 | mapping (address => uint) public wards; 76 | function rely(address guy) external note auth { wards[guy] = 1; } 77 | function deny(address guy) external note auth { wards[guy] = 0; } 78 | modifier auth { 79 | require(wards[msg.sender] == 1, "Dai/not-authorized"); 80 | _; 81 | } 82 | 83 | // --- ERC20 Data --- 84 | string public constant name = "Dai Stablecoin"; 85 | string public constant symbol = "DAI"; 86 | string public constant version = "1"; 87 | uint8 public constant decimals = 18; 88 | uint256 public totalSupply; 89 | 90 | mapping (address => uint) public balanceOf; 91 | mapping (address => mapping (address => uint)) public allowance; 92 | mapping (address => uint) public nonces; 93 | 94 | event Approval(address indexed src, address indexed guy, uint wad); 95 | event Transfer(address indexed src, address indexed dst, uint wad); 96 | 97 | // --- Math --- 98 | function add(uint x, uint y) internal pure returns (uint z) { 99 | require((z = x + y) >= x); 100 | } 101 | function sub(uint x, uint y) internal pure returns (uint z) { 102 | require((z = x - y) <= x); 103 | } 104 | 105 | // --- EIP712 niceties --- 106 | bytes32 public DOMAIN_SEPARATOR; 107 | // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); 108 | bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; 109 | 110 | constructor(uint256 chainId_) public { 111 | wards[msg.sender] = 1; 112 | DOMAIN_SEPARATOR = keccak256(abi.encode( 113 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 114 | keccak256(bytes(name)), 115 | keccak256(bytes(version)), 116 | chainId_, 117 | address(this) 118 | )); 119 | } 120 | 121 | // --- Token --- 122 | function transfer(address dst, uint wad) external returns (bool) { 123 | return transferFrom(msg.sender, dst, wad); 124 | } 125 | function transferFrom(address src, address dst, uint wad) 126 | public returns (bool) 127 | { 128 | require(balanceOf[src] >= wad, "Dai/insufficient-balance"); 129 | if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { 130 | require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance"); 131 | allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad); 132 | } 133 | balanceOf[src] = sub(balanceOf[src], wad); 134 | balanceOf[dst] = add(balanceOf[dst], wad); 135 | emit Transfer(src, dst, wad); 136 | return true; 137 | } 138 | function mint(address usr, uint wad) external auth { 139 | balanceOf[usr] = add(balanceOf[usr], wad); 140 | totalSupply = add(totalSupply, wad); 141 | emit Transfer(address(0), usr, wad); 142 | } 143 | function burn(address usr, uint wad) external { 144 | require(balanceOf[usr] >= wad, "Dai/insufficient-balance"); 145 | if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) { 146 | require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance"); 147 | allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad); 148 | } 149 | balanceOf[usr] = sub(balanceOf[usr], wad); 150 | totalSupply = sub(totalSupply, wad); 151 | emit Transfer(usr, address(0), wad); 152 | } 153 | function approve(address usr, uint wad) external returns (bool) { 154 | allowance[msg.sender][usr] = wad; 155 | emit Approval(msg.sender, usr, wad); 156 | return true; 157 | } 158 | 159 | // --- Alias --- 160 | function push(address usr, uint wad) external { 161 | transferFrom(msg.sender, usr, wad); 162 | } 163 | function pull(address usr, uint wad) external { 164 | transferFrom(usr, msg.sender, wad); 165 | } 166 | function move(address src, address dst, uint wad) external { 167 | transferFrom(src, dst, wad); 168 | } 169 | 170 | // --- Approve by signature --- 171 | function permit(address holder, address spender, uint256 nonce, uint256 expiry, 172 | bool allowed, uint8 v, bytes32 r, bytes32 s) external 173 | { 174 | bytes32 digest = 175 | keccak256(abi.encodePacked( 176 | "\x19\x01", 177 | DOMAIN_SEPARATOR, 178 | keccak256(abi.encode(PERMIT_TYPEHASH, 179 | holder, 180 | spender, 181 | nonce, 182 | expiry, 183 | allowed)) 184 | )); 185 | 186 | require(holder != address(0), "Dai/invalid-address-0"); 187 | require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit"); 188 | require(expiry == 0 || now <= expiry, "Dai/permit-expired"); 189 | require(nonce == nonces[holder]++, "Dai/invalid-nonce"); 190 | uint wad = allowed ? uint(-1) : 0; 191 | allowance[holder][spender] = wad; 192 | emit Approval(holder, spender, wad); 193 | } 194 | } -------------------------------------------------------------------------------- /test-integration/mocha-fork/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --exit --timeout 10000" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "bn.js": "^5.0.0", 13 | "chai": "^4.2.0", 14 | "lodash.uniq": "^4.5.0", 15 | "mocha": "^6.2.0", 16 | "prettier": "^1.18.2", 17 | "web3": "^1.2.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test-integration/mocha-fork/test-environment.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | accounts: { 3 | amount: 20, 4 | ether: 1e6, 5 | }, 6 | 7 | node: { 8 | fork: 'https://mainnet.infura.io/v3/95202223388e49f48b423ea50a70e336', 9 | unlocked_accounts: ['0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'], 10 | gasLimit: 7000000, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /test-integration/mocha-fork/test/config.test.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract, web3 } = require('@openzeppelin/test-environment'); 2 | const BN = require('bn.js'); 3 | const { expect } = require('chai'); 4 | 5 | const DAI = contract.fromArtifact('Dai'); 6 | 7 | const { first } = accounts; 8 | 9 | describe('forked blockchain', function() { 10 | it('gets correct name for DAI stablecoin', async function() { 11 | const inst = await DAI.at('0x6B175474E89094C44Da98b954EedeAC495271d0F'); 12 | const name = await inst.name(); 13 | expect(name).to.be.eq('Dai Stablecoin'); 14 | }); 15 | it('gets a contracts bytecode', async function() { 16 | const code = await web3.eth.getCode('0x6B175474E89094C44Da98b954EedeAC495271d0F'); 17 | expect(code).to.match(/0x608060405234801561001057600080fd5b50600436106101425760003560e01c80637ecebe001/); 18 | }); 19 | it('can spend Vitalik ether', async function() { 20 | const hash = await web3.eth.sendTransaction({ 21 | from: '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B', 22 | to: first, 23 | value: web3.utils.toWei('1000', 'ether'), 24 | }); 25 | expect(hash.transactionHash).to.not.be.undefined; 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test-integration/mocha/contracts/FooBar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract FooBar { 4 | 5 | function foo() public pure returns (string memory) { 6 | return "bar"; 7 | } 8 | 9 | function reverts() public pure returns (uint) { 10 | revert("Just do it!"); 11 | } 12 | 13 | function requires(uint answer) public pure returns (uint) { 14 | require(answer == 42, "Wrong answer"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /test-integration/mocha/coverage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { runCoverage } = require('@openzeppelin/test-environment'); 4 | 5 | runCoverage([], 'npm run compile', './node_modules/.bin/mocha --exit --timeout 10000 --recursive'.split(' ')); 6 | -------------------------------------------------------------------------------- /test-integration/mocha/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocha-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "compile": "npx oz compile", 8 | "test": "npm run compile && npx mocha --exit --timeout 10000 && npm run coverage && rm -rf ./coverage && rm -rf ./coverage.json", 9 | "coverage": "node ./coverage.js" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "chai": "^4.2.0", 15 | "ganache-cli": "^6.9.1", 16 | "mocha": "^6.2.0", 17 | "prettier": "^1.18.2", 18 | "solidity-coverage": "^0.7.7", 19 | "web3": "^1.2.1" 20 | }, 21 | "dependencies": { 22 | "@openzeppelin/cli": "^2.5.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-integration/mocha/test/foo-bar.test.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract } = require('@openzeppelin/test-environment'); 2 | const [ deployer ] = accounts; 3 | 4 | const { expect } = require('chai'); 5 | 6 | const FooBar = contract.fromArtifact('FooBar'); 7 | 8 | describe('FooBar', function() { 9 | beforeEach(async function() { 10 | this.fooBar = await FooBar.new(); 11 | }); 12 | 13 | it('foo with a bar', async function() { 14 | expect(await this.fooBar.foo()).to.be.equal('bar'); 15 | }); 16 | 17 | it('reverts a transaction', async function() { 18 | try { 19 | const ret = await this.fooBar.reverts(); 20 | } catch (err) { 21 | return expect(err.message).to.match(/Just do it!/); 22 | } 23 | expect.fail('Expected an exception but none was received'); 24 | }); 25 | 26 | it('fails requires', async function() { 27 | try { 28 | const ret = await this.fooBar.requires(324); 29 | } catch (err) { 30 | return expect(err.message).to.match(/Wrong answer/); 31 | } 32 | expect.fail('Expected an exception but none was received'); 33 | }); 34 | 35 | it('pass require with a right answer ', async function() { 36 | const ret = await this.fooBar.requires(42); 37 | expect(ret).to.not.be.null; 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "code-style/tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "outDir": "./lib", 6 | }, 7 | "include": ["./src"] 8 | } 9 | --------------------------------------------------------------------------------