From d79a5ecc82a1a059c449f312447af5888ae9f1e9 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 22 May 2020 21:25:15 +0200 Subject: [PATCH] Add demo interaction for staking contract --- packages/demo-staking/.eslintignore | 1 + packages/demo-staking/.gitignore | 3 + packages/demo-staking/README.md | 8 + packages/demo-staking/jasmine-testrunner.js | 26 +++ packages/demo-staking/karma.conf.js | 54 +++++ packages/demo-staking/nonces/README.txt | 1 + packages/demo-staking/package.json | 48 +++++ packages/demo-staking/src/index.spec.ts | 111 +++++++++++ packages/demo-staking/src/index.ts | 0 packages/demo-staking/src/schema.d.ts | 210 ++++++++++++++++++++ packages/demo-staking/tsconfig.json | 12 ++ packages/demo-staking/typedoc.js | 14 ++ packages/demo-staking/types/index.d.ts | 0 packages/demo-staking/webpack.web.config.js | 19 ++ 14 files changed, 507 insertions(+) create mode 120000 packages/demo-staking/.eslintignore create mode 100644 packages/demo-staking/.gitignore create mode 100644 packages/demo-staking/README.md create mode 100755 packages/demo-staking/jasmine-testrunner.js create mode 100644 packages/demo-staking/karma.conf.js create mode 100644 packages/demo-staking/nonces/README.txt create mode 100644 packages/demo-staking/package.json create mode 100644 packages/demo-staking/src/index.spec.ts create mode 100644 packages/demo-staking/src/index.ts create mode 100644 packages/demo-staking/src/schema.d.ts create mode 100644 packages/demo-staking/tsconfig.json create mode 100644 packages/demo-staking/typedoc.js create mode 100644 packages/demo-staking/types/index.d.ts create mode 100644 packages/demo-staking/webpack.web.config.js diff --git a/packages/demo-staking/.eslintignore b/packages/demo-staking/.eslintignore new file mode 120000 index 00000000..86039baf --- /dev/null +++ b/packages/demo-staking/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/demo-staking/.gitignore b/packages/demo-staking/.gitignore new file mode 100644 index 00000000..68bf3735 --- /dev/null +++ b/packages/demo-staking/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +docs/ diff --git a/packages/demo-staking/README.md b/packages/demo-staking/README.md new file mode 100644 index 00000000..f97fadc5 --- /dev/null +++ b/packages/demo-staking/README.md @@ -0,0 +1,8 @@ +# @cosmwasm/demo-staking + +## License + +This package is part of the cosmwasm-js repository, licensed under the Apache +License 2.0 (see +[NOTICE](https://github.com/confio/cosmwasm-js/blob/master/NOTICE) and +[LICENSE](https://github.com/confio/cosmwasm-js/blob/master/LICENSE)). diff --git a/packages/demo-staking/jasmine-testrunner.js b/packages/demo-staking/jasmine-testrunner.js new file mode 100755 index 00000000..9fada59b --- /dev/null +++ b/packages/demo-staking/jasmine-testrunner.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +require("source-map-support").install(); +const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json"); + +// setup Jasmine +const Jasmine = require("jasmine"); +const jasmine = new Jasmine(); +jasmine.loadConfig({ + spec_dir: "build", + spec_files: ["**/*.spec.js"], + helpers: [], + random: false, + seed: null, + stopSpecOnExpectationFailure: false, +}); +jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000; + +// setup reporter +const { SpecReporter } = require("jasmine-spec-reporter"); +const reporter = new SpecReporter({ ...defaultSpecReporterConfig }); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +jasmine.execute(); diff --git a/packages/demo-staking/karma.conf.js b/packages/demo-staking/karma.conf.js new file mode 100644 index 00000000..e68db403 --- /dev/null +++ b/packages/demo-staking/karma.conf.js @@ -0,0 +1,54 @@ +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: ".", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["jasmine"], + + // list of files / patterns to load in the browser + files: ["dist/web/tests.js"], + + client: { + jasmine: { + random: false, + timeoutInterval: 15000, + }, + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["progress", "kjhtml"], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ["Firefox"], + + browserNoActivityTimeout: 90000, + + // Keep brower open for debugging. This is overridden by yarn scripts + singleRun: false, + + customLaunchers: { + ChromeHeadlessInsecure: { + base: "ChromeHeadless", + flags: ["--disable-web-security"], + }, + }, + }); +}; diff --git a/packages/demo-staking/nonces/README.txt b/packages/demo-staking/nonces/README.txt new file mode 100644 index 00000000..092fe732 --- /dev/null +++ b/packages/demo-staking/nonces/README.txt @@ -0,0 +1 @@ +Directory used to trigger lerna package updates for all packages diff --git a/packages/demo-staking/package.json b/packages/demo-staking/package.json new file mode 100644 index 00000000..45c6d0cf --- /dev/null +++ b/packages/demo-staking/package.json @@ -0,0 +1,48 @@ +{ + "name": "@cosmwasm/demo-staking", + "version": "0.8.0-alpha.0", + "description": "Demo interaction with the staking contract", + "author": "Simon Warta ", + "license": "Apache-2.0", + "main": "build/index.js", + "types": "types/index.d.ts", + "files": [ + "build/", + "types/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "repository": { + "type": "git", + "url": "https://github.com/confio/cosmwasm-js/tree/master/packages/demo-staking" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "docs": "shx rm -rf docs && typedoc --options typedoc.js", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", + "move-types": "shx rm -rf ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts", + "format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"", + "build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "test-node": "node jasmine-testrunner.js", + "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", + "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadlessInsecure", + "test": "yarn build-or-skip && yarn test-node", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "@cosmwasm/sdk": "^0.8.0-alpha.0", + "@iov/crypto": "^2.1.0", + "@iov/encoding": "^2.1.0", + "@iov/stream": "^2.0.2", + "@iov/utils": "^2.0.2" + }, + "devDependencies": { + "@iov/keycontrol": "^2.1.0" + } +} diff --git a/packages/demo-staking/src/index.spec.ts b/packages/demo-staking/src/index.spec.ts new file mode 100644 index 00000000..3fdc434e --- /dev/null +++ b/packages/demo-staking/src/index.spec.ts @@ -0,0 +1,111 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { Coin, Secp256k1Pen, SigningCosmWasmClient } from "@cosmwasm/sdk"; + +import { + BalanceResponse, + HandleMsg, + InitMsg, + InvestmentResponse, + QueryMsg, + TokenInfoResponse, +} from "./schema"; + +function pendingWithoutWasmd(): void { + if (!process.env.WASMD_ENABLED) { + return pending("Set WASMD_ENABLED to enable Cosmos node-based tests"); + } +} + +const httpUrl = "http://localhost:1317"; +const faucet = { + mnemonic: + "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", + address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", +}; + +/** Code info staking.wasm */ +const codeId = 3; + +/** Instance parameters */ +const validator = "cosmosvaloper1ea5cpmcj2vf5d0xwurncx7zdnmkuc6eq696h9a"; +const exit_tax = "0.005"; // 0.5 % + +describe("Staking demo", () => { + it("works", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + + const initMsg: InitMsg = { + name: "Bounty", + symbol: "BOUNTY", + decimals: 3, + validator: validator, + exit_tax: exit_tax, + min_withdrawal: "7", + }; + const { contractAddress } = await client.instantiate( + codeId, + initMsg, + `Staking derivative BOUNTY ${new Date()}`, + ); + + const tokenInfoQuery: QueryMsg = { token_info: {} }; + const tokenInfoResponse: TokenInfoResponse = await client.queryContractSmart( + contractAddress, + tokenInfoQuery, + ); + expect(tokenInfoResponse).toEqual({ decimals: 3, name: "Bounty", symbol: "BOUNTY" }); + + { + const investmentQuery: QueryMsg = { investment: {} }; + const investmentResponse: InvestmentResponse = await client.queryContractSmart( + contractAddress, + investmentQuery, + ); + expect(investmentResponse).toEqual({ + token_supply: "0", + staked_tokens: { denom: "ustake", amount: "0" }, + nominal_value: "1", + owner: faucet.address, + exit_tax: exit_tax, + validator: validator, + min_withdrawal: "7", + }); + } + + const bondMsg: HandleMsg = { bond: {} }; + const amount: Coin = { + amount: "112233", + denom: "ustake", + }; + await client.execute(contractAddress, bondMsg, undefined, [amount]); + + // Status changed + { + const investmentQuery: QueryMsg = { investment: {} }; + const investmentResponse: InvestmentResponse = await client.queryContractSmart( + contractAddress, + investmentQuery, + ); + expect(investmentResponse).toEqual({ + token_supply: "112233", + staked_tokens: { denom: "ustake", amount: "112233" }, + nominal_value: "1", + owner: faucet.address, + exit_tax: exit_tax, + validator: validator, + min_withdrawal: "7", + }); + } + + // Get balance + { + const balanceQuery: QueryMsg = { balance: { address: faucet.address } }; + const balanceResponse: BalanceResponse = await client.queryContractSmart(contractAddress, balanceQuery); + expect(balanceResponse).toEqual({ + balance: "112233", + }); + } + }); +}); diff --git a/packages/demo-staking/src/index.ts b/packages/demo-staking/src/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/demo-staking/src/schema.d.ts b/packages/demo-staking/src/schema.d.ts new file mode 100644 index 00000000..371ee2ff --- /dev/null +++ b/packages/demo-staking/src/schema.d.ts @@ -0,0 +1,210 @@ +/* + * This file was generated with ❤️ by wasm.glass and is licensed + * for you under WTFPL OR 0BSD OR Unlicense OR MIT OR BSD-3-Clause. + * Note that different terms apply for the contract's source code and schema. + * Type generation powered by json-schema-to-typescript. + */ + +export interface BalanceResponse { + balance: string; + [k: string]: unknown; +} + +export interface ClaimsResponse { + claims: string; + [k: string]: unknown; +} + +export type HandleMsg = + | { + transfer: { + amount: string; + recipient: string; + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + bond: { + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + unbond: { + amount: string; + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + claim: { + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + reinvest: { + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + __bond_all_tokens: { + [k: string]: unknown; + }; + [k: string]: unknown; + }; + +export interface InitMsg { + /** + * decimal places of the derivative token (for UI) TODO: does this make sense? Do we need to normalize on this? We don't even know the decimals of the native token + */ + decimals: number; + /** + * this is how much the owner takes as a cut when someone unbonds TODO + */ + exit_tax: string; + /** + * This is the minimum amount we will pull out to reinvest, as well as a minumum that can be unbonded (to avoid needless staking tx) + */ + min_withdrawal: string; + /** + * name of the derivative token (FIXME: auto-generate?) + */ + name: string; + /** + * symbol / ticker of the derivative token + */ + symbol: string; + /** + * This is the validator that all tokens will be bonded to + */ + validator: string; + [k: string]: unknown; +} + +/** + * Investment info is fixed at initialization, and is used to control the function of the contract + */ +export interface InvestmentInfo { + /** + * this is the denomination we can stake (and only one we accept for payments) + */ + bond_denom: string; + /** + * this is how much the owner takes as a cut when someone unbonds + */ + exit_tax: string; + /** + * This is the minimum amount we will pull out to reinvest, as well as a minumum that can be unbonded (to avoid needless staking tx) + */ + min_withdrawal: string; + /** + * owner created the contract and takes a cut + */ + owner: string; + /** + * All tokens are bonded to this validator FIXME: humanize/canonicalize address doesn't work for validator addrresses + */ + validator: string; + [k: string]: unknown; +} + +export interface InvestmentResponse { + /** + * this is how much the owner takes as a cut when someone unbonds + */ + exit_tax: string; + /** + * This is the minimum amount we will pull out to reinvest, as well as a minumum that can be unbonded (to avoid needless staking tx) + */ + min_withdrawal: string; + /** + * A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 + * + * The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18) + */ + nominal_value: string; + /** + * owner created the contract and takes a cut + */ + owner: string; + staked_tokens: { + amount: string; + denom: string; + [k: string]: unknown; + }; + token_supply: string; + /** + * All tokens are bonded to this validator + */ + validator: string; + [k: string]: unknown; +} + +export type QueryMsg = + | { + balance: { + address: string; + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + claims: { + address: string; + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + token_info: { + [k: string]: unknown; + }; + [k: string]: unknown; + } + | { + investment: { + [k: string]: unknown; + }; + [k: string]: unknown; + }; + +/** + * Supply is dynamic and tracks the current supply of staked and ERC20 tokens. + */ +export interface Supply { + /** + * bonded is how many native tokens exist bonded to the validator + */ + bonded: string; + /** + * claims is how many tokens need to be reserved paying back those who unbonded + */ + claims: string; + /** + * issued is how many derivative tokens this contract has issued + */ + issued: string; + [k: string]: unknown; +} + +/** + * TokenInfoResponse is info to display the derivative token in a UI + */ +export interface TokenInfoResponse { + /** + * decimal places of the derivative token (for UI) + */ + decimals: number; + /** + * name of the derivative token + */ + name: string; + /** + * symbol / ticker of the derivative token + */ + symbol: string; + [k: string]: unknown; +} diff --git a/packages/demo-staking/tsconfig.json b/packages/demo-staking/tsconfig.json new file mode 100644 index 00000000..167e8c02 --- /dev/null +++ b/packages/demo-staking/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "declarationDir": "build/types", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/demo-staking/typedoc.js b/packages/demo-staking/typedoc.js new file mode 100644 index 00000000..e2387c7d --- /dev/null +++ b/packages/demo-staking/typedoc.js @@ -0,0 +1,14 @@ +const packageJson = require("./package.json"); + +module.exports = { + src: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + target: "es6", + name: `${packageJson.name} Documentation`, + readme: "README.md", + mode: "file", + excludeExternals: true, + excludeNotExported: true, + excludePrivate: true, +}; diff --git a/packages/demo-staking/types/index.d.ts b/packages/demo-staking/types/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/demo-staking/webpack.web.config.js b/packages/demo-staking/webpack.web.config.js new file mode 100644 index 00000000..7373cace --- /dev/null +++ b/packages/demo-staking/webpack.web.config.js @@ -0,0 +1,19 @@ +const glob = require("glob"); +const path = require("path"); +const webpack = require("webpack"); + +const target = "web"; +const distdir = path.join(__dirname, "dist", "web"); + +module.exports = [ + { + // bundle used for Karma tests + target: target, + entry: glob.sync("./build/**/*.spec.js"), + output: { + path: distdir, + filename: "tests.js", + }, + plugins: [new webpack.EnvironmentPlugin(["WASMD_ENABLED"])], + }, +];