diff --git a/packages/govern-tx/.gitignore b/packages/govern-tx/.gitignore new file mode 100644 index 000000000..076c97772 --- /dev/null +++ b/packages/govern-tx/.gitignore @@ -0,0 +1,2 @@ +coverage/ +dev-data/ diff --git a/packages/govern-tx/README.md b/packages/govern-tx/README.md new file mode 100644 index 000000000..1d1113033 --- /dev/null +++ b/packages/govern-tx/README.md @@ -0,0 +1,66 @@ +# Govern TX + +The govern-tx package does include the transaction service of the govern project. +This service can get deployed on any server or cloud infrastructure who has nodejs installed. + +## Commands + +- ``yarn dev`` - Starts the docker containers and the server in dev mode +- ``yarn start`` - Starts the docker containers and the server in production mode +- ``yarn start:server`` - Starts the server without starting the docker containers +- ``yarn start:containers`` - Starts the docker containers +- ``yarn stop:containers`` - Stops the docker containers +- ``yarn test`` - Does execute the unit and e2e tests of this package + +## Postgres DB diagramm + +![DB Diagramm](./assets/db_model.png) + +## Directory Structure + +Overall are the thoughts to have a modularized structure with the separation between abstract classes and concrete implementations with ``lib`` and ``src``. +Means ``lib`` and ``src`` should (If abstraction is required for all module) have the same base structure. This is important to increase the readability. + +``` +# All concrete implementations are located in the ./src/ folder +./src/ + +# Authentication handler used in the fastify pre-handler +./src/auth + +# Configratuon object with the required validations of the input +./src/config + +# DB wrapper and entities used in the server actions +./src/db + +# Provider class used to connect to a Ethereum node +./src/provider + +# All transactions related action commands +./src/transactions + +# The wallet we use to sign the transactions +./src/wallet + +# All whitelist related CRUD action commands +./src/whitelist + +# This class is used to boot the entire server and is used in ./src/index.ts +./Bootstrap.ts + +# All abstract classes and helpers are located in the ./lib/ folder +./lib/ + +# Base abstraction for all actions +./lib/AbstractAction.ts + +# Base abstraction for all transaction related actions and VO for contract function encoding +./lib/transactions + +# Base abstraction for all whiteliste related CRUD actions +./lib/whitelist + +# Home of all tests (e2e & unit) +./test +``` diff --git a/packages/govern-tx/assets/db_model.png b/packages/govern-tx/assets/db_model.png new file mode 100644 index 000000000..facf4e440 Binary files /dev/null and b/packages/govern-tx/assets/db_model.png differ diff --git a/packages/govern-tx/config/dev.config.ts b/packages/govern-tx/config/dev.config.ts new file mode 100644 index 000000000..f67d2ae34 --- /dev/null +++ b/packages/govern-tx/config/dev.config.ts @@ -0,0 +1,24 @@ +import { Config } from '../src/config/Configuration'; + +export const config: Config = { + ethereum: { + publicKey: '', // Add default EOA + contracts: { + GovernQueue: '', // Add deployment address can get calculated if deployed with create2 + GovernBaseFactory: '' + }, + url: 'http://localhost:8545', + blockConfirmations: 42 + }, + database: { + user: 'govern-tx', + host: 'localhost', + password: 'tx-service', + database: 'govern-tx', + port: 5432 + }, + server: { + host: '0.0.0.0', + port: 4040 + } +} diff --git a/packages/govern-tx/config/prod.config.ts b/packages/govern-tx/config/prod.config.ts new file mode 100644 index 000000000..83db549ab --- /dev/null +++ b/packages/govern-tx/config/prod.config.ts @@ -0,0 +1,24 @@ +import { Config } from '../src/config/Configuration'; + +export const config: Config = { + ethereum: { + publicKey: '', // Add default EOA + contracts: { + GovernQueue: '', // Add deployment address can get calculated if deployed with create2 + GovernBaseFactory: '' + }, + url: 'localhost:8545', + blockConfirmations: 42 + }, + database: { + user: 'govern-tx', + host: 'localhost', + password: 'tx-service', + database: 'govern-tx', + port: 5432 + }, + server: { + host: '0.0.0.0', + port: 4040 + } +} diff --git a/packages/govern-tx/docker-compose.yml b/packages/govern-tx/docker-compose.yml new file mode 100644 index 000000000..8ff5d75b3 --- /dev/null +++ b/packages/govern-tx/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3" +services: + postgres: + image: postgres + ports: + - "5432:5432" + environment: + POSTGRES_USER: govern-tx + POSTGRES_PASSWORD: tx-service + POSTGRES_DB: govern-tx + user: "${HOST_UID}:${HOST_GID}" + volumes: + - ./dev-data/postgres:/var/lib/postgresql/data + - ./postgres:/docker-entrypoint-initdb.d/ + ganache: + image: trufflesuite/ganache-cli + ports: + - "8545:8545" diff --git a/packages/govern-tx/jest.config.e2e.js b/packages/govern-tx/jest.config.e2e.js new file mode 100644 index 000000000..1315a4696 --- /dev/null +++ b/packages/govern-tx/jest.config.e2e.js @@ -0,0 +1,4 @@ +const jestConfig = require('./jest.config.js') +jestConfig.testMatch = ['/**/**Test.e2e.ts'] + +module.exports = jestConfig; diff --git a/packages/govern-tx/jest.config.js b/packages/govern-tx/jest.config.js new file mode 100644 index 000000000..69cf730de --- /dev/null +++ b/packages/govern-tx/jest.config.js @@ -0,0 +1,25 @@ +module.exports = { + rootDir: './', + preset: 'ts-jest', + notifyMode: 'success-change', + collectCoverage: true, + coverageDirectory: './coverage/', + coverageThreshold: { + global: { + functions: 80, + lines: 80, + statements: 80 + } + }, + notify: true, + clearMocks: true, + resetMocks: true, + resetModules: true, + testMatch: ['/**/**Test.ts'], + bail: true, + coveragePathIgnorePatterns: [ + 'node_modules', + 'dist' + ] + } + \ No newline at end of file diff --git a/packages/govern-tx/lib/AbstractAction.ts b/packages/govern-tx/lib/AbstractAction.ts new file mode 100644 index 000000000..b3d40c03e --- /dev/null +++ b/packages/govern-tx/lib/AbstractAction.ts @@ -0,0 +1,75 @@ +import { FastifySchema, FastifyRequest } from 'fastify' + +export interface Params { + message: string | any, + signature: string, +} + +export default abstract class AbstractAction { + /** + * The given request by the user + * + * @var {Request} parameters + */ + protected request: FastifyRequest; + + /** + * @param {Request} parameters + * + * @constructor + */ + constructor(request: FastifyRequest) { + this.request = this.validateRequest(request); + } + + /** + * Validates the given request body. + * + * @method validateRequest + * + * @param {Request} request + * + * @returns {Request} + * + * @protected + */ + protected validateRequest(request: FastifyRequest): FastifyRequest { + return request; + } + + /** + * Executes the actual action + * + * @method execute + * + * @returns {Promise} + * + * @public + */ + public abstract execute(): Promise + + /** + * Returns the required request schema + * + * @property schema + * + * @returns {FastifySchema} + */ + public static get schema(): FastifySchema { + return { + body: { + type: 'object', + required: ['message', 'signature'], + properties: { + message: { + oneOf: [ + { type: 'string'}, + { type: 'object'} + ] + }, + signature: { type: 'string' } + } + } + } as FastifySchema + } +} diff --git a/packages/govern-tx/lib/transactions/AbstractTransaction.ts b/packages/govern-tx/lib/transactions/AbstractTransaction.ts new file mode 100644 index 000000000..fd7aac3f3 --- /dev/null +++ b/packages/govern-tx/lib/transactions/AbstractTransaction.ts @@ -0,0 +1,109 @@ +import { FastifySchema, FastifyRequest } from 'fastify'; +import { TransactionReceipt } from '@ethersproject/abstract-provider' +import { JsonFragment } from '@ethersproject/abi'; +import AbstractAction from '../AbstractAction' +import ContractFunction from '../transactions/ContractFunction' +import Provider from '../../src/provider/Provider' +import { EthereumOptions } from '../../src/config/Configuration'; +import Whitelist from '../../src/db/Whitelist'; +import { Params } from '../AbstractAction'; +import { AuthenticatedRequest } from '../../src/auth/Authenticator'; + +export default abstract class AbstractTransaction extends AbstractAction { + /** + * The function ABI used to create a transaction + * + * @property {Object} functionABI + * + * @protected + */ + protected functionABI: JsonFragment + + /** + * The contract name + * + * @property {string} contract + * + * @protected + */ + protected contract: string + + /** + * @param {EthereumOptions} config - Ethereum related configurations + * @param {Provider} provider - The Ethereum provider object + * @param {Whitelist} whitelist - Whitelist DB adapter + * @param {Request} request - The request body given by the user + * @param {string} publicKey - The public key of the user + * + * @constructor + */ + constructor( + private config: EthereumOptions, + private provider: Provider, + private whitelist: Whitelist, + request: FastifyRequest, + ) { + super(request) + } + + /** + * Executes the transaction and returns the TransactionReceipt + * + * @method execute + * + * @returns {Promise} + * + * @public + */ + public async execute(): Promise { + const contractFunction = new ContractFunction( + this.functionABI, + (this.request.params as Params).message + ) + contractFunction.functionArguments[0].payload.submitter = this.config.publicKey + + let receipt: TransactionReceipt = await this.provider.sendTransaction(this.contract, contractFunction) + + if(!(this.request as AuthenticatedRequest).admin) { + this.whitelist.increaseExecutionCounter((this.request as AuthenticatedRequest).publicKey) + } + + return receipt; + } + + /** + * TODO: Test BigNumber handling of the response and the fastify schema validation + * + * Returns the schema of a transaction command + * + * @property {FastifySchema} schema + * + * @returns {FastifySchema} + */ + public static get schema(): FastifySchema { + const schema = AbstractAction.schema + + schema.response = { + 200: { + type: 'object', + properties: { + to: { type: 'string' }, + from: { type: 'string' }, + contractAddress: { type: 'string' }, + transactionIndex: { type: 'number' }, + gasUsed: { type: 'object' }, // BigNumber + logsBloom: { type: 'string' }, + blockHash: { type: 'string' }, + transactionHash: { type: 'string' }, + logs: { type: 'array' }, + confirmations: { type: 'number' }, + cumulativeGasUsed: { type: 'object'}, // BigNumber + byzantium: { type: 'boolean' }, + status: { type: 'number' } + } + } + } + + return schema + } +} diff --git a/packages/govern-tx/lib/transactions/ContractFunction.ts b/packages/govern-tx/lib/transactions/ContractFunction.ts new file mode 100644 index 000000000..b85e20bbb --- /dev/null +++ b/packages/govern-tx/lib/transactions/ContractFunction.ts @@ -0,0 +1,66 @@ +import { defaultAbiCoder, Fragment, JsonFragment } from '@ethersproject/abi'; +import { id } from '@ethersproject/hash' + +export default class ContractFunction { + /** + * The decoded function arguments + * + * @property {Result} functionArguments + * + * @private + */ + public functionArguments: any[]; + + /** + * The ABI item as ethers.js Fragment object + * + * @property {Fragment} abiItem + */ + private abiItem: Fragment; + + /** + * @param {any} abiItem + * @param {string} requestMsg + * + * @constructor + */ + constructor( + abiItem: JsonFragment, + private requestMsg: string, + ) { + this.abiItem = Fragment.fromObject(abiItem) + this.functionArguments = this.decode() + } + + /** + * Encodes the function by the given ABI item and the function parameters + * + * @method encode + * + * @returns {string} + * + * @public + */ + public encode(): string { + return id(this.abiItem.format()) + + ( + defaultAbiCoder.encode( + this.abiItem.inputs, + this.functionArguments + ).replace('0x','') + ) + } + + /** + * Returns the decoded values by the given ABI item and the request message + * + * @method decode + * + * @returns {Result} + * + * @public + */ + public decode(): any[] { + return defaultAbiCoder.decode(this.abiItem.inputs, '0x' + this.requestMsg.slice(10)) as any[] + } +} diff --git a/packages/govern-tx/lib/whitelist/AbstractWhitelistAction.ts b/packages/govern-tx/lib/whitelist/AbstractWhitelistAction.ts new file mode 100644 index 000000000..fb0da93a8 --- /dev/null +++ b/packages/govern-tx/lib/whitelist/AbstractWhitelistAction.ts @@ -0,0 +1,46 @@ +import { FastifyRequest, FastifySchema } from 'fastify'; +import AbstractAction, { Params } from '../AbstractAction' +import Whitelist, {ListItem} from '../../src/db/Whitelist' + +export interface WhitelistParams extends Params { + message: { + publicKey: string, + txLimit?: number + } +} + +export default abstract class AbstractWhitelistAction extends AbstractAction { + /** + * @param {Whitelist} whitelist - The whitelist database entitiy + * @param {WhitelistRequest} request - The given request body by the user + * + * @constructor + */ + constructor(protected whitelist: Whitelist, request: FastifyRequest) { + super(request) + } + + /** + * Executes the actual action + * + * @method execute + * + * @returns {Promise} + * + * @public + */ + public abstract execute(): Promise + + /** + * TODO: Define response validation + * + * Returns the schema of a transaction command + * + * @property {FastifySchema} schema + * + * @returns {FastifySchema} + */ + public static get schema(): FastifySchema { + return AbstractAction.schema + } +} diff --git a/packages/govern-tx/package.json b/packages/govern-tx/package.json new file mode 100644 index 000000000..e43743e42 --- /dev/null +++ b/packages/govern-tx/package.json @@ -0,0 +1,41 @@ +{ + "name": "govern-tx", + "version": "1.0.0-beta.12", + "description": "Transactions service of Govern", + "main": "./src/index.js", + "scripts": { + "dev": "yarn start:containers && DEV=true ts-node-dev src/index.ts", + "start": "node --loader ts-node/esm.mjs src/index.ts", + "start:containers": "docker-compose up -d", + "stop:containers": "docker-compose down", + "e2e": "yarn start && jest -c ./jest.config.e2e.js", + "test": "jest && yarn e2e" + }, + "authors": [ + { + "name": "Samuel Furter", + "homepage": "https://github.com/nivida" + } + ], + "license": "GPL-3.0", + "devDependencies": { + "@ethereumjs/config-tsc": "^1.1.1", + "@ethereumjs/config-tslint": "^1.1.1", + "@types/jest": "^26.0.15", + "@types/node": "^14.14.10", + "jest": "^26.6.1", + "ts-jest": "^26.4.2", + "tslint": "^6.1.3", + "typescript": "^4.0.5" + }, + "dependencies": { + "@ethersproject/address": "^5.0.7", + "@ethersproject/bytes": "^5.0.6", + "@ethersproject/hash": "^5.0.9", + "@ethersproject/providers": "^5.0.15", + "@ethersproject/wallet": "^5.0.8", + "@ethersproject/strings": "^5.0.7", + "fastify": "^3.8.0", + "postgres": "^2.0.0-beta.2" + } +} diff --git a/packages/govern-tx/postgres/init.sql b/packages/govern-tx/postgres/init.sql new file mode 100644 index 000000000..f9e5fe106 --- /dev/null +++ b/packages/govern-tx/postgres/init.sql @@ -0,0 +1,20 @@ +CREATE TABLE whitelist ( + PublicKey CHAR(42) PRIMARY KEY, + TxLimit INT DEFAULT 100 NOT NULL, + Executed INT DEFAULT 0 NOT NULL +); + +CREATE TABLE admins ( + PublicKey CHAR(42) PRIMARY KEY +); + +CREATE TABLE wallet { + PublicKey CHAR(42) PRIMARY KEY, + PrivateKey CHAR(64) NOT NULL +} + +INSERT INTO whitelist (PublicKey, TxLimit) VALUES ('0x6FA59C2C8EAaCC1f8875794f2DAe145Bb32Bd9B1', '100'); + +INSERT INTO admins (PublicKey) VALUES ('0x6FA59C2C8EAaCC1f8875794f2DAe145Bb32Bd9B1'); + +INSERT INTO wallet (PublicKey, PrivateKey) VALUES ('0x6FA59C2C8EAaCC1f8875794f2DAe145Bb32Bd9B1', '3d4ba04a9c7b159a998d146760cba981ea05784404e38c6fa0a2fe852fbdd648'); diff --git a/packages/govern-tx/src/Bootstrap.ts b/packages/govern-tx/src/Bootstrap.ts new file mode 100644 index 000000000..a0e7e723b --- /dev/null +++ b/packages/govern-tx/src/Bootstrap.ts @@ -0,0 +1,263 @@ +import fastify, { FastifyInstance, FastifyRequest } from 'fastify' +import { TransactionReceipt } from '@ethersproject/abstract-provider' + +import Configuration from './config/Configuration' +import Provider from './provider/Provider' +import Wallet from './wallet/Wallet' + +import Database from './db/Database' +import Whitelist, { ListItem } from './db/Whitelist' +import Admin from './db/Admin' +import Authenticator from './auth/Authenticator' + +import ExecuteTransaction from './transactions/execute/ExecuteTransaction' +import ChallengeTransaction from './transactions/challenge/ChallengeTransaction' +import ScheduleTransaction from './transactions/schedule/ScheduleTransaction' +import CreateTransaction from './transactions/create/CreateTransaction'; + +import AddItemAction from './whitelist/AddItemAction' +import DeleteItemAction from './whitelist/DeleteItemAction' +import GetListAction from './whitelist/GetListAction' +import AbstractTransaction from '../lib/transactions/AbstractTransaction' +import AbstractWhitelistAction from '../lib/whitelist/AbstractWhitelistAction' + +export default class Bootstrap { + /** + * @property {FastifyInstance} server + * + * @private + */ + private server: FastifyInstance + + /** + * @property {Authenticator} authenticator + * + * @private + */ + private authenticator: Authenticator + + /** + * @property {Whitelist} whitelist + * + * @private + */ + private whitelist: Whitelist + + /** + * @property {Provider} provider + * + * @private + */ + private provider: Provider + + /** + * @property {Database} database + * + * @private + */ + private database: Database + + /** + * @param {Configuration} config + * + * @constructor + */ + constructor(private config: Configuration) { + this.setServer() + this.setDatabase() + this.setProvider() + this.setupAuth() + this.registerTransactionRoutes() + this.registerWhitelistRoutes() + } + + /** + * Starts the entire server + * + * @method run + * + * @returns {void} + * + * @public + */ + public run(): void { + this.server.listen( + this.config.server.port, + this.config.server.host, + (error: Error, address: string): void => { + if (error) { + console.error(error) + process.exit(0) + } + + console.log(`Server is listening at ${address}`) + } + ) + } + + /** + * Register all transaction related routes + * + * @method registerTransactionRoutes + * + * @returns {void} + * + * @private + */ + private registerTransactionRoutes(): void { + this.server.post( + '/execute', + {schema: AbstractTransaction.schema}, + (request: FastifyRequest): Promise => { + return new ExecuteTransaction( + this.config.ethereum, + this.provider, + this.whitelist, + request + ).execute() + } + ) + + this.server.post( + '/schedule', + {schema: AbstractTransaction.schema}, + (request: FastifyRequest): Promise => { + return new ScheduleTransaction( + this.config.ethereum, + this.provider, + this.whitelist, + request + ).execute() + } + ) + + this.server.post( + '/challenge', + {schema: AbstractTransaction.schema}, + (request: FastifyRequest): Promise => { + return new ChallengeTransaction( + this.config.ethereum, + this.provider, + this.whitelist, + request + ).execute() + } + ) + + this.server.post( + '/create', + {schema: AbstractTransaction.schema}, + (request: FastifyRequest): Promise => { + return new CreateTransaction( + this.config.ethereum, + this.provider, + this.whitelist, + request + ).execute() + } + ) + } + + /** + * Register all whitelist related routes + * + * @method registerWhitelistRoutes + * + * @returns {void} + * + * @private + */ + private registerWhitelistRoutes(): void { + this.server.post( + '/whitelist', + {schema: AbstractWhitelistAction.schema}, + (request: FastifyRequest): Promise => { + return new AddItemAction(this.whitelist, request).execute() + } + ) + + this.server.delete( + '/whitelist', + {schema: AbstractWhitelistAction.schema}, + (request: FastifyRequest): Promise => { + return new DeleteItemAction(this.whitelist, request).execute() + } + ) + + this.server.get( + '/whitelist', + {schema: AbstractWhitelistAction.schema}, + (request: FastifyRequest): Promise => { + return new GetListAction(this.whitelist, request).execute() + } + ) + } + + /** + * Inititates the server instance + * + * @method setServer + * + * @returns {void} + * + * @private + */ + private setServer(): void { + this.server = fastify({ + logger: { + level: this.config.server.logLevel ?? 'debug' + } + }) + } + + /** + * Initiates the database instance + * + * @method setProvider + * + * @returns {void} + * + * @private + */ + private setDatabase(): void { + this.database = new Database(this.config.database) + } + + /** + * Initiates the provider instance + * + * @method setProvider + * + * @returns {void} + * + * @private + */ + private setProvider(): void { + this.provider = new Provider(this.config.ethereum, new Wallet(this.database)) + } + + /** + * Registers the authentication handler + * + * @method setupAuth + * + * @returns {void} + * + * @private + */ + private setupAuth(): void { + const admin = new Admin(this.database); + this.whitelist = new Whitelist(this.database) + + + this.authenticator = new Authenticator( + this.whitelist, + admin, + ) + + this.server.addHook( + 'preHandler', + this.authenticator.authenticate.bind(this.authenticator) + ) + } +} diff --git a/packages/govern-tx/src/auth/Authenticator.ts b/packages/govern-tx/src/auth/Authenticator.ts new file mode 100644 index 000000000..1c0568e46 --- /dev/null +++ b/packages/govern-tx/src/auth/Authenticator.ts @@ -0,0 +1,108 @@ +import { FastifyRequest } from 'fastify'; +import { verifyMessage } from '@ethersproject/wallet'; +import { arrayify } from '@ethersproject/bytes' +import { toUtf8Bytes } from '@ethersproject/strings' +import { Unauthorized, HttpError } from 'http-errors' +import Whitelist from '../db/Whitelist' +import Admin from '../db/Admin'; +import { Params } from '../../lib/AbstractAction'; + +export interface AuthenticatedRequest extends FastifyRequest { + publicKey: string + admin: boolean +} + +export default class Authenticator { + /** + * @property {HttpError} NOT_ALLOWED + * + * @private + */ + private NOT_ALLOWED: HttpError = new Unauthorized('Not allowed action!') + + /** + * @param {Whitelist} whitelist + * @param {Admin} admin + * + * @constructor + */ + constructor(private whitelist: Whitelist, private admin: Admin) { } + + /** + * Checks if the given public key is existing and if this account is allowed to execute the requested action + * + * @method authenticate + * + * @param {FastifyRequest} request - The fastify request object + * @param {FastifyReply} reply - The fastify response object + * + * @returns Promise + * + * @public + */ + public async authenticate(request: FastifyRequest): Promise { + if (request.method == 'GET') { + request.body = '' + + for await (const data of request.raw) { + request.body += data.toString() + } + + request.body = JSON.parse(request.body as string); + (request.body as Params).message = toUtf8Bytes((request.body as Params).message) + } + + const publicKey = verifyMessage( + arrayify((request.body as Params).message), + (request.body as Params).signature + ) + + if ( + await this.hasPermission( + request, + publicKey + ) + ) { + (request as AuthenticatedRequest).publicKey = publicKey + + return + } + + throw this.NOT_ALLOWED + } + + /** + * Checks if the current requesting user has permissions + * + * @method hasPermission + * + * @param {FastifyRequest} request + * @param {string} publicKey + * + * @returns {Promise} + * + * @private + */ + private async hasPermission(request: FastifyRequest, publicKey: string): Promise { + if (request.routerPath === '/whitelist' && await this.admin.isAdmin(publicKey)) { + (request as AuthenticatedRequest).admin = true + + return true + } + + // TODO: Fire only one SQL statement to check if the key exists and if the limit is reached + if ( + request.routerPath !== '/whitelist' && + ( + (await this.whitelist.keyExists(publicKey) && !(await this.whitelist.limitReached(publicKey))) || + await this.admin.isAdmin(publicKey) + ) + ) { + (request as AuthenticatedRequest).admin = false + + return true + } + + return false + } +} diff --git a/packages/govern-tx/src/config/Configuration.ts b/packages/govern-tx/src/config/Configuration.ts new file mode 100644 index 000000000..1d6ef07c5 --- /dev/null +++ b/packages/govern-tx/src/config/Configuration.ts @@ -0,0 +1,168 @@ +/* istanbul ignore file */ +// Ignore added because VOs doesn't have to be tested especially if they do not have any logic + +export interface EthereumOptions { + url: string + blockConfirmations: number + publicKey: string + contracts: { + [name: string]: string + } +} + +export interface DatabaseOptions { + user: string, + host: string, + password: string, + database: string, + port: number +} + +export interface ServerOptions { + host: string, + port: number, + logLevel?: string +} + +export interface Config { + ethereum: EthereumOptions, + database: DatabaseOptions, + server: ServerOptions +} + +export default class Configuration { + /** + * The options to connect to a Ethereum node and how TXs should be handled + * + * @property {EthereumOptions} _ethereum + * + * @private + */ + private _ethereum: EthereumOptions + + /** + * The options to connect to the Postgres database + * + * @property {DatabaseOptions} _database + * + * @private + */ + private _database: DatabaseOptions + + /** + * The options to configure fastify server + * + * @property {ServerOptions} _server + * + * @private + */ + private _server: ServerOptions + + /** + * @param {Config} config - The wrapper object for all configuration properties + * + * @constructor + */ + constructor( + config: Config + ) { + this.ethereum = config.ethereum + this.database = config.database + this.server = config.server + } + + /** + * Getter for the database options. + * + * @property database + * + * @returns {DatabaseOptions} + * + * @public + */ + public get database(): DatabaseOptions { + return this._database; + } + + /** + * Setter for the database options. + * + * @property database + * + * @returns {void} + * + * @public + */ + public set database(value: DatabaseOptions) { + this._database = value; + } + + /** + * Getter for ethereum node options + * + * @property ethereum + * + * @returns {EthereumOptions} + * + * @public + */ + public get ethereum(): EthereumOptions { + return this._ethereum; + } + + /** + * Setter for the ethereum node options + * + * @property ethereum + * + * @returns {EthereumOptions} + * + * @public + */ + public set ethereum(value: EthereumOptions) { + this._ethereum = value; + } + + /** + * Getter for the server options + * + * @property server + * + * @returns {ServerOptions} + * + * @public + */ + public get server(): ServerOptions { + return this._server; + } + + /** + * Setter for the server options + * + * @property server + * + * @returns {void} + * + * @public + */ + public set server(value: ServerOptions) { + this._server = value; + } + + /** + * Returns the internal options object as new object + * + * @method toObject + * + * @returns {object} + * + * @public + */ + public toObject(): any { + return { + ethereum: this._ethereum, + database: this._database, + server: this._server + } + } +} diff --git a/packages/govern-tx/src/db/Admin.ts b/packages/govern-tx/src/db/Admin.ts new file mode 100644 index 000000000..5a3b00f6e --- /dev/null +++ b/packages/govern-tx/src/db/Admin.ts @@ -0,0 +1,71 @@ +import Database from './Database' + +export interface AdminItem { + PublicKey: string + PrivateKey: string +} + +export default class Admin { + /** + * @param {Database} db - The Database adapter + * + * @constructor + */ + constructor(private db: Database) {} + + /** + * Checks if a given publicKey does have admin rights + * + * @method isAdmin + * + * @param {string} publicKey + * + * @returns {boolean} + */ + public async isAdmin(publicKey: string): Promise { + return (await this.db.query(`SELECT * FROM admins WHERE PublicKey='${publicKey}'`)).length > 0 + } + + /** + * Adds a admin record + * + * @method addAdmin + * + * @param {string} publicKey + * + * @returns {AdminItem} + * + * @public + */ + public addAdmin(publicKey: string): Promise { + return this.db.query(`INSERT INTO admins VALUES ('${publicKey}')`) + } + + /** + * Deletes a admin record + * + * @method deleteAdmin + * + * @param {string} publicKey + * + * @returns {boolean} + * + * @public + */ + public async deleteAdmin(publicKey: string): Promise { + return (await this.db.query(`DELETE FROM admins WHERE PublicKey='${publicKey}'`)).length > 0 + } + + /** + * Returns all admin records + * + * @method getAdmins + * + * @returns {AdminItem[]} + * + * @public + */ + public getAdmins(): Promise { + return this.db.query('SELECT * from admins') + } +} diff --git a/packages/govern-tx/src/db/Database.ts b/packages/govern-tx/src/db/Database.ts new file mode 100644 index 000000000..b1f627a96 --- /dev/null +++ b/packages/govern-tx/src/db/Database.ts @@ -0,0 +1,56 @@ +import postgres = require('postgres'); +import { DatabaseOptions } from '../config/Configuration' + +export default class Database { + /** + * The sql function of the postgres client + * + * @property {Function} sql + * + * @private + */ + private sql: any; + + /** + * @param {DatabaseOptions} config - The database configuration + * + * @constructor + */ + constructor(private config: DatabaseOptions) { + this.connect() + } + + /** + * Establishes the connection to the postgres DB + * + * @method connect + * + * @returns {void} + * + * @private + */ + private connect(): void { + this.sql = postgres({ + host: this.config.host, + port: this.config.port, + database: this.config.database, + username: this.config.user, + password: this.config.password + }); + } + + /** + * Executes a query on the DB + * + * @method query + * + * @param {string} query - The SQL statement + * + * @returns {Promise} + * + * @public + */ + public query(query: string): Promise { + return this.sql(query); + } +} diff --git a/packages/govern-tx/src/db/Whitelist.ts b/packages/govern-tx/src/db/Whitelist.ts new file mode 100644 index 000000000..f63a08133 --- /dev/null +++ b/packages/govern-tx/src/db/Whitelist.ts @@ -0,0 +1,122 @@ +import Database from './Database' + +export interface ListItem { + PublicKey: string, + Limit: number, + Executed: number +} + +export default class Whitelist { + /** + * @param {Database} db - The Database adapter + * + * @constructor + */ + constructor(private db: Database) {} + + /** + * Returns the whitelist + * + * @method getList + * + * @returns {Promise} + */ + public getList(): Promise { + return this.db.query('SELECT * FROM whitelist') + } + + /** + * TODO: Check return values of postgres package + * + * Checks if a key is existing + * + * @method keyExists + * + * @param {string} publicKey - The public key to look for + * + * @returns {Promise { + return typeof (await this.getItemByKey(publicKey)) !== 'undefined' + } + + /** + * Returns a item from the whitelist by his public key + * + * @method getItemByKey + * + * @param {string} publicKey - The public key to look for + * + * @returns {Promise} + * + * @public + */ + public async getItemByKey(publicKey: string): Promise { + return (await this.db.query(`SELECT * FROM whitelist WHERE PublicKey='${publicKey}'`))[0] + } + + /** + * Adds a new item to the whitelist + * + * @method addItem + * + * @param {string} publicKey - The public key we would like to add + * @param {number} rateLimit - The amount of allowed transactions for this user + * + * @returns {Promise} + * + * @public + */ + public addItem(publicKey: string, rateLimit: number): Promise { + return this.db.query(`INSERT INTO whitelist (PublicKey, TxLimit) VALUES ('${publicKey}', '${rateLimit}')`) + } + + /** + * Removes a item from the whitelist + * + * @method deleteItem + * + * @param publicKey - The public key to delete + * + * @returns {Promise} + * + * @public + */ + public async deleteItem(publicKey: string): Promise { + return (await this.db.query(`DELETE FROM whitelist WHERE PublicKey='${publicKey}'`)).length > 0 + } + + /** + * Increases the execution counter + * + * @method increaseExecutionCounter + * + * @param {string} publicKey + * + * @returns {Promise} + * + * @public + */ + public async increaseExecutionCounter(publicKey: string): Promise { + return this.db.query(`UPDATE whitelist SET Executed = Executed + 1 WHERE PublicKey='${publicKey}'`) + } + + /** + * Returns true if the limit isn't reached otherwise false + * + * @method increaseExecutionCounter + * + * @param {string} publicKey + * + * @returns {Promise} + * + * @public + */ + public async limitReached(publicKey: string): Promise { + const item: ListItem = await this.getItemByKey(publicKey) + + return (item.Executed + 1) >= item.Limit + } +} diff --git a/packages/govern-tx/src/index.ts b/packages/govern-tx/src/index.ts new file mode 100644 index 000000000..f39cefd52 --- /dev/null +++ b/packages/govern-tx/src/index.ts @@ -0,0 +1,10 @@ +import { config as prodConfig } from '../config/prod.config' +import { config as devConfig } from '../config/dev.config' +import Configuration from './config/Configuration' +import Bootstrap from './Bootstrap' + +new Bootstrap( + new Configuration( + process.env.DEV ? devConfig : prodConfig + ) +).run() diff --git a/packages/govern-tx/src/provider/Provider.ts b/packages/govern-tx/src/provider/Provider.ts new file mode 100644 index 000000000..4f91e4f25 --- /dev/null +++ b/packages/govern-tx/src/provider/Provider.ts @@ -0,0 +1,74 @@ +import { JsonRpcProvider, TransactionRequest } from '@ethersproject/providers' +import { TransactionReceipt } from '@ethersproject/abstract-provider' +import Wallet from '../wallet/Wallet' +import { EthereumOptions } from '../config/Configuration'; +import ContractFunction from '../../lib/transactions/ContractFunction' + +// TODO: Check populating of TX options +export default class Provider { + /** + * The base provider of ethers.js + * + * @property {BaseProvider} provider + * + * @private + */ + private provider: JsonRpcProvider + + /** + * @param {EthereumOptions} config + * @param {Wallet} wallet + * + * @constructor + */ + constructor(private config: EthereumOptions, private wallet: Wallet) { + this.provider = new JsonRpcProvider(this.config.url) + } + + /** + * Sends the transaction and returns the receipt after the configured block confirmations are reached + * + * @method sendTransaction + * + * @param {string} contract + * @param {ContractFunction} contractFunction + * + * @returns {Promise} + */ + public async sendTransaction(contract: string, contractFunction: ContractFunction): Promise { + return ( + await this.provider.sendTransaction( + await this.wallet.sign( + await this.getTransactionOptions( + contract, + contractFunction + ), + this.config.publicKey + ) + ) + ).wait( + this.config.blockConfirmations + ) + } + + /** + * Returns the transaction options + * + * @method getTransactionOptions + * + * @param {string} contract - The name of the contract + * @param {ContractFunction} contractFunction - The ContractFunction object + * + * @returns {Promise} + */ + private async getTransactionOptions(contract: string, contractFunction: ContractFunction): Promise { + const txOptions: TransactionRequest = { + to: this.config.contracts[contract], + data: contractFunction.encode() + } + + txOptions.gasLimit = await this.provider.estimateGas(txOptions) + + return txOptions + } +} diff --git a/packages/govern-tx/src/transactions/challenge/ChallengeTransaction.ts b/packages/govern-tx/src/transactions/challenge/ChallengeTransaction.ts new file mode 100644 index 000000000..5d45d7fdf --- /dev/null +++ b/packages/govern-tx/src/transactions/challenge/ChallengeTransaction.ts @@ -0,0 +1,23 @@ +import { JsonFragment } from '@ethersproject/abi'; +import AbstractTransaction from '../../../lib/transactions/AbstractTransaction' +import * as challengeABI from './challenge.json' + +export default class ChallengeTransaction extends AbstractTransaction { + /** + * The contract name + * + * @property {string} contract + * + * @protected + */ + protected contract: string = 'GovernQueue' + + /** + * The function ABI used to create a transaction + * + * @property {JsonFragment} functionABI + * + * @protected + */ + protected functionABI: JsonFragment = challengeABI +} diff --git a/packages/govern-tx/src/transactions/challenge/challenge.json b/packages/govern-tx/src/transactions/challenge/challenge.json new file mode 100644 index 000000000..9c56b72f9 --- /dev/null +++ b/packages/govern-tx/src/transactions/challenge/challenge.json @@ -0,0 +1,141 @@ +{ + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "executionTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "submitter", + "type": "address" + }, + { + "internalType": "contract IERC3000Executor", + "name": "executor", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Action[]", + "name": "actions", + "type": "tuple[]" + }, + { + "internalType": "bytes32", + "name": "allowFailuresMap", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Payload", + "name": "payload", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "executionDelay", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ERC3000Data.Collateral", + "name": "scheduleDeposit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ERC3000Data.Collateral", + "name": "challengeDeposit", + "type": "tuple" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "rules", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Config", + "name": "config", + "type": "tuple" + } + ], + "internalType": "struct ERC3000Data.Container", + "name": "_container", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "_reason", + "type": "bytes" + } + ], + "name": "challenge", + "outputs": [ + { + "internalType": "uint256", + "name": "disputeId", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } diff --git a/packages/govern-tx/src/transactions/create/CreateTransaction.ts b/packages/govern-tx/src/transactions/create/CreateTransaction.ts new file mode 100644 index 000000000..1c0de5fd0 --- /dev/null +++ b/packages/govern-tx/src/transactions/create/CreateTransaction.ts @@ -0,0 +1,23 @@ +import { JsonFragment } from '@ethersproject/abi'; +import AbstractTransaction from '../../../lib/transactions/AbstractTransaction' +import * as createABI from './create.json' + +export default class CreateTransaction extends AbstractTransaction { + /** + * The contract name + * + * @property {string} contract + * + * @protected + */ + protected contract: string = 'GovernBaseFactory' + + /** + * The function ABI used to create a transaction + * + * @property {JsonFragment} functionABI + * + * @protected + */ + protected functionABI: JsonFragment = createABI +} diff --git a/packages/govern-tx/src/transactions/create/create.json b/packages/govern-tx/src/transactions/create/create.json new file mode 100644 index 000000000..616a70a01 --- /dev/null +++ b/packages/govern-tx/src/transactions/create/create.json @@ -0,0 +1,44 @@ +{ + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "contract IERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "string", + "name": "_tokenName", + "type": "string" + }, + { + "internalType": "string", + "name": "_tokenSymbol", + "type": "string" + }, + { + "internalType": "bool", + "name": "_useProxies", + "type": "bool" + } + ], + "name": "newGovernWithoutConfig", + "outputs": [ + { + "internalType": "contract Govern", + "name": "govern", + "type": "address" + }, + { + "internalType": "contract GovernQueue", + "name": "queue", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } diff --git a/packages/govern-tx/src/transactions/execute/ExecuteTransaction.ts b/packages/govern-tx/src/transactions/execute/ExecuteTransaction.ts new file mode 100644 index 000000000..6a5adf7b3 --- /dev/null +++ b/packages/govern-tx/src/transactions/execute/ExecuteTransaction.ts @@ -0,0 +1,23 @@ +import { JsonFragment } from '@ethersproject/abi'; +import AbstractTransaction from '../../../lib/transactions/AbstractTransaction' +import * as executeABI from './execute.json' + +export default class ExecuteTransaction extends AbstractTransaction { + /** + * The contract name + * + * @property {string} contract + * + * @protected + */ + protected contract: string = 'GovernQueue' + + /** + * The function ABI used to create a transaction + * + * @property {JsonFragment} functionABI + * + * @protected + */ + protected functionABI: JsonFragment = executeABI +} diff --git a/packages/govern-tx/src/transactions/execute/execute.json b/packages/govern-tx/src/transactions/execute/execute.json new file mode 100644 index 000000000..dde6dfc5f --- /dev/null +++ b/packages/govern-tx/src/transactions/execute/execute.json @@ -0,0 +1,141 @@ +{ + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "executionTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "submitter", + "type": "address" + }, + { + "internalType": "contract IERC3000Executor", + "name": "executor", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Action[]", + "name": "actions", + "type": "tuple[]" + }, + { + "internalType": "bytes32", + "name": "allowFailuresMap", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Payload", + "name": "payload", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "executionDelay", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ERC3000Data.Collateral", + "name": "scheduleDeposit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ERC3000Data.Collateral", + "name": "challengeDeposit", + "type": "tuple" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "rules", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Config", + "name": "config", + "type": "tuple" + } + ], + "internalType": "struct ERC3000Data.Container", + "name": "_container", + "type": "tuple" + } + ], + "name": "execute", + "outputs": [ + { + "internalType": "bytes32", + "name": "failureMap", + "type": "bytes32" + }, + { + "internalType": "bytes[]", + "name": "execResults", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } diff --git a/packages/govern-tx/src/transactions/schedule/ScheduleTransaction.ts b/packages/govern-tx/src/transactions/schedule/ScheduleTransaction.ts new file mode 100644 index 000000000..0acd6df8a --- /dev/null +++ b/packages/govern-tx/src/transactions/schedule/ScheduleTransaction.ts @@ -0,0 +1,23 @@ +import { JsonFragment } from '@ethersproject/abi'; +import AbstractTransaction from '../../../lib/transactions/AbstractTransaction' +import * as scheduleABI from './schedule.json' + +export default class ScheduleTransaction extends AbstractTransaction { + /** + * The contract name + * + * @property {string} contract + * + * @protected + */ + protected contract: string = 'GovernQueue' + + /** + * The function ABI used to create a transaction + * + * @property {JsonFragment} functionABI + * + * @protected + */ + protected functionABI: JsonFragment = scheduleABI +} diff --git a/packages/govern-tx/src/transactions/schedule/schedule.json b/packages/govern-tx/src/transactions/schedule/schedule.json new file mode 100644 index 000000000..8b717088f --- /dev/null +++ b/packages/govern-tx/src/transactions/schedule/schedule.json @@ -0,0 +1,136 @@ +{ + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "executionTime", + "type": "uint256" + }, + { + "internalType": "address", + "name": "submitter", + "type": "address" + }, + { + "internalType": "contract IERC3000Executor", + "name": "executor", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Action[]", + "name": "actions", + "type": "tuple[]" + }, + { + "internalType": "bytes32", + "name": "allowFailuresMap", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Payload", + "name": "payload", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "executionDelay", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ERC3000Data.Collateral", + "name": "scheduleDeposit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ERC3000Data.Collateral", + "name": "challengeDeposit", + "type": "tuple" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "rules", + "type": "bytes" + } + ], + "internalType": "struct ERC3000Data.Config", + "name": "config", + "type": "tuple" + } + ], + "internalType": "struct ERC3000Data.Container", + "name": "_container", + "type": "tuple" + } + ], + "name": "schedule", + "outputs": [ + { + "internalType": "bytes32", + "name": "containerHash", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } diff --git a/packages/govern-tx/src/wallet/Wallet.ts b/packages/govern-tx/src/wallet/Wallet.ts new file mode 100644 index 000000000..7a0ccf2de --- /dev/null +++ b/packages/govern-tx/src/wallet/Wallet.ts @@ -0,0 +1,74 @@ +import {Wallet as EthersWallet} from '@ethersproject/wallet' +import { TransactionRequest } from '@ethersproject/providers' +import Database from '../db/Database' + +export interface WalletItem { + PrivateKey: string + PublicKey: string +} + +export default class Wallet { + /** + * The used wallet object to sign a transaction + * + * @property {EthersWallet} wallet + * + * @private + */ + private wallet: EthersWallet + + /** + * The public key of the loaded wallet account + * + * @property {string} publicKey + * + * @private + */ + private publicKey: string; + + /** + * @param {Database} db - Database adapter + * + * @constructor + */ + constructor(private db: Database) { } + + /** + * Signs the given contract function with the Aragon privateKey + * + * @method sign + * + * @param {TransactionRequest} txOptions + * @param {string} publicKey + * + * @returns {Promise} + * + * @public + */ + public async sign(txOptions: TransactionRequest, publicKey: string): Promise { + await this.loadWallet(publicKey) + + txOptions.from = publicKey + + return this.wallet.signTransaction(txOptions) + } + + /** + * Returns the ethers wallet instance prepopulated with the Aragon privateKey + * + * @method loadWallet + * + * @param {string} publicKey + * + * @returns {EthersWallet} + * + * @private + */ + private async loadWallet(publicKey: string): Promise { + if (!this.wallet || this.publicKey !== publicKey) { + const pk = (await this.db.query(`SELECT PrivateKey FROM wallet WHERE PublicKey='${publicKey}'`))[0]; + this.wallet = new EthersWallet(pk) + this.publicKey = publicKey; + } + } +} diff --git a/packages/govern-tx/src/whitelist/AddItemAction.ts b/packages/govern-tx/src/whitelist/AddItemAction.ts new file mode 100644 index 000000000..a8e4a4319 --- /dev/null +++ b/packages/govern-tx/src/whitelist/AddItemAction.ts @@ -0,0 +1,45 @@ +import {isAddress} from '@ethersproject/address' +import { FastifyRequest } from 'fastify' +import AbstractWhitelistAction, { WhitelistParams } from "../../lib/whitelist/AbstractWhitelistAction" +import {ListItem} from '../db/Whitelist' + +export default class AddItemAction extends AbstractWhitelistAction { + /** + * Validates the given request body. + * + * @method validateRequest + * + * @param {FastifyRequest} request + * + * @returns {FastifyRequest} + * + * @protected + */ + protected validateRequest(request: FastifyRequest): FastifyRequest { + if (!isAddress((request.body as WhitelistParams).message.publicKey)) { + throw new Error('Invalid public key passed!') + } + + if ((request.body as WhitelistParams).message.txLimit == 0) { + throw new Error('Invalid rate limit passed!') + } + + return request + } + + /** + * Adds a new item to the whitelist + * + * @method execute + * + * @returns {Promise} + * + * @public + */ + public execute(): Promise { + return this.whitelist.addItem( + (this.request.body as WhitelistParams).message.publicKey, + ((this.request.body as WhitelistParams).message.txLimit as number) + ) + } +} diff --git a/packages/govern-tx/src/whitelist/DeleteItemAction.ts b/packages/govern-tx/src/whitelist/DeleteItemAction.ts new file mode 100644 index 000000000..eb3737140 --- /dev/null +++ b/packages/govern-tx/src/whitelist/DeleteItemAction.ts @@ -0,0 +1,37 @@ +import {isAddress} from '@ethersproject/address' +import { FastifyRequest } from 'fastify'; +import AbstractWhitelistAction, { WhitelistParams } from "../../lib/whitelist/AbstractWhitelistAction"; + +export default class DeleteItemAction extends AbstractWhitelistAction { + /** + * Validates the given request body. + * + * @method validateRequest + * + * @param {FastifyRequest} request + * + * @returns {FastifyRequest} + * + * @protected + */ + protected validateRequest(request: FastifyRequest): FastifyRequest { + if (!isAddress((request.body as WhitelistParams).message.publicKey)) { + throw new Error('Invalid public key passed!') + } + + return request; + } + + /** + * Adds a new item to the whitelist + * + * @method execute + * + * @returns {Promise} + * + * @public + */ + public execute(): Promise { + return this.whitelist.deleteItem((this.request.body as WhitelistParams).message.publicKey) + } +} diff --git a/packages/govern-tx/src/whitelist/GetListAction.ts b/packages/govern-tx/src/whitelist/GetListAction.ts new file mode 100644 index 000000000..d644071bd --- /dev/null +++ b/packages/govern-tx/src/whitelist/GetListAction.ts @@ -0,0 +1,17 @@ +import AbstractWhitelistAction from '../../lib/whitelist/AbstractWhitelistAction'; +import {ListItem} from '../db/Whitelist' + +export default class GetListAction extends AbstractWhitelistAction { + /** + * Adds a new item to the whitelist + * + * @method execute + * + * @returns {Promise} + * + * @public + */ + public execute(): Promise { + return this.whitelist.getList() + } +} diff --git a/packages/govern-tx/test/lib/AbstractActionTest.ts b/packages/govern-tx/test/lib/AbstractActionTest.ts new file mode 100644 index 000000000..aa04118d7 --- /dev/null +++ b/packages/govern-tx/test/lib/AbstractActionTest.ts @@ -0,0 +1,46 @@ +import AbstractAction from '../../lib/AbstractAction'; + +interface MockInterface { + message: string | any +} + + +class MockAction extends AbstractAction { + public execute(): Promise { + return Promise.resolve(true) + } +} + +/** + * AbstractAction test + */ +describe('AbstractAction Test', () => { + let action: MockAction + + beforeEach(() => { + action = new MockAction({message: true} as any) + }) + + it('has the correct schema defined', () => { + expect(AbstractAction.schema).toEqual({ + body: { + type: 'object', + required: ['message', 'signature'], + properties: { + message: { + oneOf: [ + { type: 'string'}, + { type: 'object'} + ] + }, + signature: { type: 'string' } + } + } + }) + }) + + it('has set the request property', () => { + //@ts-ignore + expect(action.request).toEqual({message: true}) + }) +}) diff --git a/packages/govern-tx/test/lib/transactions/AbstractTransactionTest.ts b/packages/govern-tx/test/lib/transactions/AbstractTransactionTest.ts new file mode 100644 index 000000000..5dc66d86b --- /dev/null +++ b/packages/govern-tx/test/lib/transactions/AbstractTransactionTest.ts @@ -0,0 +1,89 @@ +// import { Request } from '../../../lib//AbstractAction'; +import { FastifySchema, FastifyRequest } from 'fastify' + +import Provider from '../../../src/provider/Provider'; +import Wallet from '../../../src/wallet/Wallet'; +import { JsonFragment } from '@ethersproject/abi'; +import { TransactionReceipt } from '@ethersproject/abstract-provider'; +import { EthereumOptions } from '../../../src/config/Configuration'; +import AbstractAction from '../../../lib/AbstractAction'; +import ContractFunction from '../../../lib/transactions/ContractFunction'; +import AbstractTransaction from '../../../lib/transactions/AbstractTransaction'; +import Whitelist from '../../../src/db/Whitelist'; +import Database from '../../../src/db/Database'; + +// Mocks +class MockTransaction extends AbstractTransaction { + protected functionABI = {} + protected contract = 'CONTRACT_NAME' +} + +jest.mock('../../../src/provider/Provider') +jest.mock('../../../lib/transactions/ContractFunction') +jest.mock('../../../src/db/Whitelist') + +/** + * AbstractTransaction test + */ +describe('AbstractTransactionTest', () => { + let txAction: MockTransaction, + providerMock: Provider, + contractFunctionMock: ContractFunction, + whiteListMock: Whitelist + + beforeEach(() => { + new Provider({} as EthereumOptions, {} as Wallet) + providerMock = (Provider as jest.MockedClass).mock.instances[0] + + new Whitelist({} as Database); + whiteListMock = (Whitelist as jest.MockedClass).mock.instances[0]; + + ContractFunction.prototype.functionArguments = [{payload: {submitter: ''}}] + + txAction = new MockTransaction( + { + publicKey: '0x00' + } as EthereumOptions, + providerMock, + whiteListMock, + { + params: { + message: 'MESSAGE' + } + } as unknown as FastifyRequest + ) + + }) + + it('calls execute and returns the expected value', async () => { + (providerMock.sendTransaction as jest.MockedFunction).mockReturnValueOnce(Promise.resolve({} as TransactionReceipt)) + + await expect(txAction.execute()).resolves.toEqual({}) + + contractFunctionMock = (ContractFunction as jest.MockedClass).mock.instances[0] + + expect(ContractFunction).toHaveBeenNthCalledWith(1, {} as JsonFragment, 'MESSAGE') + + expect(providerMock.sendTransaction).toHaveBeenNthCalledWith(1, 'CONTRACT_NAME', contractFunctionMock) + + expect(contractFunctionMock.functionArguments[0].payload.submitter).toEqual('0x00') + }) + + it('calls execute and throws as expected', async () => { + (providerMock.sendTransaction as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(txAction.execute()).rejects.toEqual('NOPE') + + contractFunctionMock = (ContractFunction as jest.MockedClass).mock.instances[0] + + expect(ContractFunction).toHaveBeenNthCalledWith(1, {} as JsonFragment, 'MESSAGE') + + expect(providerMock.sendTransaction).toHaveBeenNthCalledWith(1, 'CONTRACT_NAME', contractFunctionMock) + + expect(contractFunctionMock.functionArguments[0].payload.submitter).toEqual('0x00') + }) + + it('has the correct schema defined', () => { + expect(AbstractTransaction.schema).toEqual(Object.assign(AbstractAction.schema, AbstractTransaction.schema)) + }) +}) diff --git a/packages/govern-tx/test/lib/transactions/ContractFunctionTest.ts b/packages/govern-tx/test/lib/transactions/ContractFunctionTest.ts new file mode 100644 index 000000000..e23efb04a --- /dev/null +++ b/packages/govern-tx/test/lib/transactions/ContractFunctionTest.ts @@ -0,0 +1,67 @@ +import { defaultAbiCoder, Fragment, JsonFragment, FunctionFragment } from '@ethersproject/abi'; +import { id } from '@ethersproject/hash' +import ContractFunction from '../../../lib/transactions/ContractFunction'; + +// Mocks +jest.mock('@ethersproject/abi') +jest.mock('@ethersproject/hash') + +/** + * ContractFunction test + */ +describe('ContractFunctionTest', () => { + let contractFunction: ContractFunction, + fragmentMock = {inputs: 'INPUTS', format: jest.fn()} + + beforeEach(() => { + (defaultAbiCoder.decode as jest.MockedFunction).mockReturnValueOnce(['ARGUMENT']); + + (Fragment.fromObject as jest.MockedFunction).mockReturnValueOnce(fragmentMock as any); + // calldata(4 byte + arguments) + contractFunction = new ContractFunction({} as JsonFragment, '0x9f7b4579MESSAGE') + + expect(defaultAbiCoder.decode).toHaveBeenNthCalledWith(1, 'INPUTS', '0xMESSAGE') + + expect(contractFunction.functionArguments).toEqual(['ARGUMENT']) + }) + + it('calls encode and returns the expected value', () => { + (id as jest.MockedFunction).mockReturnValueOnce('0x0'); + + (fragmentMock.format as jest.MockedFunction).mockReturnValueOnce('SIGNATURE'); + + (defaultAbiCoder.encode as jest.MockedFunction).mockReturnValueOnce('0xENCODED'); + + expect(contractFunction.encode()).toEqual('0x0ENCODED') + + expect(defaultAbiCoder.encode).toHaveBeenNthCalledWith(1, 'INPUTS', ['ARGUMENT']) + + expect(id).toHaveBeenNthCalledWith(1, 'SIGNATURE') + }) + + it('calls encode and throws as expected', () => { + //@ts-ignore + (defaultAbiCoder.encode as jest.MockedFunction).mockImplementation(() => {throw 'NOPE'}) + + expect(() => contractFunction.encode()).toThrow('NOPE') + + expect(defaultAbiCoder.encode).toHaveBeenNthCalledWith(1, 'INPUTS', ['ARGUMENT']) + }) + + it('calls decode and returns the expected value', () => { + (defaultAbiCoder.decode as jest.MockedFunction).mockReturnValueOnce(['DECODED']); + + expect(contractFunction.decode()).toEqual(['DECODED']) + + expect(defaultAbiCoder.decode).toHaveBeenNthCalledWith(1, 'INPUTS', '0xMESSAGE') + }) + + it('calls decode and throws as expected', () => { + //@ts-ignore + (defaultAbiCoder.decode as jest.MockedFunction).mockImplementation(() => {throw 'NOPE'}) + + expect(() => contractFunction.decode()).toThrow('NOPE') + + expect(defaultAbiCoder.decode).toHaveBeenNthCalledWith(1, 'INPUTS', '0xMESSAGE') + }) +}) diff --git a/packages/govern-tx/test/lib/whitelist/AbstractWhitelistActionTest.ts b/packages/govern-tx/test/lib/whitelist/AbstractWhitelistActionTest.ts new file mode 100644 index 000000000..78dd9d2d6 --- /dev/null +++ b/packages/govern-tx/test/lib/whitelist/AbstractWhitelistActionTest.ts @@ -0,0 +1,53 @@ +import Whitelist from '../../../src/db/Whitelist'; +import AbstractWhitelistAction from '../../../lib/whitelist/AbstractWhitelistAction'; +import WhiteListParams from '../../../lib/whitelist/AbstractWhitelistAction' + +interface MockInterface { + message: string | any +} + + +// Mocks +class MockAction extends AbstractWhitelistAction{ + public execute(): Promise { + return Promise.resolve(true) + } +} + +/** + * AbstractWhitelistAction test + */ +describe('AbstractWhitelistAction Test', () => { + let action: MockAction + + beforeEach(() => { + action = new MockAction( + {} as Whitelist, + { + message: { + publicKey: '0x00', + txLimit: 0 + }, + signature: '' + } as any, + ) + }) + + it('has the correct schema defined', () => { + expect(AbstractWhitelistAction.schema).toEqual({ + body: { + type: 'object', + required: ['message', 'signature'], + properties: { + message: { + oneOf: [ + { type: 'string'}, + { type: 'object'} + ] + }, + signature: { type: 'string' } + } + } + }) + }) +}) diff --git a/packages/govern-tx/test/src/.gitkeep b/packages/govern-tx/test/src/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/govern-tx/test/src/BootstrapTest.ts b/packages/govern-tx/test/src/BootstrapTest.ts new file mode 100644 index 000000000..fd4504ac4 --- /dev/null +++ b/packages/govern-tx/test/src/BootstrapTest.ts @@ -0,0 +1,272 @@ +//@ts-nocheck +import AbstractWhitelistAction from '../../lib/whitelist/AbstractWhitelistAction' +import AbstractTransaction from '../../lib/transactions/AbstractTransaction' + +import Configuration from '../../src/config/Configuration' +import Provider from '../../src/provider/Provider' +import Wallet from '../../src/wallet/Wallet' + +import Database from '../../src/db/Database' +import Whitelist from '../../src/db/Whitelist' +import Admin from '../../src/db/Admin' + +import Authenticator from '../../src/auth/Authenticator' + +import ExecuteTransaction from '../../src/transactions/execute/ExecuteTransaction'; +import ChallengeTransaction from '../../src/transactions/challenge/ChallengeTransaction' +import ScheduleTransaction from '../../src/transactions/schedule/ScheduleTransaction' + +import AddItemAction from '../../src/whitelist/AddItemAction'; +import DeleteItemAction from '../../src/whitelist/DeleteItemAction'; +import GetListAction from '../../src/whitelist/GetListAction'; + +import Bootstrap from '../../src/Bootstrap' + +//Mocks +const fastifyMock: any = { + post: jest.fn(), + delete: jest.fn(), + get: jest.fn(), + addHook: jest.fn(), + options: {}, + FastifyRequest: jest.fn() +} +jest.mock('fastify', () => { + return { + default: (options) => { + fastifyMock.options = options + + return fastifyMock + } + } +}) + +jest.mock('../../src/provider/Provider') +jest.mock('../../src/wallet/Wallet') + +jest.mock('../../src/db/Database') +jest.mock('../../src/db/Whitelist') +jest.mock('../../src/db/Admin') + +jest.mock('../../src/auth/Authenticator') + +jest.mock('../../src/transactions/execute/ExecuteTransaction') +jest.mock('../../src/transactions/challenge/ChallengeTransaction') +jest.mock('../../src/transactions/schedule/ScheduleTransaction') + +jest.mock('../../src/whitelist/AddItemAction') +jest.mock('../../src/whitelist/DeleteItemAction') +jest.mock('../../src/whitelist/GetListAction') + +/** + * Bootstrap test + */ +describe('BootstrapTest', () => { + let bootstrap: Bootstrap, + executeTransactionMock: ExecuteTransaction, + scheduleTransactionMock: ScheduleTransaction, + challengeTransactionMock: ChallengeTransaction, + addItemActionMock: AddItemAction, + deleteItemActionMock: DeleteItemAction, + getListActionMock: GetListAction + + const config = { + ethereum: { + publicKey: '0x00', + contracts: { + GovernQueue: '0x00' + }, + url: 'localhost:8545', + blockConfirmations: 42 + }, + database: { + user: 'govern', + host: 'localhost', + password: 'dev', + database: 'govern', + port: 4000 + }, + server: { + host: '0.0.0.0', + port: 4040 + } + } + + it('calls the constructor and initiates the class as expected', () => { + fastifyMock.post = jest.fn((path, schemaObj, callback) => { + switch(path) { + case '/execute': + expect(schemaObj).toEqual({schema: AbstractTransaction.schema}) + callback(fastifyMock.FastifyRequest) + executeTransactionMock = ExecuteTransaction.mock.instances[0] + break + case '/schedule': + expect(schemaObj).toEqual({schema: AbstractTransaction.schema}) + callback(fastifyMock.FastifyRequest) + scheduleTransactionMock = ScheduleTransaction.mock.instances[0] + break + case '/challenge': + expect(schemaObj).toEqual({schema: AbstractTransaction.schema}) + callback(fastifyMock.FastifyRequest) + challengeTransactionMock = ChallengeTransaction.mock.instances[0] + break + case '/whitelist': + expect(schemaObj).toEqual({schema: AbstractWhitelistAction.schema}) + callback(fastifyMock.FastifyRequest) + addItemActionMock = AddItemAction.mock.instances[0] + break + } + }) + + fastifyMock.delete = jest.fn((path, schemaObj, callback) => { + expect(path).toEqual('/whitelist') + + expect(schemaObj).toEqual({schema: AbstractWhitelistAction.schema}) + + callback(fastifyMock.FastifyRequest) + + deleteItemActionMock = DeleteItemAction.mock.instances[0] + }) + + fastifyMock.get = jest.fn((path, schemaObj, callback) => { + expect(path).toEqual('/whitelist') + + expect(schemaObj).toEqual({schema: AbstractWhitelistAction.schema}) + + callback(fastifyMock.FastifyRequest) + + getListActionMock = GetListAction.mock.instances[0] + }) + + bootstrap = new Bootstrap( + new Configuration(config) + ) + + /******************************************** + * Expectations for all invoked constructors + ********************************************/ + expect( + fastifyMock.options + ).toEqual( + { + logger: { + level: 'debug' + } + } + ) + + expect(bootstrap.server).toEqual(fastifyMock) + + expect(bootstrap.database).toBeInstanceOf(Database) + + expect(Database).toHaveBeenNthCalledWith(1, config.database) + + expect(bootstrap.provider).toBeInstanceOf(Provider) + + expect(Provider).toHaveBeenNthCalledWith(1, config.ethereum, Wallet.mock.instances[0]) + + expect(Wallet).toHaveBeenNthCalledWith(1, Database.mock.instances[0]) + + expect(bootstrap.whitelist).toBeInstanceOf(Whitelist) + + expect(Admin).toHaveBeenNthCalledWith(1, bootstrap.database) + + expect(bootstrap.authenticator).toBeInstanceOf(Authenticator) + + expect(Authenticator).toHaveBeenNthCalledWith(1, bootstrap.whitelist, Admin.mock.instances[0]) + + /*********************************** + * Expectations for added Auth Hook + ***********************************/ + expect(bootstrap.server.addHook).toHaveBeenNthCalledWith( + 1, + 'preHandler', + expect.any(Function) + ) + + /************************************ + * Expectations for all added routes + ************************************/ + expect(ExecuteTransaction).toHaveBeenNthCalledWith(1, config.ethereum, Provider.mock.instances[0], Whitelist.mock.instances[0], fastifyMock.FastifyRequest) + expect(executeTransactionMock.execute).toHaveBeenCalledTimes(1) + + expect(ScheduleTransaction).toHaveBeenNthCalledWith(1, config.ethereum, Provider.mock.instances[0], Whitelist.mock.instances[0], fastifyMock.FastifyRequest) + expect(scheduleTransactionMock.execute).toHaveBeenCalledTimes(1) + + expect(ChallengeTransaction).toHaveBeenNthCalledWith(1, config.ethereum, Provider.mock.instances[0], Whitelist.mock.instances[0], fastifyMock.FastifyRequest) + expect(challengeTransactionMock.execute).toHaveBeenCalledTimes(1) + + expect(AddItemAction).toHaveBeenNthCalledWith(1, Whitelist.mock.instances[0], fastifyMock.FastifyRequest) + expect(addItemActionMock.execute).toHaveBeenCalledTimes(1) + + expect(DeleteItemAction).toHaveBeenNthCalledWith(1, Whitelist.mock.instances[0], fastifyMock.FastifyRequest) + expect(deleteItemActionMock.execute).toHaveBeenCalledTimes(1) + + expect(fastifyMock.post).toHaveBeenCalledTimes(5) + expect(fastifyMock.delete).toHaveBeenCalledTimes(1) + expect(fastifyMock.get).toHaveBeenCalledTimes(1) + }) + + it('calls the constructor and uses the configured logging level for fastify', () => { + config.server.logLevel = 'warn' + + bootstrap = new Bootstrap( + new Configuration(config) + ) + + expect( + fastifyMock.options + ).toEqual( + { + logger: { + level: 'warn' + } + } + ) + }) + + it('calls run and starts the server as expected on the configured port', (done) => { + console.log = jest.fn(); + + fastifyMock.listen = jest.fn((port, host, callback) => { + expect(port).toEqual(config.server.port) + expect(host).toEqual(config.server.host) + + callback(false, 'myAddress') + + expect(console.log).toHaveBeenNthCalledWith(1, `Server is listening at myAddress`) + + done() + }) + + bootstrap = new Bootstrap( + new Configuration(config) + ) + bootstrap.run() + }) + + it('calls run and does log the error as expected', (done) => { + const realProcess = process + console.error = jest.fn(); + process = {...realProcess, exit: jest.fn()} + + bootstrap.server.listen = jest.fn((port, host, callback) => { + expect(port).toEqual(config.server.port) + expect(host).toEqual(config.server.host) + + callback(true, null) + + expect(console.error).toHaveBeenNthCalledWith(1, true) + + expect(process.exit).toHaveBeenNthCalledWith(1, 0) + + done() + process = realProcess + }) + + bootstrap = new Bootstrap( + new Configuration(config) + ) + bootstrap.run() + }) +}) diff --git a/packages/govern-tx/test/src/auth/AuthenticatorTest.ts b/packages/govern-tx/test/src/auth/AuthenticatorTest.ts new file mode 100644 index 000000000..0ad997cc4 --- /dev/null +++ b/packages/govern-tx/test/src/auth/AuthenticatorTest.ts @@ -0,0 +1,103 @@ +import { verifyMessage } from '@ethersproject/wallet'; +import { arrayify } from '@ethersproject/bytes' +import { Unauthorized } from 'http-errors'; +import { FastifyRequest } from 'fastify'; +import Authenticator from '../../../src/auth/Authenticator'; +import Admin from '../../../src/db/Admin'; +import Whitelist from '../../../src/db/Whitelist'; +import Database from '../../../src/db/Database'; + +// Mocks +jest.mock('../../../src/db/Admin') +jest.mock('../../../src/db/Whitelist') +jest.mock('@ethersproject/wallet') +jest.mock('@ethersproject/bytes') + +/** + * Authenticator test + */ +describe('AuthenticatorTest', () => { + let authenticator: Authenticator, + whitelistMock: Whitelist, + adminMock: Admin, + request = { + routerPath: '/execute', + body: { + signature: '0x00', + message: '0x00' + } + }; + + const NO_ALLOWED = new Unauthorized('Not allowed action!'); + + beforeEach(() => { + (arrayify as jest.MockedFunction).mockReturnValue(new Uint8Array(0x00)); + (verifyMessage as jest.MockedFunction).mockReturnValue('0x00') + + new Whitelist({} as Database) + whitelistMock = (Whitelist as jest.MockedClass).mock.instances[0] + + new Admin({} as Database) + adminMock = (Admin as jest.MockedClass).mock.instances[0] + + authenticator = new Authenticator(whitelistMock, adminMock) + }) + + it('calls authenticate as normal user and grants access to the transaction actions', async () => { + (whitelistMock.keyExists as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)) + + await authenticator.authenticate(request as FastifyRequest) + + expect(verifyMessage).toHaveBeenNthCalledWith(1, new Uint8Array(0x00), request.body.signature) + + expect(arrayify).toHaveBeenNthCalledWith(1, request.body.message) + + expect(whitelistMock.keyExists).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls authenticate as normal user and restricts access to the whitelist', async () => { + (adminMock.isAdmin as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(false)) + + request.routerPath = '/whitelist' + + await expect(authenticator.authenticate(request as FastifyRequest)).rejects.toThrowError(NO_ALLOWED) + + expect(verifyMessage).toHaveBeenNthCalledWith(1, new Uint8Array(0x00), request.body.signature) + + expect(arrayify).toHaveBeenNthCalledWith(1, request.body.message) + + expect(adminMock.isAdmin).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls authencticate as admin user and grants access to the transaction actions', async () => { + (adminMock.isAdmin as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)); + + (whitelistMock.keyExists as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(false)) + + request.routerPath = '/execute' + + await authenticator.authenticate(request as FastifyRequest) + + expect(verifyMessage).toHaveBeenNthCalledWith(1, new Uint8Array(0x00), request.body.signature) + + expect(arrayify).toHaveBeenNthCalledWith(1, request.body.message) + + expect(adminMock.isAdmin).toHaveBeenNthCalledWith(1, '0x00') + + expect(whitelistMock.keyExists).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls authenticate as admin user and grants access to the whitelist actions', async () => { + (adminMock.isAdmin as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)) + + request.routerPath = '/whitelist' + + await authenticator.authenticate(request as FastifyRequest) + + expect(verifyMessage).toHaveBeenNthCalledWith(1, new Uint8Array(0x00), request.body.signature) + + expect(arrayify).toHaveBeenNthCalledWith(1, request.body.message) + + expect(adminMock.isAdmin).toHaveBeenNthCalledWith(1, '0x00') + }) +}) diff --git a/packages/govern-tx/test/src/db/AdminTest.ts b/packages/govern-tx/test/src/db/AdminTest.ts new file mode 100644 index 000000000..e30c24259 --- /dev/null +++ b/packages/govern-tx/test/src/db/AdminTest.ts @@ -0,0 +1,106 @@ +import Database from '../../../src/db/Database'; +import Admin from '../../../src/db/Admin'; + +// Mocks +jest.mock('../../../src/db/Database') + +/** + * Admin test + */ +describe('AdminTest', () => { + let admin: Admin, + databaseMock: Database; + + beforeEach(() => { + new Database({ + host: 'host', + port: 100, + database: 'database', + user: 'user', + password: 'password' + }) + databaseMock = (Database as jest.MockedClass).mock.instances[0] + + admin = new Admin(databaseMock); + }) + + it('calls isAdmin and returns true', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([0])) + + await expect(admin.isAdmin('0x00')).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM admins WHERE PublicKey='0x00'`) + }) + + it('calls isAdmin and returns false', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([])) + + await expect(admin.isAdmin('0x00')).resolves.toEqual(false) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM admins WHERE PublicKey='0x00'`) + }) + + it('calls isAdmin and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(admin.isAdmin('0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM admins WHERE PublicKey='0x00'`) + }) + + it('calls addAdmin and returns the expected value', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)) + + await expect(admin.addAdmin('0x00')).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `INSERT INTO admins VALUES ('0x00')`) + }) + + it('calls addAdmin and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(admin.addAdmin('0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `INSERT INTO admins VALUES ('0x00')`) + }) + + it('calls deleteAdmin and returns true', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([0])) + + await expect(admin.deleteAdmin('0x00')).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `DELETE FROM admins WHERE PublicKey='0x00'`) + }) + + it('calls deleteAdmin and returns false', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([])) + + await expect(admin.deleteAdmin('0x00')).resolves.toEqual(false) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `DELETE FROM admins WHERE PublicKey='0x00'`) + }) + + it('calls deleteAdmin and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(admin.deleteAdmin('0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `DELETE FROM admins WHERE PublicKey='0x00'`) + }) + + it('calls getAdmins and returns the expected value', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)) + + await expect(admin.getAdmins()).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, 'SELECT * from admins') + }) + + it('calls getAdmins and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(admin.getAdmins()).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, 'SELECT * from admins') + }) +}) diff --git a/packages/govern-tx/test/src/db/DatabaseTest.ts b/packages/govern-tx/test/src/db/DatabaseTest.ts new file mode 100644 index 000000000..1e3f4019f --- /dev/null +++ b/packages/govern-tx/test/src/db/DatabaseTest.ts @@ -0,0 +1,39 @@ +import Database from '../../../src/db/Database'; + +const config = { + host: 'host', + port: 100, + database: 'database', + user: 'user', + password: 'password' +} + +// Mocks +jest.mock('postgres', () => { + return (options: any) => { + expect(options).toEqual({ + host: 'host', + port: 100, + database: 'database', + username: 'user', + password: 'password' + }) + + return jest.fn(() => Promise.resolve('EXECUTED')) + } +}) + +/** + * Database test + */ +describe('DatabaseTest', () => { + let database: Database; + + beforeEach(() => { + database = new Database(config) + }) + + it('calls query and calls the postgres sql function as expected', async () => { + expect(await database.query('ASDF')).toEqual('EXECUTED') + }) +}) diff --git a/packages/govern-tx/test/src/db/WhitelistTest.ts b/packages/govern-tx/test/src/db/WhitelistTest.ts new file mode 100644 index 000000000..a172ac70a --- /dev/null +++ b/packages/govern-tx/test/src/db/WhitelistTest.ts @@ -0,0 +1,122 @@ +import Database from '../../../src/db/Database'; +import Whitelist from '../../../src/db/Whitelist'; + +// Mocks +jest.mock('../../../src/db/Database') + +/** + * Whitelist test + */ +describe('WhitelistTest', () => { + let whitelist: Whitelist, + databaseMock: Database; + + beforeEach(() => { + new Database({ + host: 'host', + port: 100, + database: 'database', + user: 'user', + password: 'password' + }) + databaseMock = (Database as jest.MockedClass).mock.instances[0] + + whitelist = new Whitelist(databaseMock); + }) + + it('calls getList and returns the expected value', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)) + + await expect(whitelist.getList()).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, 'SELECT * FROM whitelist') + }) + + it('calls getList and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(whitelist.getList()).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, 'SELECT * FROM whitelist') + }) + + it('calls keyExists and returns true', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([0])) + + await expect(whitelist.keyExists('0x00')).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls keyExists and returns false', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([])) + + await expect(whitelist.keyExists('0x00')).resolves.toEqual(false) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls keyExists and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(whitelist.keyExists('0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls getItemByKey and returns the expected value', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([true])) + + await expect(whitelist.getItemByKey('0x00')).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls getItemByKey and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(whitelist.getItemByKey('0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT * FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls addItem and returns the expected value', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)) + + await expect(whitelist.addItem('0x00', 0)).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `INSERT INTO whitelist (PublicKey, TxLimit) VALUES ('0x00', '0')`) + }) + + it('calls addItem and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(whitelist.addItem('0x00', 0)).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `INSERT INTO whitelist (PublicKey, TxLimit) VALUES ('0x00', '0')`) + }) + + it('calls deleteItem and returns true', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([0])) + + await expect(whitelist.deleteItem('0x00')).resolves.toEqual(true) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `DELETE FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls deleteItem and returns false', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([])) + + await expect(whitelist.deleteItem('0x00')).resolves.toEqual(false) + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `DELETE FROM whitelist WHERE PublicKey='0x00'`) + }) + + it('calls deleteItem and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')) + + await expect(whitelist.deleteItem('0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `DELETE FROM whitelist WHERE PublicKey='0x00'`) + }) +}) diff --git a/packages/govern-tx/test/src/provider/ProviderTest.ts b/packages/govern-tx/test/src/provider/ProviderTest.ts new file mode 100644 index 000000000..c25a8bf16 --- /dev/null +++ b/packages/govern-tx/test/src/provider/ProviderTest.ts @@ -0,0 +1,186 @@ +import { JsonRpcProvider, TransactionResponse } from '@ethersproject/providers'; +import { BigNumber } from "@ethersproject/bignumber"; +import { JsonFragment } from '@ethersproject/abi'; +import ContractFunction from '../../../lib/transactions/ContractFunction'; +import Wallet from '../../../src/wallet/Wallet' +import Database from '../../../src/db/Database' +import Provider from '../../../src/provider/Provider' + +// Mocks +jest.mock('@ethersproject/providers') +jest.mock('../../../src/wallet/Wallet') +jest.mock('../../../lib/transactions/ContractFunction') + +// Mock TX response class +class TXResponse implements TransactionResponse { + public hash = '0x00' + public confirmations = 1 + public from = '0x00' + public nonce = 0 + public gasLimit = BigNumber.from(0) + public gasPrice = BigNumber.from(0) + public data = '0x00' + public value = BigNumber.from(0) + public chainId = 0 + wait = jest.fn() +} + +/** + * Provider test + */ +describe('ProviderTest', () => { + let provider: Provider, + walletMock: Wallet, + jsonRpcProviderMock: JsonRpcProvider, + contractFunctionMock: ContractFunction; + + beforeEach(() => { + new Wallet({} as Database) + walletMock = (Wallet as jest.MockedClass).mock.instances[0] + + new ContractFunction({} as JsonFragment, 'request') + contractFunctionMock = (ContractFunction as jest.MockedClass).mock.instances[0] + + provider = new Provider( + { + url: 'url', + blockConfirmations: 0, + publicKey: 'publicKey', + contracts: {GovernQueue: '0x00'} + }, + walletMock + ) + + jsonRpcProviderMock = (JsonRpcProvider as jest.MockedClass).mock.instances[0] + }) + + it('calls sendTransaction and returns with the expected value', async () => { + const txResponse = new TXResponse() + + txResponse.wait.mockReturnValueOnce(Promise.resolve('RECEIPT')); + + (walletMock.sign as jest.MockedFunction).mockReturnValueOnce(Promise.resolve('0x00')); + + (contractFunctionMock.encode as jest.MockedFunction).mockReturnValue('0x00'); + + (jsonRpcProviderMock.sendTransaction as jest.MockedFunction).mockReturnValue(Promise.resolve(txResponse)); + + (jsonRpcProviderMock.estimateGas as jest.MockedFunction).mockReturnValue(Promise.resolve(BigNumber.from(0))); + + await expect(provider.sendTransaction('GovernQueue', contractFunctionMock)).resolves.toEqual('RECEIPT'); + + expect(jsonRpcProviderMock.sendTransaction).toHaveBeenNthCalledWith(1, '0x00'); + + expect(txResponse.wait).toHaveBeenNthCalledWith(1, 0); + + expect(walletMock.sign).toHaveBeenNthCalledWith( + 1, + { + to: '0x00', + data: '0x00', + gasLimit: BigNumber.from(0) + }, + 'publicKey' + ); + + expect(contractFunctionMock.encode).toHaveBeenCalledTimes(1); + }) + + it('calls sendTransaction and the wallet throws on signing', async () => { + (walletMock.sign as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')); + + (contractFunctionMock.encode as jest.MockedFunction).mockReturnValue('0x00'); + + (jsonRpcProviderMock.estimateGas as jest.MockedFunction).mockReturnValue(Promise.resolve(BigNumber.from(0))); + + await expect(provider.sendTransaction('GovernQueue', contractFunctionMock)).rejects.toEqual('NOPE'); + + expect(walletMock.sign).toHaveBeenNthCalledWith( + 1, + { + to: '0x00', + data: '0x00', + gasLimit: BigNumber.from(0) + }, + 'publicKey' + ); + + expect(contractFunctionMock.encode).toHaveBeenCalledTimes(1); + }) + + it('calls sendTransaction and estimation of the gas throws', async () => { + (contractFunctionMock.encode as jest.MockedFunction).mockReturnValue('0x00'); + + (jsonRpcProviderMock.estimateGas as jest.MockedFunction).mockReturnValue(Promise.reject('NOPE')); + + await expect(provider.sendTransaction('GovernQueue', contractFunctionMock)).rejects.toEqual('NOPE'); + + expect(contractFunctionMock.encode).toHaveBeenCalledTimes(1); + }) + + it('calls sendTransaction and encoding of the contract function throws', async () => { + (contractFunctionMock.encode as jest.MockedFunction).mockImplementation(() => {throw 'NOPE'}) + + await expect(provider.sendTransaction('GovernQueue', contractFunctionMock)).rejects.toEqual('NOPE'); + + expect(contractFunctionMock.encode).toHaveBeenCalledTimes(1); + }) + + it('calls sendTransaction and the BaseProvider throws on executing the request', async () => { + (walletMock.sign as jest.MockedFunction).mockReturnValueOnce(Promise.resolve('0x00')); + + (contractFunctionMock.encode as jest.MockedFunction).mockReturnValue('0x00'); + + (jsonRpcProviderMock.sendTransaction as jest.MockedFunction).mockReturnValue(Promise.reject('NOPE')); + + (jsonRpcProviderMock.estimateGas as jest.MockedFunction).mockReturnValue(Promise.resolve(BigNumber.from(0))); + + await expect(provider.sendTransaction('GovernQueue', contractFunctionMock)).rejects.toEqual('NOPE'); + + expect(jsonRpcProviderMock.sendTransaction).toHaveBeenNthCalledWith(1, '0x00'); + + expect(walletMock.sign).toHaveBeenNthCalledWith( + 1, + { + to: '0x00', + data: '0x00', + gasLimit: BigNumber.from(0) + }, + 'publicKey' + ); + + expect(contractFunctionMock.encode).toHaveBeenCalledTimes(1); + }) + + it('calls sendTransaction and the BaseProvider throws while waiting until it is mined', async () => { + const txResponse = new TXResponse() + + txResponse.wait.mockReturnValueOnce(Promise.reject('NOPE')); + + (walletMock.sign as jest.MockedFunction).mockReturnValueOnce(Promise.resolve('0x00')); + + (contractFunctionMock.encode as jest.MockedFunction).mockReturnValue('0x00'); + + (jsonRpcProviderMock.sendTransaction as jest.MockedFunction).mockReturnValue(Promise.resolve(txResponse)); + + (jsonRpcProviderMock.estimateGas as jest.MockedFunction).mockReturnValue(Promise.resolve(BigNumber.from(0))); + + await expect(provider.sendTransaction('GovernQueue', contractFunctionMock)).rejects.toEqual('NOPE'); + + expect(jsonRpcProviderMock.sendTransaction).toHaveBeenNthCalledWith(1, '0x00'); + + expect(txResponse.wait).toHaveBeenNthCalledWith(1, 0); + + expect(walletMock.sign).toHaveBeenNthCalledWith( + 1, + { + to: '0x00', + data: '0x00', + gasLimit: BigNumber.from(0) + }, + 'publicKey' + ); + + expect(contractFunctionMock.encode).toHaveBeenCalledTimes(1); + }) +}) diff --git a/packages/govern-tx/test/src/transactions/challenge/ChallengeTransactionTest.ts b/packages/govern-tx/test/src/transactions/challenge/ChallengeTransactionTest.ts new file mode 100644 index 000000000..e0bb5d057 --- /dev/null +++ b/packages/govern-tx/test/src/transactions/challenge/ChallengeTransactionTest.ts @@ -0,0 +1,34 @@ +import { EthereumOptions } from '../../../../src/config/Configuration' +import Provider from '../../../../src/provider/Provider' +import * as challengeABI from '../../../../src/transactions/challenge/challenge.json' +import ChallengeTransaction from '../../../../src/transactions/challenge/ChallengeTransaction' +import Whitelist from '../../../../src/db/Whitelist'; + +// Mocks +jest.mock('../../../../lib/transactions/AbstractTransaction') + +/** + * ChallengeTransaction test + */ +describe('ChallengeTransactionTest', () => { + let challengeTransaction: ChallengeTransaction + + beforeEach(() => { + challengeTransaction = new ChallengeTransaction( + {} as EthereumOptions, + {} as Provider, + {} as Whitelist, + {} as any + ) + }) + + it('has the correct contract defined', () => { + //@ts-ignore + expect(challengeTransaction.contract).toEqual('GovernQueue') + }) + + it('has the correct function abi defined', () => { + //@ts-ignore + expect(challengeTransaction.functionABI).toEqual(challengeABI) + }) +}) diff --git a/packages/govern-tx/test/src/transactions/execute/ExecuteTransactionTest.ts b/packages/govern-tx/test/src/transactions/execute/ExecuteTransactionTest.ts new file mode 100644 index 000000000..f6c404f64 --- /dev/null +++ b/packages/govern-tx/test/src/transactions/execute/ExecuteTransactionTest.ts @@ -0,0 +1,34 @@ +import { EthereumOptions } from '../../../../src/config/Configuration' +import Provider from '../../../../src/provider/Provider' +import * as executeABI from '../../../../src/transactions/execute/execute.json' +import ExecuteTransaction from '../../../../src/transactions/execute/ExecuteTransaction' +import Whitelist from '../../../../src/db/Whitelist'; + +// Mocks +jest.mock('../../../../lib/transactions/AbstractTransaction') + +/** + * ExecuteTransaction test + */ +describe('ExecuteTransactionTest', () => { + let executeTransaction: ExecuteTransaction + + beforeEach(() => { + executeTransaction = new ExecuteTransaction( + {} as EthereumOptions, + {} as Provider, + {} as Whitelist, + {} as any + ) + }) + + it('has the correct contract defined', () => { + //@ts-ignore + expect(executeTransaction.contract).toEqual('GovernQueue') + }) + + it('has the correct function abi defined', () => { + //@ts-ignore + expect(executeTransaction.functionABI).toEqual(executeABI) + }) +}) diff --git a/packages/govern-tx/test/src/transactions/schedule/ScheduleTransactionTest.ts b/packages/govern-tx/test/src/transactions/schedule/ScheduleTransactionTest.ts new file mode 100644 index 000000000..86341fe14 --- /dev/null +++ b/packages/govern-tx/test/src/transactions/schedule/ScheduleTransactionTest.ts @@ -0,0 +1,34 @@ +import { EthereumOptions } from '../../../../src/config/Configuration' +import Provider from '../../../../src/provider/Provider' +import * as scheduleABI from '../../../../src/transactions/schedule/schedule.json' +import ScheduleTransaction from '../../../../src/transactions/schedule/ScheduleTransaction' +import Whitelist from '../../../../src/db/Whitelist'; + +// Mocks +jest.mock('../../../../lib/transactions/AbstractTransaction') + +/** + * ScheduleTransaction test + */ +describe('ScheduleTransactionTest', () => { + let scheduleTransaction: ScheduleTransaction + + beforeEach(() => { + scheduleTransaction = new ScheduleTransaction( + {} as EthereumOptions, + {} as Provider, + {} as Whitelist, + {} as any + ) + }) + + it('has the correct contract defined', () => { + //@ts-ignore + expect(scheduleTransaction.contract).toEqual('GovernQueue') + }) + + it('has the correct function abi defined', () => { + //@ts-ignore + expect(scheduleTransaction.functionABI).toEqual(scheduleABI) + }) +}) diff --git a/packages/govern-tx/test/src/wallet/WalletTest.ts b/packages/govern-tx/test/src/wallet/WalletTest.ts new file mode 100644 index 000000000..afa8dbed0 --- /dev/null +++ b/packages/govern-tx/test/src/wallet/WalletTest.ts @@ -0,0 +1,69 @@ +import {Wallet as EthersWallet} from '@ethersproject/wallet' +import { TransactionRequest } from '@ethersproject/providers'; +import { DatabaseOptions } from '../../../src/config/Configuration'; +import Database from '../../../src/db/Database'; +import Wallet from '../../../src/wallet/Wallet'; + +// Mocks +jest.mock('../../../src/db/Database') +jest.mock('@ethersproject/wallet') + +/** + * Wallet test + */ +describe('WalletTest', () => { + let wallet: Wallet, + databaseMock: Database + + beforeEach(() => { + new Database({} as DatabaseOptions) + databaseMock = (Database as jest.MockedClass).mock.instances[0] + + EthersWallet.prototype.signTransaction = jest.fn() + + wallet = new Wallet(databaseMock) + }) + + it('calls sign and returns the expected value', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(['0x01'])); + + (EthersWallet.prototype.signTransaction as jest.MockedFunction).mockReturnValueOnce(Promise.resolve('0x02')) + + await expect(wallet.sign({} as TransactionRequest, '0x00')).resolves.toEqual('0x02') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT PrivateKey FROM wallet WHERE PublicKey='0x00'`) + + expect(EthersWallet).toHaveBeenNthCalledWith(1, '0x01') + + expect(EthersWallet.prototype.signTransaction).toHaveBeenNthCalledWith(1, {from: '0x00'}) + }) + + it('calls sign and throws as expected', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')); + + await expect(wallet.sign({} as TransactionRequest, '0x00')).rejects.toEqual('NOPE') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT PrivateKey FROM wallet WHERE PublicKey='0x00'`) + }) + + + it('calls sign with another publicKey and releads to wallet for it', async () => { + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(['0x01'])); + (databaseMock.query as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(['0x03'])); + + (EthersWallet.prototype.signTransaction as jest.MockedFunction).mockReturnValue(Promise.resolve('0x02')) + + await expect(wallet.sign({} as TransactionRequest, '0x00')).resolves.toEqual('0x02') + await expect(wallet.sign({} as TransactionRequest, '0x01')).resolves.toEqual('0x02') + + expect(databaseMock.query).toHaveBeenNthCalledWith(1, `SELECT PrivateKey FROM wallet WHERE PublicKey='0x00'`) + + expect(EthersWallet).toHaveBeenNthCalledWith(1, '0x01') + + expect(EthersWallet).toHaveBeenNthCalledWith(2, '0x03') + + expect(EthersWallet.prototype.signTransaction).toHaveBeenNthCalledWith(1, {from: '0x00'}) + + expect(EthersWallet.prototype.signTransaction).toHaveBeenNthCalledWith(2, {from: '0x01'}) + }) +}) \ No newline at end of file diff --git a/packages/govern-tx/test/src/whitelist/AddItemActionTest.ts b/packages/govern-tx/test/src/whitelist/AddItemActionTest.ts new file mode 100644 index 000000000..26712b763 --- /dev/null +++ b/packages/govern-tx/test/src/whitelist/AddItemActionTest.ts @@ -0,0 +1,88 @@ +import { isAddress } from '@ethersproject/address'; +import Database from '../../../src/db/Database'; +import Whitelist, { ListItem } from '../../../src/db/Whitelist'; +import AddItemAction from '../../../src/whitelist/AddItemAction'; + + +// Mocks +jest.mock('../../../src/db/Whitelist') +jest.mock('@ethersproject/address') + +/** + * AddItemAction test + */ +describe('AddItemActionTest', () => { + let addItemAction: AddItemAction, + whitelistMock: Whitelist + + const request = { + body : { + message: { + publicKey: '0x00', + txLimit: 1 + }, + signature: '' + } + } + + beforeEach(() => { + new Whitelist({} as Database) + whitelistMock = (Whitelist as jest.MockedClass).mock.instances[0] + }) + + it('calls validateRequest and returns the expected values', () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true) + + addItemAction = new AddItemAction(whitelistMock, request as any) + + expect(isAddress).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls validateRequest and throws because of a invalid ethereum address', () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(false) + + expect(() => { + addItemAction = new AddItemAction(whitelistMock, request as any) + }).toThrow('Invalid public key passed!') + + expect(isAddress).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls validateRequest and throws because of a invalid rate limit', () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true) + + request.body.message.txLimit = 0; + + expect(() => { + addItemAction = new AddItemAction(whitelistMock, request as any) + }).toThrow('Invalid rate limit passed!') + + expect(isAddress).toHaveBeenNthCalledWith(1, '0x00') + + request.body.message.txLimit = 1; + }) + + it('calls execute and returns the expected result', async () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true); + + (whitelistMock.addItem as jest.MockedFunction).mockReturnValueOnce(Promise.resolve({} as ListItem)); + + addItemAction = new AddItemAction(whitelistMock, request as any) + + await expect(addItemAction.execute()).resolves.toEqual({}) + + expect(whitelistMock.addItem).toHaveBeenNthCalledWith(1, '0x00', 1) + }) + + it('calls execute and throws as expected', async () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true); + + (whitelistMock.addItem as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')); + + addItemAction = new AddItemAction(whitelistMock, request as any) + + await expect(addItemAction.execute()).rejects.toEqual('NOPE') + + expect(whitelistMock.addItem).toHaveBeenNthCalledWith(1, '0x00', 1) + }) +}) diff --git a/packages/govern-tx/test/src/whitelist/DeleteItemActionTest.ts b/packages/govern-tx/test/src/whitelist/DeleteItemActionTest.ts new file mode 100644 index 000000000..78d4ce727 --- /dev/null +++ b/packages/govern-tx/test/src/whitelist/DeleteItemActionTest.ts @@ -0,0 +1,72 @@ +import { isAddress } from '@ethersproject/address'; +import Database from '../../../src/db/Database'; +import Whitelist, { ListItem } from '../../../src/db/Whitelist'; +import DeleteItemAction from '../../../src/whitelist/DeleteItemAction'; + +// Mocks +jest.mock('../../../src/db/Whitelist') +jest.mock('@ethersproject/address') + +/** + * DeleteItemAction test + */ +describe('DeleteItemActionTest', () => { + let deleteItemAction: DeleteItemAction, + whitelistMock: Whitelist + + const request = { + body: { + message: { + publicKey: '0x00' + }, + signature: '' + } + } + + beforeEach(() => { + new Whitelist({} as Database) + whitelistMock = (Whitelist as jest.MockedClass).mock.instances[0] + }) + + it('calls validateRequest and returns the expected values', () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true) + + deleteItemAction = new DeleteItemAction(whitelistMock, request as any) + + expect(isAddress).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls validateRequest and throws because of a invalid ethereum address', () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(false) + + expect(() => { + deleteItemAction = new DeleteItemAction(whitelistMock, request as any) + }).toThrow('Invalid public key passed!') + + expect(isAddress).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls execute and returns the expected result', async () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true); + + (whitelistMock.deleteItem as jest.MockedFunction).mockReturnValueOnce(Promise.resolve(true)); + + deleteItemAction = new DeleteItemAction(whitelistMock, request as any) + + await expect(deleteItemAction.execute()).resolves.toEqual(true) + + expect(whitelistMock.deleteItem).toHaveBeenNthCalledWith(1, '0x00') + }) + + it('calls execute and throws as expected', async () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true); + + (whitelistMock.deleteItem as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')); + + deleteItemAction = new DeleteItemAction(whitelistMock, request as any) + + await expect(deleteItemAction.execute()).rejects.toEqual('NOPE') + + expect(whitelistMock.deleteItem).toHaveBeenNthCalledWith(1, '0x00') + }) +}) diff --git a/packages/govern-tx/test/src/whitelist/GetListActionTest.ts b/packages/govern-tx/test/src/whitelist/GetListActionTest.ts new file mode 100644 index 000000000..81c1bbcbc --- /dev/null +++ b/packages/govern-tx/test/src/whitelist/GetListActionTest.ts @@ -0,0 +1,45 @@ +import { isAddress } from '@ethersproject/address'; +import Database from '../../../src/db/Database'; +import Whitelist, { ListItem } from '../../../src/db/Whitelist'; +import GetListAction from '../../../src/whitelist/GetListAction'; + +// Mocks +jest.mock('../../../src/db/Whitelist') +jest.mock('@ethersproject/address') + +/** + * GetListAction test + */ +describe('GetListActionTest', () => { + let getListAction: GetListAction, + whitelistMock: Whitelist + + beforeEach(() => { + new Whitelist({} as Database) + whitelistMock = (Whitelist as jest.MockedClass).mock.instances[0] + }) + + it('calls execute and returns the expected result', async () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true); + + (whitelistMock.getList as jest.MockedFunction).mockReturnValueOnce(Promise.resolve([{}] as ListItem[])); + + getListAction = new GetListAction(whitelistMock, {} as any) + + await expect(getListAction.execute()).resolves.toEqual([{}]) + + expect(whitelistMock.getList).toHaveBeenNthCalledWith(1) + }) + + it('calls execute and throws as expected', async () => { + (isAddress as jest.MockedFunction).mockReturnValueOnce(true); + + (whitelistMock.getList as jest.MockedFunction).mockReturnValueOnce(Promise.reject('NOPE')); + + getListAction = new GetListAction(whitelistMock, {} as any) + + await expect(getListAction.execute()).rejects.toEqual('NOPE') + + expect(whitelistMock.getList).toHaveBeenNthCalledWith(1) + }) +}) diff --git a/packages/govern-tx/tsconfig.json b/packages/govern-tx/tsconfig.json new file mode 100644 index 000000000..c7cbba784 --- /dev/null +++ b/packages/govern-tx/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "@ethereumjs/config-tsc", + "compilerOptions": { + "resolveJsonModule": true, + "removeComments": true, + "strictPropertyInitialization": false, + "outDir": "./dist", + "inlineSources": true, + "types": [ + "jest", + "node" + ], + "lib": [ + "dom", + "es2018" + ] + }, + "include": [ + "./src/**/*", + "./lib/**/*", + ] +} diff --git a/packages/govern-tx/tslint.json b/packages/govern-tx/tslint.json new file mode 100644 index 000000000..8d923d657 --- /dev/null +++ b/packages/govern-tx/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": "@ethereumjs/config-tslint", + "rules": { + "prefer-conditional-expression": true + } +} diff --git a/yarn.lock b/yarn.lock index d06245bad..705654052 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1496,6 +1496,17 @@ "@ethersproject/rlp" "^5.0.3" bn.js "^4.4.0" +"@ethersproject/address@^5.0.7": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.8.tgz#0c551659144a5a7643c6bea337149d410825298f" + integrity sha512-V87DHiZMZR6hmFYmoGaHex0D53UEbZpW75uj8AqPbjYUmi65RB4N2LPRcJXuWuN2R0Y2CxkvW6ArijWychr5FA== + dependencies: + "@ethersproject/bignumber" "^5.0.10" + "@ethersproject/bytes" "^5.0.4" + "@ethersproject/keccak256" "^5.0.3" + "@ethersproject/logger" "^5.0.5" + "@ethersproject/rlp" "^5.0.3" + "@ethersproject/base64@5.0.4", "@ethersproject/base64@^5.0.3": version "5.0.4" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.0.4.tgz#b0d8fdbf3dda977cf546dcd35725a7b1d5256caa" @@ -1520,6 +1531,15 @@ "@ethersproject/logger" "^5.0.5" bn.js "^4.4.0" +"@ethersproject/bignumber@^5.0.10": + version "5.0.12" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.12.tgz#fe4a78667d7cb01790f75131147e82d6ea7e7cba" + integrity sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw== + dependencies: + "@ethersproject/bytes" "^5.0.8" + "@ethersproject/logger" "^5.0.5" + bn.js "^4.4.0" + "@ethersproject/bytes@5.0.5", "@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@^5.0.4": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.5.tgz#688b70000e550de0c97a151a21f15b87d7f97d7c" @@ -1527,6 +1547,13 @@ dependencies: "@ethersproject/logger" "^5.0.5" +"@ethersproject/bytes@^5.0.6", "@ethersproject/bytes@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.8.tgz#cf1246a6a386086e590063a4602b1ffb6cc43db1" + integrity sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw== + dependencies: + "@ethersproject/logger" "^5.0.5" + "@ethersproject/constants@5.0.5", "@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.4": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.5.tgz#0ed19b002e8404bdf6d135234dc86a7d9bcf9b71" @@ -1563,6 +1590,20 @@ "@ethersproject/properties" "^5.0.4" "@ethersproject/strings" "^5.0.4" +"@ethersproject/hash@^5.0.9": + version "5.0.9" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.9.tgz#81252a848185b584aa600db4a1a68cad9229a4d4" + integrity sha512-e8/i2ZDeGSgCxXT0vocL54+pMbw5oX5fNjb2E3bAIvdkh5kH29M7zz1jHu1QDZnptIuvCZepIbhUH8lxKE2/SQ== + dependencies: + "@ethersproject/abstract-signer" "^5.0.6" + "@ethersproject/address" "^5.0.5" + "@ethersproject/bignumber" "^5.0.8" + "@ethersproject/bytes" "^5.0.4" + "@ethersproject/keccak256" "^5.0.3" + "@ethersproject/logger" "^5.0.5" + "@ethersproject/properties" "^5.0.4" + "@ethersproject/strings" "^5.0.4" + "@ethersproject/hdnode@5.0.5", "@ethersproject/hdnode@^5.0.4": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.0.5.tgz#1f89aad0a5ba9dfae3a85a36e0669f8bc7a74781" @@ -1660,6 +1701,31 @@ bech32 "1.1.4" ws "7.2.3" +"@ethersproject/providers@^5.0.15": + version "5.0.17" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.17.tgz#f380e7831149e24e7a1c6c9b5fb1d6dfc729d024" + integrity sha512-bJnvs5X7ttU5x2ekGJYG7R3Z+spZawLFfR0IDsbaMDLiCwZOyrgk+VTBU7amSFLT0WUhWFv8WwSUB+AryCQG1Q== + dependencies: + "@ethersproject/abstract-provider" "^5.0.4" + "@ethersproject/abstract-signer" "^5.0.4" + "@ethersproject/address" "^5.0.4" + "@ethersproject/basex" "^5.0.3" + "@ethersproject/bignumber" "^5.0.7" + "@ethersproject/bytes" "^5.0.4" + "@ethersproject/constants" "^5.0.4" + "@ethersproject/hash" "^5.0.4" + "@ethersproject/logger" "^5.0.5" + "@ethersproject/networks" "^5.0.3" + "@ethersproject/properties" "^5.0.3" + "@ethersproject/random" "^5.0.3" + "@ethersproject/rlp" "^5.0.3" + "@ethersproject/sha2" "^5.0.3" + "@ethersproject/strings" "^5.0.4" + "@ethersproject/transactions" "^5.0.5" + "@ethersproject/web" "^5.0.6" + bech32 "1.1.4" + ws "7.2.3" + "@ethersproject/random@5.0.4", "@ethersproject/random@^5.0.3": version "5.0.4" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.4.tgz#98f7cf65b0e588cec39ef24843e391ed5004556f" @@ -1760,6 +1826,27 @@ "@ethersproject/transactions" "^5.0.5" "@ethersproject/wordlists" "^5.0.4" +"@ethersproject/wallet@^5.0.8": + version "5.0.9" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.9.tgz#976c7d950489c40308d676869d24e59ab7b82ad1" + integrity sha512-GfpQF56PO/945SJq7Wdg5F5U6wkxaDgkAzcgGbCW6Joz8oW8MzKItkvYCzMh+j/8gJMzFncsuqX4zg2gq3J6nQ== + dependencies: + "@ethersproject/abstract-provider" "^5.0.4" + "@ethersproject/abstract-signer" "^5.0.4" + "@ethersproject/address" "^5.0.4" + "@ethersproject/bignumber" "^5.0.7" + "@ethersproject/bytes" "^5.0.4" + "@ethersproject/hash" "^5.0.4" + "@ethersproject/hdnode" "^5.0.4" + "@ethersproject/json-wallets" "^5.0.6" + "@ethersproject/keccak256" "^5.0.3" + "@ethersproject/logger" "^5.0.5" + "@ethersproject/properties" "^5.0.3" + "@ethersproject/random" "^5.0.3" + "@ethersproject/signing-key" "^5.0.4" + "@ethersproject/transactions" "^5.0.5" + "@ethersproject/wordlists" "^5.0.4" + "@ethersproject/web@5.0.9", "@ethersproject/web@^5.0.6": version "5.0.9" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.9.tgz#b08f8295f4bfd4777c8723fe9572f5453b9f03cb" @@ -4058,7 +4145,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@26.x", "@types/jest@^26.0.0", "@types/jest@^26.0.14", "@types/jest@^26.0.15": +"@types/jest@26.x", "@types/jest@^26.0.14", "@types/jest@^26.0.15": version "26.0.15" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.15.tgz#12e02c0372ad0548e07b9f4e19132b834cb1effe" integrity sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog== @@ -4167,6 +4254,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.1.tgz#303f74c8a2b35644594139e948b2be470ae1186f" integrity sha512-/xaVmBBjOGh55WCqumLAHXU9VhjGtmyTGqJzFBXRWZzByOXI5JAJNx9xPVGEsNizrNwcec92fQMj458MWfjN1A== +"@types/node@^14.14.10": + version "14.14.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785" + integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ== + "@types/node@^14.14.6": version "14.14.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.6.tgz#146d3da57b3c636cc0d1769396ce1cfa8991147f" @@ -4392,7 +4484,7 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.1.tgz#5668c0bce55a91f2b9566b1d8a4c0a8dbbc79764" integrity sha512-wmk0xQI6Yy7Fs/il4EpOcflG4uonUpYGqvZARESLc2oy4u69fkatFLbJOeW4Q6awO15P4rduAe6xkwHevpXcUQ== -"@typescript-eslint/eslint-plugin@^2.10.0", "@typescript-eslint/eslint-plugin@^2.29.0": +"@typescript-eslint/eslint-plugin@^2.10.0": version "2.34.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== @@ -4412,7 +4504,7 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.10.0", "@typescript-eslint/parser@^2.29.0": +"@typescript-eslint/parser@^2.10.0": version "2.34.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== @@ -4954,6 +5046,11 @@ abstract-leveldown@~2.6.0: dependencies: xtend "~4.0.0" +abstract-logging@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" + integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== + accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -5094,7 +5191,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.11.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -5469,6 +5566,11 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -5712,14 +5814,6 @@ async-eventemitter@^0.2.2: dependencies: async "^2.4.0" -async-kit@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/async-kit/-/async-kit-2.2.4.tgz#53249064fc5c894c46210cbd1c1a9ff5bd44bf9f" - integrity sha512-LuWbpSYdTwrGv5MWhsUY69UaQAc3AYMwf/LwTupotu/ubb/1TjDd03WK1eoMXRK/s3bmi4aUkKY0TmxYQgRrmw== - dependencies: - nextgen-events "^0.14.5" - tree-kit "^0.5.27" - async-limiter@^1.0.0, async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" @@ -5776,6 +5870,11 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + authereum@^0.0.4-beta.157: version "0.0.4-beta.201" resolved "https://registry.yarnpkg.com/authereum/-/authereum-0.0.4-beta.201.tgz#ea380efc6d231dc4222dc20cd9395b02318dd0c3" @@ -5809,6 +5908,16 @@ autoprefixer@^9.6.1: postcss "^7.0.32" postcss-value-parser "^4.1.0" +avvio@^7.1.2: + version "7.2.0" + resolved "https://registry.yarnpkg.com/avvio/-/avvio-7.2.0.tgz#b4bf4eaf4a0207a4e6a58a7859207250793cc81b" + integrity sha512-KtC63UyZARidAoIV8wXutAZnDIbZcXBqLjTAhZOX+mdMZBQCh5il/15MvCvma1178nhTwvN2D0TOAdiKG1MpUA== + dependencies: + archy "^1.0.0" + debug "^4.0.0" + fastq "^1.6.1" + queue-microtask "^1.1.2" + await-semaphore@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3" @@ -7389,7 +7498,16 @@ chai@^4.2.0: pathval "^1.1.0" type-detect "^4.0.5" -chalk@1.x, chalk@^1.1.3: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -7400,15 +7518,6 @@ chalk@1.x, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -7606,11 +7715,6 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-spinners@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" - integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== - cli-spinners@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.5.0.tgz#12763e47251bf951cb75c201dfa58ff1bcb2d047" @@ -7830,11 +7934,6 @@ command-line-args@^4.0.7: find-replace "^1.0.3" typical "^2.6.1" -commander@2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - commander@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" @@ -7868,11 +7967,6 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -8091,7 +8185,7 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -cookie@^0.4.1: +cookie@^0.4.0, cookie@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== @@ -8175,17 +8269,6 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1: js-yaml "^3.13.1" parse-json "^4.0.0" -cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - coveralls@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.1.0.tgz#13c754d5e7a2dd8b44fe5269e21ca394fb4d615b" @@ -8245,15 +8328,6 @@ cross-spawn@7.0.1: shebang-command "^2.0.0" which "^2.0.1" -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -9022,11 +9096,6 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" -diff-match-patch@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" - integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== - diff-sequences@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" @@ -10676,7 +10745,7 @@ ethers@^4.0.0-beta.1, ethers@^4.0.32: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.14, ethers@^5.0.16, ethers@^5.0.2, ethers@^5.0.7: +ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.14, ethers@^5.0.2, ethers@^5.0.7: version "5.0.19" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.19.tgz#a4636f62a180135b13fd1f0a393477beafd535b7" integrity sha512-0AZnUgZh98q888WAd1oI3aLeI+iyDtrupjANVtPPS7O63lVopkR/No8A1NqSkgl/rU+b2iuu2mUZor6GD4RG2w== @@ -10778,32 +10847,6 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== -execa@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe" - integrity sha1-V7aaWU8IF1nGnlNw8NF7nLEWWP4= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.9.0.tgz#adb7ce62cf985071f60580deb4a88b9e34712d01" - integrity sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA== - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -11023,6 +11066,11 @@ fake-merkle-patricia-tree@^1.0.1: dependencies: checkpoint-store "^1.1.0" +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -11062,16 +11110,62 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-json-stringify@^2.2.1: + version "2.2.9" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.2.9.tgz#6298eb0a78e540d74b7507afd55d1a456d584d96" + integrity sha512-O8YmNoc7LnfSafVaTfa1yXVFT4UMsi/N7cYcNZw6w5D5tltyu6XGXvH45mvWfsrcFoSK+H0q0exGXsUqC18z/g== + dependencies: + ajv "^6.11.0" + deepmerge "^4.2.2" + string-similarity "^4.0.1" + fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-safe-stringify@^2.0.6: +fast-redact@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.0.tgz#ac2f9e36c9f4976f5db9fb18c6ffbaf308cf316d" + integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== + +fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fastify-error@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/fastify-error/-/fastify-error-0.2.0.tgz#9a1c28d4f42b6259e7a549671c8e5e2d85660634" + integrity sha512-zabxsBatj59ROG0fhP36zNdc5Q1/eYeH9oSF9uvfrurZf8/JKfrJbMcIGrLpLWcf89rS6L91RHWm20A/X85hcA== + +fastify-warning@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/fastify-warning/-/fastify-warning-0.2.0.tgz#e717776026a4493dc9a2befa44db6d17f618008f" + integrity sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw== + +fastify@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/fastify/-/fastify-3.8.0.tgz#455bfa70394322247050c330d0e52532b349662d" + integrity sha512-w57/uvyQWzF/KSr9CbWQ5nfTqSSfYcmrems9Lc3VvtrAF7EsLbfZQBQZul6xwvE1uEfxA4nGdoUKqpU7xiv7cw== + dependencies: + abstract-logging "^2.0.0" + ajv "^6.12.2" + avvio "^7.1.2" + fast-json-stringify "^2.2.1" + fastify-error "^0.2.0" + fastify-warning "^0.2.0" + find-my-way "^3.0.5" + flatstr "^1.0.12" + light-my-request "^4.2.0" + pino "^6.2.1" + proxy-addr "^2.0.5" + readable-stream "^3.4.0" + rfdc "^1.1.4" + secure-json-parse "^2.0.0" + semver "^7.3.2" + tiny-lru "^7.0.0" + fastq@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" @@ -11079,6 +11173,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fastq@^1.6.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947" + integrity sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -11254,6 +11355,15 @@ find-cache-dir@^3.2.0: make-dir "^3.0.2" pkg-dir "^4.1.0" +find-my-way@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-3.0.5.tgz#f71c5ef1b4865401e1b97ba428121a8f55439eec" + integrity sha512-FweGg0cv1sBX8z7WhvBX5B5AECW4Zdh/NiB38Oa0qwSNIyPgRBCl/YjxuZn/rz38E/MMBHeVKJ22i7W3c626Gg== + dependencies: + fast-decode-uri-component "^1.0.1" + safe-regex2 "^2.0.0" + semver-store "^0.3.0" + find-replace@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-1.0.3.tgz#b88e7364d2d9c959559f388c66670d6130441fa0" @@ -11292,13 +11402,6 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-versions@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" - integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== - dependencies: - semver-regex "^2.0.0" - find-yarn-workspace-root@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz#40eb8e6e7c2502ddfaa2577c176f221422f860db" @@ -11328,6 +11431,11 @@ flatmap@0.0.3: resolved "https://registry.yarnpkg.com/flatmap/-/flatmap-0.0.3.tgz#1f18a4d938152d495965f9c958d923ab2dd669b4" integrity sha1-Hxik2TgVLUlZZfnJWNkjqy3WabQ= +flatstr@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" + integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== + flatted@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" @@ -11921,17 +12029,6 @@ globals@^9.18.0: resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== -globby@6.1.0, globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - globby@8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" @@ -11971,6 +12068,17 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + globby@^9.2.0: version "9.2.0" resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" @@ -12669,22 +12777,6 @@ husky@^0.14.3: normalize-path "^1.0.0" strip-indent "^2.0.0" -husky@^4.2.5: - version "4.3.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" - integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== - dependencies: - chalk "^4.0.0" - ci-info "^2.0.0" - compare-versions "^3.6.0" - cosmiconfig "^7.0.0" - find-versions "^3.2.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" - please-upgrade-node "^3.2.0" - slash "^3.0.0" - which-pm-runs "^1.0.0" - iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -12737,7 +12829,7 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^3.3.5, ignore@^3.3.7: +ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== @@ -12792,7 +12884,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== @@ -12916,7 +13008,7 @@ inquirer@7.0.4: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@^6.2.0, inquirer@^6.2.2: +inquirer@^6.2.0: version "6.5.2" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== @@ -13207,14 +13299,6 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== -is-ci-cli@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/is-ci-cli/-/is-ci-cli-2.1.2.tgz#52f233e3f6d7718642f2bb356a5b5110b29d9b02" - integrity sha512-dgGkNUs6ws6RfkjQYwkrFhwkdyNkWxyng1Tz7W7OnkGPhXV9z1ofnzmTmuXpvlCfolB8Z7BBL/Zi3LDB6b2hTg== - dependencies: - cross-spawn "^7.0.0" - is-ci "^2.0.0" - is-ci@^1.0.10: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -14921,11 +15005,6 @@ just-map-keys@^1.1.0: resolved "https://registry.yarnpkg.com/just-map-keys/-/just-map-keys-1.1.0.tgz#9663c9f971ba46e17f2b05e66fec81149375f230" integrity sha512-oNKi+4y7fr8lXnhKYpBbCkiwHRVkAnx0VDkCeTDtKKMzGr1Lz1Yym+RSieKUTKim68emC5Yxrb4YmiF9STDO+g== -kebab-case@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/kebab-case/-/kebab-case-1.0.0.tgz#3f9e4990adcad0c686c0e701f7645868f75f91eb" - integrity sha1-P55JkK3K0MaGwOcB92RYaPdfkes= - keccak256@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/keccak256/-/keccak256-1.0.1.tgz#f579e937d6f32ac4ab62ff862d50204f775bb6f6" @@ -15307,6 +15386,17 @@ libp2p-crypto@~0.16.1: tweetnacl "^1.0.0" ursa-optional "~0.10.0" +light-my-request@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-4.3.0.tgz#0178dfe7f298089df976f8e4cc0e10998be50aad" + integrity sha512-WrEvI7V41ZbEUe0bsfuS170QrYSVADKA0JiWyK/lVtm4Ra26pl9CYKBdlr823/s37N2wMJze8YNkHbg11aZWAw== + dependencies: + ajv "^6.12.2" + cookie "^0.4.0" + fastify-warning "^0.2.0" + readable-stream "^3.6.0" + set-cookie-parser "^2.4.1" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -15573,7 +15663,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= -lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: +lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -15590,13 +15680,6 @@ log-symbols@3.0.0, log-symbols@^3.0.0: dependencies: chalk "^2.4.2" -log-symbols@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - loglevel@^1.6.6, loglevel@^1.6.7, loglevel@^1.6.8: version "1.7.0" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" @@ -15668,14 +15751,6 @@ lru-cache@^3.2.0: dependencies: pseudomap "^1.0.1" -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru_map@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" @@ -16314,11 +16389,6 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -mri@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" - integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -16584,16 +16654,6 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -nextgen-events@^0.14.5: - version "0.14.6" - resolved "https://registry.yarnpkg.com/nextgen-events/-/nextgen-events-0.14.6.tgz#945b3fc75951fe8c945f8455c35bf644a3a2c8b1" - integrity sha512-Ln9d5Midoah7RCxFk8z9tAAcRW/VkB4wZ61Nnw8aqM1/lb/WfPAnlzpLGYRghEjwZdXQNQedTfD/gclYMeI0eQ== - -nextgen-events@^0.9.8: - version "0.9.9" - resolved "https://registry.yarnpkg.com/nextgen-events/-/nextgen-events-0.9.9.tgz#39a8afc4a2b845388c57e2c6bb9716711986a3a0" - integrity sha1-OaivxKK4RTiMV+LGu5cWcRmGo6A= - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -16982,26 +17042,6 @@ nwsapi@^2.0.7, nwsapi@^2.1.3, nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -oao@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/oao/-/oao-2.0.0.tgz#2309ea06d9090d55fa1505e0ccee5ec710247c0f" - integrity sha512-ZIOSQ0rqm9CLjCDBARE47QA6Tb/dk4k+s+FuzIpW2ZgfMqh9tYioZ4B2p62Ye3ahRzrpAXVQiPshwPGte2lpiw== - dependencies: - commander "2.19.0" - execa "0.6.3" - globby "6.1.0" - inquirer "^6.2.2" - kebab-case "1.0.0" - minimatch "^3.0.4" - rimraf "2.6.3" - semver "5.6.0" - shelljs "0.7.8" - split "1.0.1" - storyboard "3.1.4" - storyboard-listener-console "3.1.4" - storyboard-listener-console-parallel "3.1.4" - timm "^1.6.2" - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -17224,7 +17264,7 @@ open@^7.0.2: is-docker "^2.0.0" is-wsl "^2.1.1" -opencollective-postinstall@^2.0.0, opencollective-postinstall@^2.0.2: +opencollective-postinstall@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== @@ -17277,16 +17317,6 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" -ora@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-1.4.0.tgz#884458215b3a5d4097592285f93321bb7a79e2e5" - integrity sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw== - dependencies: - chalk "^2.1.0" - cli-cursor "^2.1.0" - cli-spinners "^1.0.1" - log-symbols "^2.1.0" - ora@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/ora/-/ora-4.1.1.tgz#566cc0348a15c36f5f0e979612842e02ba9dddbc" @@ -17866,6 +17896,23 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pino-std-serializers@^2.4.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz#40ead781c65a0ce7ecd9c1c33f409d31fe712315" + integrity sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg== + +pino@^6.2.1: + version "6.7.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-6.7.0.tgz#d5d96b7004fed78816b5694fda3eab02b5ca6d23" + integrity sha512-vPXJ4P9rWCwzlTJt+f0Ni4THc3DWyt8iDDCO4edQ8narTu6hnpzdXu8FqeSJCGndl1W6lfbYQUQihUO54y66Lw== + dependencies: + fast-redact "^3.0.0" + fast-safe-stringify "^2.0.7" + flatstr "^1.0.12" + pino-std-serializers "^2.4.2" + quick-format-unescaped "^4.0.1" + sonic-boom "^1.0.2" + pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" @@ -17913,18 +17960,6 @@ pkginfo@^0.4.1: resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= -platform@1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.3.tgz#646c77011899870b6a0903e75e997e8e51da7461" - integrity sha1-ZGx3ARiZhwtqCQPnXpl+jlHadGE= - -please-upgrade-node@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" - integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== - dependencies: - semver-compare "^1.0.0" - pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -18637,6 +18672,11 @@ postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, po source-map "^0.6.1" supports-color "^6.1.0" +postgres@^2.0.0-beta.2: + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/postgres/-/postgres-2.0.0-beta.2.tgz#4844381137b6394a66c4c7566aedb63392ffe416" + integrity sha512-x5/FBSLsU/eVeKcIREsaCBeFp5CFbZoberSug3dgsJMlvWwGBtl753hALBVR+GJ5Dty1lb+zfqV9ySHyOndypA== + postinstall-postinstall@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" @@ -18673,19 +18713,6 @@ prebuild-install@5.3.3: tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" -precise-commits@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/precise-commits/-/precise-commits-1.0.2.tgz#4659be01a9c3310f50ce51ddf913fead1d7cc940" - integrity sha512-PYkoNTFXVvZRzJTDxdgzmPanhSNGj5Wtj2NgSo7IhwNXGcKktX+L4DJhyIrhFSLsWWAvd+cYyyU2eXlaX5QxzA== - dependencies: - diff-match-patch "^1.0.0" - execa "^0.9.0" - find-up "^2.1.0" - glob "^7.1.2" - ignore "^3.3.7" - mri "^1.1.0" - ora "^1.3.0" - precond@0.2: version "0.2.3" resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" @@ -18897,7 +18924,7 @@ protons@^1.0.1: signed-varint "^2.0.1" varint "^5.0.0" -proxy-addr@~2.0.5: +proxy-addr@^2.0.5, proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== @@ -18910,7 +18937,7 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= -pseudomap@^1.0.1, pseudomap@^1.0.2: +pseudomap@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= @@ -19114,6 +19141,16 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +queue-microtask@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.0.tgz#f27d002cbfac741072afa0e9af3a119b0e8724a3" + integrity sha512-J95OVUiS4b8qqmpqhCodN8yPpHG2mpZUPQ8tDGyIY0VhM+kBHszOuvsMJVGNQ1OH2BnTFbqz45i+2jGpDw9H0w== + +quick-format-unescaped@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz#437a5ea1a0b61deb7605f8ab6a8fd3858dbeb701" + integrity sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A== + quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" @@ -19967,6 +20004,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +ret@~0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" + integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== + retry@0.12.0, retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -19995,6 +20037,11 @@ rework@1.0.1: convert-source-map "^0.3.3" css "^2.0.0" +rfdc@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" + integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -20117,6 +20164,13 @@ safe-json-utils@1.0.0: resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.0.0.tgz#8b1d68b13cff2ac6a5b68e6c9651cf7f8bb56d9b" integrity sha512-n0hJm6BgX8wk3G+AS8MOQnfcA8dfE6ZMUfwkHUNx69YxPlU3HDaZTHXWto35Z+C4mOjK1odlT95WutkGC+0Idw== +safe-regex2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9" + integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== + dependencies: + ret "~0.2.0" + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -20282,6 +20336,11 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +secure-json-parse@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.1.0.tgz#ae76f5624256b5c497af887090a5d9e156c9fb20" + integrity sha512-GckO+MS/wT4UogDyoI/H/S1L0MCcKS1XX/vp48wfmU7Nw4woBmb8mIpu4zPBQjKlRT88/bt9xdoV4111jPpNJA== + seedrandom@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.1.tgz#eb3dde015bcf55df05a233514e5df44ef9dce083" @@ -20311,26 +20370,16 @@ semaphore@>=1.0.1, semaphore@^1.0.3, semaphore@^1.1.0: resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" integrity sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA== -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" - integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== +semver-store@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/semver-store/-/semver-store-0.3.0.tgz#ce602ff07df37080ec9f4fb40b29576547befbe9" + integrity sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg== "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - semver@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db" @@ -20426,6 +20475,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-cookie-parser@^2.4.1: + version "2.4.6" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.6.tgz#43bdea028b9e6f176474ee5298e758b4a44799c3" + integrity sha512-mNCnTUF0OYPwYzSHbdRdCfNNHqrne+HS5tS5xNb6yJbdP9wInV0q5xPLE0EyfV/Q3tImo3y/OXpD8Jn0Jtnjrg== + set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" @@ -20525,15 +20579,6 @@ shell-quote@1.7.2: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -shelljs@0.7.8: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM= - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shelljs@^0.8.3: version "0.8.4" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" @@ -20781,6 +20826,14 @@ solidity-coverage@^0.7.10: shelljs "^0.8.3" web3 "^1.3.0" +sonic-boom@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-1.3.0.tgz#5c77c846ce6c395dddf2eb8e8e65f9cc576f2e76" + integrity sha512-4nX6OYvOYr6R76xfQKi6cZpTO3YSWe/vd+QdIfoH0lBy0MnPkeAbb2rRWgmgADkXUeCKPwO1FZAKlAVWAadELw== + dependencies: + atomic-sleep "^1.0.0" + flatstr "^1.0.12" + sort-keys-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" @@ -20948,7 +21001,7 @@ split2@^3.1.0: dependencies: readable-stream "^3.0.0" -split@1.0.1, split@^1.0.0: +split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== @@ -21073,40 +21126,6 @@ store@2.0.12: resolved "https://registry.yarnpkg.com/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593" integrity sha1-jFNOKguDH3K3X8XxEZhXxE711ZM= -storyboard-core@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/storyboard-core/-/storyboard-core-3.2.0.tgz#6765e2c71bdc5b8ed1db52faf896df43461bca7c" - integrity sha512-v2p3SDYi22go1oLrJPnsiwXgSsjqKL6oryy9B3rz2UEpLwqWArjUoaECciD+/sfwJFJs1etIjUbE4tBx2G+adQ== - dependencies: - chalk "1.x" - lodash "^4.17.10" - platform "1.3.3" - semver "^5.3.0" - timm "^1.6.1" - uuid "^3.0.1" - -storyboard-listener-console-parallel@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/storyboard-listener-console-parallel/-/storyboard-listener-console-parallel-3.1.4.tgz#110d4582f4ab924c2ca5c56eb6a3c61fe4d162b3" - integrity sha512-VjgpN02DISKSNWNB9eSF3zhWA5nRINQITan/g/CuS45S7YqISKCP3+ztOLLDmfYvrmg6PrjahwLMq0GvQ37ZDA== - dependencies: - terminal-kit "^0.26.1" - timm "^1.6.1" - -storyboard-listener-console@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/storyboard-listener-console/-/storyboard-listener-console-3.1.4.tgz#165c23629721a0327b181d4b926bc2d9787eb661" - integrity sha512-zP2x0XKXHXGYrz4/RS4kxMp/4JqEvREV3rdkfbb6uqGld9gPKNqAdRDLQPN7cJRycTipTt+qew7RFGDTIVSIVA== - dependencies: - timm "^1.6.1" - -storyboard@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/storyboard/-/storyboard-3.1.4.tgz#f39a908f168541ffae2af6ed8a06f27e0eed12e2" - integrity sha512-YkWhyz6IxvL/+kN9iMtcw3XVppqY/J8wBRDMpKxnkMniD/QKsKnHPMsxloob2B2+ONwASQPbzZkNXeyY2/kXwg== - dependencies: - storyboard-core "^3.1.1" - stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -21162,14 +21181,6 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= -string-kit@^0.5.12: - version "0.5.27" - resolved "https://registry.yarnpkg.com/string-kit/-/string-kit-0.5.27.tgz#5bd58b7172d7efd7a2981a398967b8dbc78fabe1" - integrity sha512-folwNms0Xq4SCUmsRZfnj1uQsD1lrH/fTXdGCYgdlDxMEWMfMfvt8A3Fc60/Zwvxj74nVBBJzxc2NaW5KaeWAA== - dependencies: - tree-kit "^0.5.24" - xregexp "^3.2.0" - string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -21194,6 +21205,11 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-similarity@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21" + integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -21696,16 +21712,6 @@ temp-write@^3.4.0: temp-dir "^1.0.0" uuid "^3.0.1" -terminal-kit@^0.26.1: - version "0.26.2" - resolved "https://registry.yarnpkg.com/terminal-kit/-/terminal-kit-0.26.2.tgz#545e61585e90c284782a5bb0d17f6f1be9b8f1ad" - integrity sha1-VF5hWF6QwoR4Kluw0X9vG+m48a0= - dependencies: - async-kit "^2.2.3" - nextgen-events "^0.9.8" - string-kit "^0.5.12" - tree-kit "^0.5.26" - terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -21857,11 +21863,6 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -timm@^1.6.1, timm@^1.6.2: - version "1.7.1" - resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" - integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== - timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" @@ -21872,6 +21873,11 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== +tiny-lru@^7.0.0: + version "7.0.6" + resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-7.0.6.tgz#b0c3cdede1e5882aa2d1ae21cb2ceccf2a331f24" + integrity sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow== + tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" @@ -22001,11 +22007,6 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -tree-kit@^0.5.24, tree-kit@^0.5.26, tree-kit@^0.5.27: - version "0.5.27" - resolved "https://registry.yarnpkg.com/tree-kit/-/tree-kit-0.5.27.tgz#d055a7ae6a087dda918cd92ac8c8c2abf5cfaea3" - integrity sha512-0AtAzYDYaKSzeEPK3SI72lg/io5jrBxnT1gIRxEQasJycpQf5iXGh6YAl1kkh9wHmLlNRhDx0oj+GZEQHVe+cw== - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -22080,7 +22081,7 @@ ts-invariant@^0.4.0, ts-invariant@^0.4.4: dependencies: tslib "^1.9.3" -ts-jest@^26.1.0, ts-jest@^26.4.2: +ts-jest@^26.4.2: version "26.4.2" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.2.tgz#00b6c970bee202ceef7c6e6e9805c4837b22dab8" integrity sha512-0+MynTTzzbuy5rGjzsCKjxHJk5gY906c/FSaqQ3081+G7dp2Yygfa9hVlbrtNNcztffh1mC6Rs9jb/yHpcjsoQ== @@ -24784,11 +24785,6 @@ xmlhttprequest@*, xmlhttprequest@1.8.0: resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= -xregexp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-3.2.0.tgz#cb3601987bfe2695b584000c18f1c4a8c322878e" - integrity sha1-yzYBmHv+JpW1hAAMGPHEqMMih44= - xregexp@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" @@ -24831,11 +24827,6 @@ yaeti@^0.0.6: resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -24846,7 +24837,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.5.1, yaml@^1.7.2: +yaml@^1.5.1, yaml@^1.7.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==